-
Notifications
You must be signed in to change notification settings - Fork 0
Contribution Guidelines
This document is a detailed guide about how we work in this project. We assume that you have already setup the project by following the project's readme guide.
Prerequisites
- Git
- NodeJS (>= v8)
- Yarn
- Restify
- TypeScript
- REST APIs
- Unit Testing (Chai/Mocha)
- Workflow Related
- File Structure
- Using TypeScript
- Creating an Endpoint
- Middlewares
- Utilities
- Testing
- Documentation
The commit messages should be in present tense and start with plurals. Please make them short and concise so its easier to look at history and make sense of those.
Good commit message: fixes jwt_token_invalid error in auth middleware
Bad commit message: fixed auth middleware issue
or fix auth middleware issue
When submitting Pull Requests, please make sure the following is done:
- You have tested the feature you have introduced or you have updated the tests if a bug is solved
- You have ran
tests
by doingyarn test
and they pass. - You have ran the
linter
by doingyarn lint
and it passes. - You have ran the
dot-only-hunter
by doingyarn hunt
and it passes. - You have squashed all the commits and rebased with
development
The title of which should compose of the following:
{branch}: {<message>}
The message should be a broad overview of what you added. It should start with . The PR's description should be used to elaborate what you did in this PR so that its easy for the reviewer to review it. Following is a good PR title.
feat/auth: adds jwt auth middleware
Note: We have CI in place so whenever you'll make PR it'll run the tests and linter on the files. If tests fail or linter fails, the build's not gonna pass. You are responsible for making PR's which passes the build. Fix your PRs build to make sure it passes.
The branch name depends upon the type of the work you're doing. Following are valid names:
- bug/{ticket number or short name} if its bug fix
- feat/{ticket number or short name} if its a feature
- change/{ticket number or short name} if its short task
- hotfix/{ticket number or short name} if it needs to be merged in production immed.
We do reviews of every PRs. Although we have some automated tools in place, its always good to have someone look at the code. So after you create PR, assign some other developer to review your code. The developer will either request changes or merge it.
We use _
as separator beteween words to be used as filenames or folder names. So auth-module
is invalid, auth_module
is correct. Following is the file structure:
├───app/ │ ├───interfaces/ │ ├───middlewares/ │ ├───models/ │ ├───modules/ ├───config/ ├───types/ │ └───types.d.ts ├───utils/ │ ├───logger.ts ├───.editorconfig ├───.nvmrc ├───.travis.yml ├───README.md ├───nodemon.json ├───package.json ├───server.ts ├───tsconfig.json ├───tslint.json └───yarn.lock
Modules:
The modules go in modules folder. It should be abstracted by domain. So for example, login, signup
module is abstracted inside auth
module. Ethereum related modules are in eth
folder. A module is made up of route, controller and its test.
Utils: Utils folder contains the utilities or any helpers that are shared by the repo. e.g logger
Interfaces: It contains the typescripts interfaces.
Middlewares: It has the server's middlewares that we need to configure.
Models: It has the db models. After defining the model, its must to create its TypeScript interface in app/interfaces/models.ts
We use typescript language. Typescript offers many things thats missing in Javascript which makes it a pain to develop apps in javascript. TypeScript offers strict checking which makes it easy to spot bugs before hand. Also it has awesome intellisense support in VS Code which makes it a bliss to develop apps in it.
Some useful topics are:
To create an endpoint, first you need to create a module folder under app/modules
the name should be specific. Then under that folder we need to create 3 files:
-
module_name.controller.ts
(controller) -
module_name.route.ts
(route, its necessary to add .route postfix so its automatically picked up by server) -
module_name.route.unit.spec.ts
(unit test in chai/mocha)
We're using REST specifications to structure our API. A good article on REST api guidelines by hackernoon can be found here. The endpoints should:
- have very specific and self descriptive name
- should be plural whenever possible like
/api/users
instead of/api/user
- should have correct http method.
- GET is used to fetch data
- PUT is used to update
- POST is used to create
- DELETE is used to delete
- should contain
_
to as separator
import ModuleController from './module_name.controller';
import * as Joi from 'joi';
import { IRoute, IRouteConfig, HttpMethods, AuthStrategies } from '../../interfaces/utils/Route';
import { oneLine } from 'common-tags';
class ModuleRoute implements IRoute {
public basePath = '/api/endpoint_name';
public controller = new ModuleController();
public swaggerTag = 'OAuth2';
public getServerRoutes(): IRouteConfig[] {
return [
{
method: HttpMethods.GET,
auth: AuthStrategies.PUBLIC,
handler: this.controller.post,
param: 'id',
validation: {
schema: {
query: {
any_query: Joi.string().required(),
},
params: {
id: Joi.string().required()
}
}
},
swagger: {
summary: 'Create Something',
description: oneLine`
This is the swagger documentation tag, a documentation will be
automatically created out of this
`,
responses: [
{
code: 200, // Status code for responses, you can add others like 404 etc
data: {} // For Swagger doc, show here what data is returned by EP.
}
]
},
}
];
}
}
export default ModuleRoute;
The module's route class implements the IRoute
interface which has some properties:
- basePath: Path for endpoint, it should be according to REST patterns
- controller: Should be an instance of module's respective controller which implements IController interface.
-
swaggerTag: It should be the abstracted module name, its used to group the endpoints under certain name. Used for swagger documentation. For example, auth module contains login and signup endpoint. So the
swaggerTag
for those 2 modules isauth
.
The getServerRoutes
is a function that must have at least a minimum of 1 route method and maximum of 5 routes (get, getAll, post, put, delete - a CRUD basically) It has certain properties which are required:
-
method: must be
HttpMethods
'sPOST, PUT, GET or DELETE
-
auth: must be
AuthStrategies
'sOAUTH, JWT or PUBLIC
.-
JWT
auth is used for ThinBlock's internal API -
OAUTH
is used for endpoints which are consumed by developers having a valid OAuth token. -
PUBLIC
is for endpoint which doesn't require any auth. Like login, signup.
-
-
handler: Must be a controller method, should be one of
get, getAll, put, post, delete
. -
param: If this key exist, it'd be appended to the
basePath
as param. SO the above route becomes:/api/endpoint_name/:id
-
validations.schema: Its an object containing Joi schema for
params, query & body
-
swagger: This is used to generate automatic documentation from this route. It has 3 attributes, all required:
-
summary
: brief summary about the endpoint. -
description
Full description of the endpoint, what to expect from it etc.. -
responses
: Its an array of responses that the consumer should expect while consuming the endpoint. It hascode
which is status_code anddata
which is data to be returned by the endpoint for that status code.
-
import * as restify from 'restify';
import { InternalServerError, NotFoundError } from 'restify-errors';
import IController from '../../interfaces/utils/IController';
import { IRequest, IResponse } from '../../interfaces/utils/IServer';
export default class KeysController implements IController {
public async getAll(req: IRequest, res: IResponse, next: restify.Next) {
return next();
}
public async get(req: IRequest, res: IResponse, next: restify.Next) {
return next();
}
public async post(req: IRequest, res: IResponse, next: restify.Next) {
return next();
}
public async put(req: IRequest, res: IResponse, next: restify.Next) {
return next();
}
public async delete(req: IRequest, res: IResponse, next: restify.Next) {
return next();
}
}
Controller implementation is pretty straightforward. We have 5 methods in the IController
interface which can be implemeneted as per requirement.
Difference between get
and getAll
is that get
method is used to fetch only 1 resource. While getAll
is used to fetch all records related to endpoint. Like /api/users/1
and /api/users
respectively.
Models go into app/models
. They must end with .model.ts
for easy search in IDEs. We're using Sequelize as ORM and using PostgreSQL as db. Whenever you create a model you should create its respective TypeScript Interface in app/interfaces/models.ts
. The table names must be plural and model names must be singular.
We have consistent endpoint responses throughout the API.
- POST is successfully, return the newly created entity
- For retreiving single items, just return the item as response
- For retreiving an array, return an object with
result
as items.{ result: [..items], total_results: 3 }
- For acknlowedging soemthing is successfull, return
{success: true, message: '<message>'}
- If request was successfull but there is some error, you can return
{error: true, message: '<message>'}
e.g{error: true, message: 'User already exists'}
There are some examples in app/middlewares
folder. Writing middleware is easy. It must call next
function to execute next middleware in chain. To register the middleware, register it in config/restify.ts
Utilities or helpers must go in utils/
these are peices of code which is shared by the whole repo.
The documentation is automatically generated from the routes. The docs are generated and uploaded by CI. The site for documentation is http://thinblockdev.docs.apiary.io
In the route's definition there is swagger
object which has 3 keys.
swagger: {
summary: 'breif description of endpoint here',
description: 'detailed description about the endpoint and what to expect from it should go here',
resposnes: [ // its an array of responses which we can expect from endpoint
{
code: 200, // The status code that endpoint returns
data: { // The example data that endpoint returns when status code is above.
success: true,
message: 'Deleted successfully'
}
}
]
}