Express js API and REST API Organization: tips, examples and techniques

Express js API and REST API Organization: tips, examples and techniques

Express js is a Node.js web application framework which is excellent for Rest API development and is based on JavaScript. I won’t write the generic info on express in this article which you might find by simply hitting Express js API in the search tab. Instead, I would like to do the next 2 big things:

  • Share our company’s experience and personal best practices of proper Express js API organization when developing Express.js app.
  • Provide a clear strategy on how to set up Express js API correctly.

 

In this Express js API guide, I will try to unveil the answers to the following questions:

  • How to set up REST API and organize REST API structure? (with the example of using REST API)

 

  • How to process the related errors and exceptions?

 

 

  • How to organize Express js API (+example of using Express js API) and manage REST requests organization?

REST API structure. How to set up REST API correctly?

REST API development is the ‘description of routes’ by which client-devices or other services refer to your server. That’s why it's important to organize you REST API properly and make these ‘routes’ intuitive. Consider it as a map. Large REST API server performs lots of operations. The most common mistake among REST API developers is that they create a separate route for each operation and don’t record it properly. The result is an uncontrolled increase of routes which eventually causes timing and knowledge obstacles on the way to understand your API.

To avoid this issue and minimize your API routes number, try keeping up to the REST logic pattern, which has 4 requests types: GET, POST, UPDATE, DELETE.

What I want to say in this REST API tutorial, is to include these requests into each operation. For example, if you need to update any entity, use UPDATE requests if delete - DELETE requests. Another tip is to correlate each route to the business logic model.

Take a look at the table below with 5 routes to process single business logic model. Consider it as the example of using REST API.

RouteHTTP VerbDescription
/api/v1/models/GETGet list of models
/api/v1/models/:modelIdGETGet model item
/api/v1/models/POSTCrete model Item
/api/v1/models/:modelIdPUTUpdate model Item
/api/v1/models/:modelIdDELETERemove model item

The table shows us the routes variations and corresponding methods aimed to provide CRUD (create, read, update, delete) requests to process a certain model. If you keep to this strategy for each model processing and don’t create additional routes, as described in this REST API guide, your API will instantly get understandable and clear.

Parameters

What about additional parameters? We want to return sorted and filtered models list. There’s no need to create an additional route, like /api/v1/models/sorted/:param.

Using of query parameters should be enough, and after their implementation, the route will look like: /API/v1/models?sort=name.

There may be a lot more of such parameters. They're not necessary but using query parameters will help you perform several operations on a single route.

Although, such technique might seem tedious if you implement it for the client-side development, it's not. Below you’ll find some practical examples.

Code organization tips

In this Express js API guide, I want to share the most helpful coding tips when I'm on my way to set up Express js API.

 

  • Start each route as /api/v1/. Don’t exclude the future possibility of rewriting the current API by preserving the functionality of the old one for some time.

 

 

  • Separate models from controllers. Models are business logic objects, and they should be stored in the models folder. Controllers contain routes business logic and should refer to controllers folder.

 

 

  • Don’t place routes of the same model in one file. It's better to create a separate modelName folder for each entity and create a separate file for each GET, POST, DELETE operation. For instance, file controllers/notifications/index.js can look like this:

 

const fetchNotifications = require('./fetch-notifications');
const updateNotification = require('./update-notification');
const removeNotification = require('./remove-notification');
/**
* Provide Api for Investors (user with articles)
**/

module.exports = ({ config, db }) => {
api.get('/', authenticate, fetchNotifications({ config, db }));
api.put('/:notificationId',authenticate, updateNotification({ config, db
}));
api.delete('/:notificationId', authenticate, removeNotification({ config,
db }));
return api;
};

 

 

 

  • Keep your controllers documented.

 

 

  • Refactor/extract duplicated pieces of code into utils/helpers folders.

 

REST API documentation

Take care of proper documentation records as it’s a vital part of the REST API development process and Express js API organization. Since such API might be requested by other developers (front-end, mobile developers) or 3rd party services, clear guidelines on how to interact with your API is a must-have. To clarify everything on the spot, I would recommend doing the following:

 

  • Before creating any documentation, determine what is the purpose of this API and who will use it. For the projects with internal API, you can record the routes both in README files of the repository and in the controllers/model/index.js files. Find the documentation example below:

 

 

/**
 Provide Api for Model

Model list GET /api/v1/models
@header
Authorization: Bearer {token}
@optionalQueryParameters
param1 {String} - description
param2 {String} - description


Model create POST /api/v1/models
@header
Authorization: Bearer {token}
@params
param1 {string}
param2 {boolean}
**/

 

 

If it’s an API meant for external use, you should think of creating a complete, sorted and convenient in use reference/documentation. Here, take a look how Google Calendar API is managed. Another great example of using REST API.

 

  • Keep documentation reference for each of your routes. There’s no reason in route existence if nobody knows about it. If your API users don’t have enough information, they can decide that you simply don’t support such functionality. That’s why again, I put a high emphasis on keeping each route documented since it's aimed at making your REST API organization better

 

 

  • Record the route at the moment of route creation. Don’t ‘I’ll do it tomorrow’ since you can simply forget about it or its parameters.

 

REST API Error handling techniques

REST server has a large list of default HTTP Status Codes (default errors included), so you don’t have to figure out your own error types. The advice is, it's better to write your own events processor and use it everywhere than do it for each router. For this, here comes the helping hand of rest-API-errors package. But before implementing the package, you should write a general processor:

 

const { APIError, InternalServerError, Unauthorized } = require('rest-api-errors');
const { STATUS_CODES } = require('http');
const winston = require('winston');
module.exports = (err, req, res, next) => {
  let error = err instanceof APIError ? err : new InternalServerError();

if(process.env.NODE_ENV !== 'production') {
winston.log('error', '-----> Unknown server error...');
winston.log('error', err);
}
res
.status(error.status || 500)
.json({
code: error.code || 500,
message: error.message || STATUS_CODES[error.status],
});
};

 

 

And use it in Express

 

const errorHandler = require('./utils/error-handler');
const app = express();
app.use(errorHandler);

 

Now you can initiate error in any controller type

 

const { MethodNotAllowed } = require('rest-api-errors');

api.use('/account', (req, res, next) => {
....
if(!user) {
throw new Unauthorized(401, 'Unauthorized');
}
});


 

 

 

Permission checker

In this part of our REST API guide I'll touch upon permission levels with providing an example of using Express js API. There’s often a need to provide separate permission levels for different user groups. Managers, for instance, could have more rights than regular users. The following snippet represents how to write compact permission checker which looks like this:

 

 import { hasPermissionTo, actions } from '../'
 api.use('/account', (req, res, next) => {
     const user = getUser();
     if (!hasPermissionTo(actions.GET_USER, user)) {
        throw new MethodNotAllowed();
     }
});

 

Where to get a user object? It depends on the authentication type you’ve used.

 

Let’s first describe all the actions available (we can extend these in the future) utils/permission-checker/index.js

 

module.exports = {
  ALL_RIGHT: 'ALL_RIGHT',
  GET_USER: 'GET_USER',
};

 

Let’s apply these actions to a certain user group utils/permission-checker/index.js

 

const actions = require('./constants');
module.exports = {
  owner: [
    actions.GET_USER,
  ],
  user: [],
  admin: [
    actions.ALL_RIGHT,
  ],
};

 

Then we create hasPermissionTo method utils/permission-checker/index.js

 

const _ = require('lodash');
const permissions = require('./permissions');
const actions = require('./constants');

const hasPermissionTo = (action, user) => {
const isRoleHasPermission = action => role => _.includes(permissions[role], action);
// get map user rolse into true false array
const userPermissions = user.roles.map(isRoleHasPermission(action));


if (!(userPermissions && _.includes(userPermissions, true))) {
return false
}


return true;
};


module.exports = {
hasPermissionTo,
actions,
};

 

 

In the current example, the user has roles field ['owner', 'user'] , which points to what role he/she has. When user gets owner role which has GET_USER permission, then hasPermissionTo returns true value. You can also create your own logic of hasPermissionTo method.

Client requests

You can organize client requests by different means and with the help of different libraries. Here’s my personal insight on how to organize the process properly.

 

  • Use the libraries already developed for it, like axios

 

 

  • Keep routes and codes at a distance. Don’t even think (seriously) of hardcoding endpoint path directly in code that performs request:

 

 

axios.get('api/v1/users', {
    params: {}
  })

 

Why do I insist on this? When you change something in the API, it will cause troubles looking for the routes in code. According to our own experience, it’s better to write storages for each route and keep them in one place, like this:

 

export default {
  USERS: '/api/v1/amazonS3/',
};

 

Now you can use this constant for 4 requests simultaneously.

import ApiAdressses = from './utils/api/urls.js';
axios.get(ApiAdressses.USERS);
axios.post(ApiAdressses.USERS);
axios.put(ApiAdressses.USERS);
axios.delete(ApiAdressses.USERS);

 

 

  • Write an additional functionality for API interaction

     

Usually, most of the requests contain authorization headers apart from data or query parameters. Thus writing it all in your code every time is not the best idea.

 

axios({
      url: `ApiAdressses.USERS?limit=${limit};soty${sort}`,
      method: 'GET',
      headers: {
        'Authorization': 'Bearer token'
     }
    });

// or


axios({
url: ApiAdressses.USERS,
method: 'POST',
headers: {
'Authorization': 'Bearer token'
'Content-Type' : 'multipart/form-data'
}
});

 

 

The better strategy to apply is to write the 4 methods which will look like the following example:

 

import { get, post, put, remove, query, APIAddresses } from './utils/api';

const response = await get(APIAddresses.USERS${query({ limit, sort })});
const response = await post(APIAddresses.USERS, formData);
const response = await put(APIAddresses.USERS/${userId}, formData);
const response = await remove(APIAddresses.USERS/${userId});

 

 

Methods get, post, put, remove of makeRequest method wrapper are created to provide the comfort of use. Query method creates query string from the entry parameters.

 

import axios from 'axios';
import APIAddresses from './urls';

const makeRequest = async (type, url, data) => {
try {
const response = await axios({
url,
data,
method: type,
headers:
'Authorization': getTokenHeaderValue(),
'Content-Type' : 'multipart/form-data'
}
});
return response;
} catch (error) {
if(process.env.NODE_ENV !== 'production') {
console.log(Error: [${type}] - ${url});
console.log(error);
}
return error;
}
};


const get = url => makeRequest('get', url, null);
const post = (url, data) => makeRequest('post', url, data);
const put = (url, data) => makeRequest('put', url, data);
const remove = url => makeRequest('delete', url, null);
const query = data => ?${Object.keys(data).map(key => ${key}=${data[key]}).join('&')};


export { APIAddresses, get, post, put, remove, query, postFile };


 

 

 

APIAddresses is imported and exported to make importing from a single source easier.

Following the current logic organization strategy, given in this example of using Express js API, you’ll get rid of unnecessary code snippets and improve the API concept for other developers or services overall. Take into account that if you change or update the API version, changing routes in one file will be enough.

Wrapping up

Every project is different in its idea and needs. I understand that it’s not always possible to keep up to a single development pattern, but any kind of personal experience can give light to a lot of things.

Let’s shortly summarize the main ideas of the entire REST API tutorial:

  • Keep to REST logic

 

  • Organize Express js API (routes, events processing and permissions)

 

 

  • Keep documentation on your API

 

 

  • Organize your requests to avoid code duplications.

 

The correct organization techniques of your REST API will definitely reduce the development time and improve the overall quality of your project. Hope this Express js API tutorial will help you.

Let us know if you've got something to add.