Skip to content
Sandny Blog
  • Home
  • Java
  • JavaScript
  • AWS
  • Embedded
  • About
modular approach to create API Express.js

A Modular Approach to create API using Express.js and…

  • November 5, 2017August 26, 2018
  • by Coder Kai

In this post, I would like to share a Modular Approach to create API generated with express.js. The modular approach which discussed here is having a module which can perform each and every task relevant to the API. It has following features.

Features of Modular Approach to create API

  1. The functionality is well defined.
  2. Modules are separated context wise and functionality wise. They are entities which can be treated individually which make them modular.
  3. Every module poses similar behaviours. So each module has a similar footprint to invoke methods and provide service.

If you find these kinds of features it would be appropriate to come up with a modular architecture. It would provide following advantages

  1. All have the same footprint and therefore it would be easy to develop and introduce new features.
  2. Can alter the underlying architecture once and provide the change to each module easily.
  3. The module integration is easy and therefore it will save time when developing new features.

But there are some cons too.

  1. Since all the module footprints are the same, it would have a high coherence to the architecture itself. If you need a functionality that cannot be provided with the architecture, you will have to introduce it in a new definition. But if you come into a such a situation, I think there could be a solution as I didn’t find such a case when I was developing.
  2. If you want to change some intermediate service or a middleware, it would affect all the modules. So be careful when coding the underlying architecture and look towards any unwanted repetitive function invocations.

That being said let’s get our fingers working.

Hands on code!

Most of the APIs defined in express has following features,

  • A router with the paths and http request methods.
  • A schema which can validate request body before forwarding towards next middleware.
  • Handler functions to treat the request and generate a response.

These features are well defined, modular and have similarities. So we can use this to create a module. A module would first be a folder containing module footprints. So our folder structure should contain modules. I’m going to show you a todo example in this post.

-/ src
      -/ initializer/
      -/ modules
               - / todo
                        - / Handler.js
                        - / Router.js
                        - / Schema.js
               - / module2
               - / module3

Each module contains three files to add defined functionalities. My application is a simple todo app and the idea is all the todo related functionalities are driven by this API. This contains GET_TODO, GET_ALL_TODOS, POST_TODO and DELETE_TODO http requests. I have named the key variables as event types which could happen in the todo app context. So I will define the path as follows in Router.js.

export const API_EVENTS = {
  POST_TODO: 'POST_TODO',
  GET_TODO: 'GET_TODO',
  GET_ALL_TODOS: 'GET_ALL_TODOS',
  DELETE_TODO: 'DELETE_TODO',
};

export const todoAPI = {
  [API_EVENTS.POST_TODO]: {
    method: 'POST',
    path: '/'
  },
  [API_EVENTS.GET_TODO]: {
    method: 'GET',
    path: '/:id'
  },
  [API_EVENTS.GET_ALL_TODOS]: {
    method: 'GET',
    path: '/'
  },
  [API_EVENTS.DELETE_TODO]: {
    method: 'DELETE',
    path: '/:id'
  },
};

And I have a schema to validate the POST_TODO event. I have used Joi library to validate the http body attributes. Following would be the code in our Schema.js file.

import Joi from 'joi';

const postTodo = Joi.object().keys({
  todo: Joi.string().alphanum().min(3).max(30).required(),
  timestamp: Joi.number(),
});

const schema = {
  POST_TODO: postTodo,
}

export default schema;

Then someplace to handle the events requested from the router. I will have the handler to the API_EVENTS defined in the router. I have defined an array of todos so that I could store and delete the todos.

import { API_EVENTS } from './Router';

let todos = [];

export const handler = {
  [API_EVENTS.GET_ALL_TODOS]: (req, res, next) => {
    res.send(JSON.stringify(todos));
  },
  [API_EVENTS.POST_TODO]:  (req, res, next) => {
    const { todo, timestamp } = req.body;
    todos.push({ todo, timestamp })
    res.send('OK');
  },
  [API_EVENTS.GET_TODO]:  (req, res, next) => {
    res.send(JSON.stringify(todos[req.params.id]));
  },
  [API_EVENTS.DELETE_TODO]:  (req, res, next) => {
    const deleteId = req.params.id;
    if (deleteId && todos[req.params.id]) {
      todos.splice(deleteId, 1);
      res.send('OK');
    } else {
      res.send('BAD');
    }
  },
};

export default handler;

How is that?  You have a full defined API with above code lines. And you can customize anything as well. Now, how did we do this? We haven’t imported fancy stuff to connect these code lines. That is what modular approach to create API structure is all about. We should be able to keep what is necessary and do the connections somewhere else. I have created a package called initializer to initialize all the routes and add it into the express context.

How to do it

First one to go would be the app.js where all the app is created and all the others are combined. I have included the hello at the root path just to for you to do anything you want with it.

import { connectRouters, express } from './initializer/framework';
import bodyParser from 'body-parser';

const app = express();

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json({ type: 'application/json' }));
connectRouters(app);

app.get('/', function (req, res) {
  res.send('Hello from Todo API')
});

app.listen(3000, function () {
  console.log('TODO API app listening on port 3000!')
});

Afterwards, we need to construct this connectRouters method since it’s the place where we bind all the routers, handlers and schemas. Before going towards that, we will first try to aggregate the individual module functionalities. I have created three files called routers.js, schemas.js and handlers.js inside initializer package and I have used lodash to create the data structure I want. And a file called appModules.js so that I can define where my module is.

// appModule.js
export const modules = [
  'todo',
];

export default modules;
import modules from './appModules';
import _ from 'lodash';

// routers.js
export const routers = _(modules)
  .mapKeys(module => module)
  .mapValues((routerName) => {
  try {
    return require(`../modules/${routerName}/Router`).default;
  } catch (error) {
    console.log(error);
    throw 'Router names are not configured properly';
  }
}).value();


export default routers;
import appModules from './appModules';
import _ from 'lodash';

// schemas.js
export const schemas = _(appModules)
  .mapKeys(module => module)
  .mapValues((module) => {
    try {
      return require(`../modules/${module}/Schema`).default;
    } catch (error) {
      console.log(error);
      throw 'Schema names are not configured properly';
    }
  }).value();

export default schemas;
import appModules from './appModules';
import _ from 'lodash';

//handlers.js
export const handlers = _(appModules)
  .mapKeys(module => module)
  .mapValues((module) => {
    try {
      return require(`../modules/${module}/Handler`).default;
    } catch (error) {
      console.log(error);
      throw 'Handler names are not configured properly';
    }
  }).value();

export default handlers;

We can see some repetition here but its better if you can keep this files separately as in future you will need to complicate these files in a different way to support the features you need.

The Framework that holds these codes

After all you will need to create the framework.js file to combine these three files. I have included the whole file I coded. And please do note that this is not complete and you will need to add more beauty to the code if you intend to use this.

import express from 'express';
import Joi from 'joi';
import _ from 'lodash';
import modules from './appModules';
import schemas from './schemas';
import handlers from './handlers';
import routers from './routers';

const createRouter = () => (routingContext) => {
  const router = express.Router();
};

const validatorMiddleware = (schema) => (req, res, next) => {
  if (_.isNull(schema)) {
    console.log('The schema is null')
    next();
  } else {
    const result = Joi.validate(req.body, schema);
    console.log('Req Body: ', req.body);
    result.error === null ? next() : res.status(422).json({ errors: result.error});
    console.log(result.error);
  }
};

const getRouterPath = (moduleName) => `/${moduleName}`;

const defaulHandler = (req, res) => {
  res.status(404).json({ errors: ' Not Implemented'});
};

const connectRouters = (app) => {
  console.log('connecting Routers', JSON.stringify(routers));
  _.forEach(modules, (moduleName) => {
    const router = express.Router();
    const moduleSchema = schemas[moduleName];
    const moduleHandler = handlers[moduleName];
    console.log(JSON.stringify(handlers));
    _.forEach(routers[moduleName], (api, apiKey ) => {
      const { path, method } = api;
      const schema = _.isNil(moduleSchema[apiKey]) ? null : moduleSchema[apiKey];
      const handler = _.isNil(moduleHandler[apiKey]) ? defaulHandler : moduleHandler[apiKey];
      // connection
      router[_.lowerCase(method)](path, validatorMiddleware(schema), handler);
    });
    app.use(getRouterPath(moduleName), router);
  });
};

export {
  _,
  createRouter,
  express,
  validatorMiddleware,
  connectRouters,
}

In the connectRouters function, the modules are iterated and all the handlers, routers and schemas are connected. You can include more middlewares when you are making the connection with the router. And there are simpler ways to code this and you will have to define how to use it. And also this was coded in the way I described in the post and you could try it too. I just came up with idea modular approach to create API coded it.
I don’t like the data structure I created for storing the modules. If you could make it flatter and 1 level deep that would be awesome. But it is a one-time initialization and there is no harm in it.

The github repo for this tutorial is in https://github.com/sandaruny/moduler-express-api

Dont forget to add your feedback on this coz I want to know what could go wrong with this.

Debounce and avoid multiple clicks event generation on React.js components using Lodash
How to use antd components without the whole library
Coder Kai
A humble developer
api babel express.js javascript modular approach modules nodemon server

Related articles

Multiple refs for an array of React Elements
Using multiple refs for an…
Immutable and Mutable Values in Javascript
07. Immutable and Mutable Values…
wrapper objects in javascript
06. Wrapper objects in Javascript
globals undefined and null values in javascript
05 Global, null and undefined…
Javascript Booleans and Equality
04. Javascript Guide Booleans and…
How to add Chakra UI Animations
Chakra UI Animations
SSL Websocket using Nginx Proxy
SSL Websocket proxy with Nginx…
Change python version correctly
Python is not setting correct…
optimize React.js load time
How to optimize React.js app…
Multiple refs for an array of React Elements
How to use IntersectionObserver to…
Multiple refs for an array of React Elements
How to dismiss dropdowns when…
Javascript guide Strings
03. Javascript Guide – Strings
How to fetch data with useEffect
How to fetch data with…
add styles to stripe elements
How to add styles to…
Typescript
How to use Typescript with…
how to optimize react-native map view
How to optimize react-native map…
debounce with react hooks
Avoid multiple clicks using debounce…
Numbers inJavascript
02. Javascript Guide – Numbers
Introduction to Javascript
01. Javascript Guide – Introduction…
Nginx Load Balancer with docker…

Categories

  • android 3
  • Apollo Client 1
  • AWS 8
    • AppSync 5
    • EC2 1
    • EKS 1
    • Route53 1
    • S3 1
  • AWS Amplify 1
  • Chakra UI 1
  • Docker 1
  • Embedded 1
  • EmberJS 1
  • FCM 1
  • Godaddy 1
  • GraphQL 3
  • ios 1
  • Jasper 1
  • Java 10
    • Java 11 1
    • Java 14 1
  • JavaEE 2
  • JavaScript 39
    • Express.js 4
    • Javascript Guide 7
    • Node.js 3
    • react-native 4
    • React.js 17
    • Typescript 1
  • Kubernetes 1
  • machine learning 1
  • Maven 2
  • OCaml 3
  • PostgreSQL 1
  • Python 2
  • react-native 4
  • ReactJS 3
  • sass 1
  • Server 6
  • spark 1
  • Terraform 2
  • Ubuntu 4
  • Uncategorized 1
  • webpack 2

Recent Comments

  • binance register on Chakra UI Animations
  • binance account on SSL Websocket proxy with Nginx for Graphql Subscriptions
  • Binance Pag-signup on How to fetch data with useEffect

Meta

  • Log in
  • Entries feed
  • Comments feed
  • WordPress.org

Archives

  • October 2022 3
  • September 2022 7
  • May 2022 1
  • December 2021 1
  • August 2021 1
  • July 2021 6
  • June 2021 3
  • February 2021 1
  • July 2020 1
  • December 2019 5
  • November 2019 6
  • October 2019 3
  • August 2019 1
  • March 2019 1
  • February 2019 1
  • January 2019 2
  • December 2018 1
  • September 2018 2
  • August 2018 1
  • June 2018 1
  • February 2018 1
  • November 2017 2
  • October 2017 5
  • September 2017 1
  • June 2017 1
  • May 2017 10
Sandny Blog space
Theme by Colorlib Powered by WordPress