Skip to content

Contribution Guidelines

Hamza Baig edited this page Aug 2, 2018 · 2 revisions

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)

Contents

Workflow Related

Commits

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 issueor fix auth middleware issue

Submitting PRs

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 doing yarn test and they pass.
  • You have ran the linter by doing yarn lint and it passes.
  • You have ran the dot-only-hunter by doing yarn 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.

Branches

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.

Reviews

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.

File Structure

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

Using TypeScript

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:

Creating an Endpoint

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)

Naming Endpoints

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

Route:

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 is auth.

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's POST, PUT, GET or DELETE

  • auth: must be AuthStrategies's OAUTH, 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 has code which is status_code and data which is data to be returned by the endpoint for that status code.

Controller:

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.

Adding Models

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.

Responses

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'}

Middlewares

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

Utilities or helpers must go in utils/ these are peices of code which is shared by the whole repo.

Documentation

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'
      }
    }
  ]
}