Skip to content

Commit

Permalink
Feat: add endpoint and reference types
Browse files Browse the repository at this point in the history
  • Loading branch information
utkusarioglu committed Feb 4, 2021
1 parent 00bfa6e commit 5079787
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 5 deletions.
211 changes: 211 additions & 0 deletions README.md
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
6 changes: 3 additions & 3 deletions src/endpoints/post.endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import {
import { uuid } from '../helpers/helpers';

/**
* Single post
* Get single post by its slug
*
* @endpoint GET /api/post/<post id>
* @endpoint GET /api/post/slug/<postSlug>
*/
export type PostGetRes = {
id: uuid;
Expand All @@ -28,7 +28,7 @@ export type PostsGetRes = {
/**
* All the comments on a single post
*
* @endpoint GET /api/post/<post id>/
* @endpoint GET /api/post/comments/<post id>/
*/
export type PostCommentsGetRes = {
id: uuid;
Expand Down
2 changes: 1 addition & 1 deletion src/endpoints/vote.endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { CommentVoteSave, PostVoteSave } from '../references/vote.reference';
/**
* Receives the Votes cast by a specific post
*
* @endpoint POST /api/post/<post id>/vote/
* @endpoint POST /api/post/<post slug>/vote/
*/
export type PostVotePostReq = {
id: uuid;
Expand Down
6 changes: 5 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ export type {
export type { UserSqlAutoSave } from './references/user.reference';
export type { UserGetRes, UserPostReq } from './endpoints/user.endpoint';

export type { PostGetRes, PostPostReq } from './endpoints/post.endpoint';
export type {
PostGetRes,
PostsGetRes,
PostPostReq,
} from './endpoints/post.endpoint';
export type {
PostSqlAutoSave,
PostStoreAutoSave,
Expand Down
3 changes: 3 additions & 0 deletions src/references/post.reference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ interface Post {
communitySlug: string;
communityName: string;

/** Post creator related */
postCreatorUsername: string;

/** media related properties */
mediaImagePath: string; // uri
mediaType: 'image' | 'video' | 'none';
Expand Down

0 comments on commit 5079787

Please sign in to comment.