Skip to content

utkusarioglu/six__public-api

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

43 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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:

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:

/**
 * 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:

/**
 * 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:

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:

/**
 * 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:

// 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.

// 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.

// 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.

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

Releases

No releases published

Packages

No packages published