Node.js - Express, MongoDB, Passport REST API and Generalization of CRUD API Boilerplate

PRs Welcome Build Status Coverage Status Greenkeeper badge

Features

  • Generalization of CRUD API (No need to define CRUD api for every collection just define schema and permissions)
  • File upload and download to filesystem or database
  • Commom API using dispatch and actions
  • Uses yarn
  • No transpilers, just vanilla javascript with ES2017 latest features like Async/Await
  • Express + MongoDB (Mongoose)
  • CORS enabled and uses helmet to set some HTTP headers for security
  • Load environment variables from .env files with dotenv
  • Consistent coding styles with editorconfig
  • Gzip compression with compression
  • Linting with eslint
  • Code coverage with istanbul and coveralls
  • Git hooks with husky
  • Logging with morgan
  • Authentication and Authorization with passport
  • Rate limiting with express-rate-limit
  • API documentation generation with apidoc

Prerequisites

Getting Started

  1. Clone the repo and make it yours:
git clone https://github.com/rameshbansal/node-boilerplate.git node-api
cd node-api
rm -rf .git
  1. Install dependencies:
npm install
  1. Set environment variables:
cp .env.example .env

Running Locally

yarn dev

Running in Production

yarn start

Basic CRUD operations with roles and permissions

Developer just have to create an roles config file with permissions and by defining the mongo schema of collection he will get basic CRUD API

Permission file

const ROLES = require('./roles');


const grantList =
    {
      [ROLES.SUPERADMIN]: {
        User: {
          read_any: {
            filter: {},
            allowedFields: ['*'],
            deniedFields: ['password'],
          },
          read_own: {
            filter: { _id: '=currentUser' },
            allowedFields: ['*'],
            deniedFields: ['services', 'password'],
          },
        },
        Customer: {
          read_any: {
            filter: { },
            allowedFields: ['*'],
            deniedFields: ['email'],
          },
          read_own: {
            filter: { _id: '=currentUser.customer' },
            allowedFields: ['*'],
            deniedFields: ['email'],
          },
          create_own: {
            setter: { user: '=currentUser._id' },
            allowedFields: ['*'],
            deniedFields: ['user'],
          },
          remove_any: {
            filter: {},
          },
          remove_own: {
            filter: { user: '=currentUser._id' },
            allowedFields: ['*'],
          },
          update_any: {
            filter: {},
            allowedFields: ['*'],
            deniedFields: ['address.country.name'],
            $pop: {
              allowedFields: ['*'],
            },
            $pull: {
              allowedFields: ['location', 'grades'],
            },
            $unset: {
              allowedFields: ['address.city'],
            },
          },
          update_own: {
            filter: { user: '=currentUser._id' },
            allowedFields: ['*'],
            deniedFields: ['address.country.name'],
            $pop: {
              allowedFields: ['*'],
            },
            $pull: {
              allowedFields: ['location', 'grades'],
            },
            $unset: {
              allowedFields: ['address.city'],
            },
          },
        },
      },
      [ROLES.CUSTOMER]: {
        User: {
          read_any: {
            filter: { _id: { $ne: '=currentUser' } },
            allowedFields: ['*'],
            deniedFields: ['password', 'role'],
          },
          read_own: {
            filter: {},
            allowedFields: ['*'],
            deniedFields: ['password', 'role', 'services'],
          },
        },
      }
    };

module.exports = grantList;

To call basic CRUD api on graphql

update

{
"variables":  {"setter":{"$set": {"name": "ramesh17"}}},
"query": "mutation($setter: JSON){CustomerUpdateOwn(filter:{_id: \"5d5778bddba2222371172036\"},setter: $setter) { _id email name age }}"
}

Remove

{
"query": "mutation{CustomerRemoveOwn(filter:{_id: \"5d578804de1c752f987a2a20\"})}"
}

insert

{
"query": "mutation{CustomerCreate(name: \"ramesh\", email: \"ramesh.bansal+16@daffodilsw.com\",grades: [35]) { _id email}}"
}


query

{
"variables":  {"filter": {"email": {"$in": ["ramesh.bansal@daffodilsw.com","ramesh.bansal+1211@daffodilsw.com"]}}},
"query": "query($filter: JSON){UserAny(filter: $filter, perPage : 10, page: 1, sort: {_id: 1}) { _id createdAt email name customer{_id age} role picture resetPasswordToken emailVerified emailVerificationCode services{ google { url } } }}"
}

File upload and download to filesystem or db

Developer just have to declare config for file upload in vars file

fileUpload: {
    type: 'local', // local or S3 or db
    buckets: {
      public: { permissions: { write: 'ANY', read: 'ANY' } },
      app: { permissions: { write: ['SUPERADMIN'], read: ['SUPERADMIN', 'CUSTOMER'] } },
      authBucket: { permissions: { write: ['CUSTOMER'], read: ['SUPERADMIN', 'CUSTOMER'] } },
    },

  },

Roles and permission will also work file upload and download

Common API implementation using dispatch and action

Developer just have to define actions and actions can be called as API

Example action file

const Joi = require('joi');

module.exports = {
  /**
     * @api {all} v1/dispatch/testing Dispatch Example
     * @apiDescription this is example dispatch api for developers at server side
     * @apiVersion 1.0.0
     * @apiName Testing
     * @apiGroup Dispatch
     * @apiPermission public or private or role based
     *
     * @apiHeader {String} Authorization  User's access token required for public or role based api
     *
     * @apiParam  {String}              Email     Email of the tester
     * @apiParam  {String}              name      Name of the tester
     * @apiParam  {String}              role      Role of the tester
     *
     * @apiSuccess (Done 200) {Object}  response    response object
     *
     * @apiError (Bad Request 400)   ValidationError  Some parameters may contain invalid values
     * @apiError (Unauthorized 401)  Unauthorized     Only authenticated users
     * @apiError (Forbidden 403)     Forbidden        You are not allowed to access this API
  */
  testing: {
    public: true,
    roles: ['SUPERADMIN'],
    joi: {
      email: Joi.string().email().required(),
      name: Joi.string().max(128),
      role: Joi.string().valid(['CUSTOMER', 'ADMIN', 'SUPERADMIN']),
    },
    dispatch: async ({
      params, user, getModel, dispatch,
    }) => {
      const Users = await getModel('User').get({ filter: { email: params.email } });
      return { users: Users };
    },
  },
};

using public and roles keys you can apply auth and permissions. using joi key you can validate the incoming fields in request params,body and query.

Lint

# lint code with ESLint
yarn lint

# try to fix ESLint errors
yarn lint:fix

# lint and watch for changes
yarn lint:watch

Logs

# show logs in production
pm2 logs

Documentation

# generate and open api documentation
yarn docs

Rate Limit Configuration

Change configuration in .env file