-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat: add endpoint and reference types
- Loading branch information
1 parent
00bfa6e
commit 5079787
Showing
5 changed files
with
223 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
# Six Public Api | ||
|
||
Six is an api-first project written in Node + Typescript. This allows the | ||
possibility of creating a separate submodule only for the api types and have | ||
these types guide the shape of data both on the backend and the frontend. | ||
|
||
## What's the idea? | ||
|
||
This repository is designed as a submodule. It is referenced by the store types | ||
in the backend and by the rest clients on the frontend. Changes in the shape of | ||
data in both of these ends is defined through first importing the types from the | ||
api and then manipulating them in the ways it is required. | ||
|
||
Let's say we have a data type like this: | ||
|
||
```ts | ||
interface User { | ||
id: string; // uuid | ||
name: string; | ||
age: number; | ||
lastName: string; | ||
} | ||
``` | ||
|
||
For a user creation post request, the data that would be needed from the user | ||
could be this: | ||
|
||
```ts | ||
/** | ||
* Values generated by data store default values | ||
*/ | ||
type UserAutoSave = Pick<User, 'id'>; | ||
|
||
/** | ||
* Values that have to come from the user input | ||
*/ | ||
type UserSave = Omit<User, keyof UserAutoSave>; | ||
``` | ||
|
||
That is, the id is not needed to be received from the user. At the frontend, the | ||
client would use `UserSave` as the interface that the related user creation form | ||
would have to gather. | ||
|
||
At the backend the same process could be handled this way: | ||
|
||
```ts | ||
/** | ||
* Data shape that is used in insert statements | ||
*/ | ||
interface UserInsert { | ||
name: UserSave['name']; | ||
age: UserSave['age']; | ||
last_name: UserSave['lastName']; | ||
} | ||
|
||
/** | ||
* Data shape for values that are created by the storage system | ||
*/ | ||
interface UserInsertAuto { | ||
id: UserAutoSave['id']; | ||
} | ||
|
||
/** | ||
* Return of a row with a `SELECT * ...` statement | ||
*/ | ||
type UserModel = UserInsert & UserInsertAuto; | ||
``` | ||
|
||
The value exchanges between UserInsert and UserSave is done to convert the | ||
properCase keys of the JS/TS maps to the lower_snake_case convention for the | ||
data storage. **This stage is also designed to be where the data checks are | ||
made**. | ||
|
||
`UserModel` is the shape of an entire row, which would be equivalent to the | ||
`User` interface defined above. The only difference between the two is the | ||
casing used for the keys of the objects. | ||
|
||
## What about associations? | ||
|
||
For shapes that require associations, the reference is made at the level where | ||
associated data is attached. Let's say we have the following shape for posts: | ||
|
||
```ts | ||
interface Post { | ||
id: string; // uuid | ||
title: string; | ||
body: string; | ||
createdAt: string; // iso date | ||
} | ||
|
||
type PostAutoSave = Pick<Post, 'id', 'createdAt'>; | ||
|
||
type PostSave = Omit<Post, keyof PostAutoSave> & { | ||
userId: UserGetRes['id']; | ||
}; | ||
``` | ||
|
||
`userId` is referenced from `UserGetRes`, which by the naming convention that is | ||
covered in this readme, refers to the User data retrieved from the GET response | ||
from the backend. This is the natural source of data for the value used here. | ||
Hence, it is what is referenced in the PostSave object. | ||
|
||
The same userId could be referenced from `User['id']`. However this would be an | ||
antipattern as this is not how the frontend accesses the data. | ||
|
||
On the backend the save operation would use the `PostSave` type in this fashion: | ||
|
||
```ts | ||
/** | ||
* Sql post insert statement values that come from user input | ||
*/ | ||
interface PostInsert { | ||
title: PostSave['title']; | ||
body: PostSave['body']; | ||
} | ||
|
||
/** | ||
* sql post insert statement values that are created by the data store | ||
*/ | ||
interface PostInsertAuto { | ||
id: PostAutoSave['id']; | ||
createdAt: PostAutoSave['createdAt']; | ||
} | ||
|
||
/** | ||
* User - Post association table insertion values | ||
*/ | ||
interface UserPostInsert { | ||
post_id: PostSelect['id']; | ||
user_id: PostAutoSave['userId']; | ||
} | ||
``` | ||
|
||
The important thing to notice is that the PostSave values are divided between 2 | ||
different interfaces: `PostInsert` and `UserPostInsert`. The latter being the | ||
association table that connects the Users with the Posts that they create. | ||
|
||
Once again the property case change is used as the point where data checks and | ||
conversions are made. | ||
|
||
## Naming conventions | ||
|
||
### Rest Methods | ||
|
||
The endpoints folder contains the types that the endpoints need implement. The | ||
convention for these goes as follows: | ||
|
||
``` | ||
[Nouns][rest method][Res | Req] | ||
``` | ||
|
||
Some examples would be: | ||
|
||
```ts | ||
// Response for a get request for the endpoint for sending posts | ||
PostsGetRes; | ||
|
||
// Post Request at the endpoint for post creation | ||
PostPostReq; | ||
|
||
// Response for a Patch request for the endpoint for UserSettings | ||
UserSettingsPatchRes; | ||
``` | ||
|
||
These endpoints receive their type definitions from the types in the references | ||
folder. **They do not define their own types from scratch**. | ||
|
||
### Req, Res | ||
|
||
Request and Response respectively, are used to distinguish the direction of the | ||
data at the reference level. | ||
|
||
```ts | ||
// Means that the data is going from back to front | ||
UserRes; | ||
|
||
// Means that the data is going from front to back | ||
UserSettingsReq; | ||
``` | ||
|
||
### Save | ||
|
||
Types involved with data saving, patching, have the postfix `Save`. | ||
|
||
```ts | ||
// Contains the data that is required to save a post | ||
PostSave; | ||
``` | ||
|
||
Down the pipeline, the `Save` types are converted to `Insert` types before | ||
reaching the data store. | ||
|
||
### Auto | ||
|
||
These signify properties/values that are created without user input. Typical | ||
examples of these are row ids, createdAt values. | ||
|
||
```ts | ||
type PostAutoSave = { | ||
id: string; // uuid | ||
createdAt: number; // js epoch | ||
}; | ||
``` | ||
|
||
These will probably come paired with `Save` and `Insert` types for most of the | ||
time. | ||
|
||
### Insert | ||
|
||
Insert is the type of data that is used by sql inserts. They come from `Save` | ||
types |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters