DEV Community

Xing Wang
Xing Wang

Posted on • Originally published at moesif.com on

Choosing the Libraries and Frameworks for REST APIs in the NodeJS Stack

There are many tutorials for building RESTful APIs on NodeJs, but often those tutorials already chose the libraries or the framework. This guide is intended to provide comparisons on various libraries And design decisions.

Introduction

If you boil down RESTful APIs to requests over HTTPs and communicate via JSON (mostly),creating an API in NodeJS can be shockingly simple.

var express = require('express');
var app = express();

app.get('/greeting', function (req, res) {
  res.json({ hello: 'world' });
});
Enter fullscreen mode Exit fullscreen mode

We need to understand both the design principals and technology at each layer of the stack that helps us build the APIs and then we can go back and choose the sets of tools and libraries that helps us.

Overview of REST design principles

Let’s review what makes a good RESTful API design. Some core principals that you should follow:

  • Semantically Meaningful:
    • The URI end points should be resources (i.e. nouns) and human readable such as /items or /users. A function or operation is NOT a resource.
    • HTTP verbs (GET, POST, PUT, DELETE) represents the actions that a client can take on a resource.
    • The HTTP response codes (e.g. 201(created), 404 (not found), and 401 (not authorized)) represents what happened.
    • Relationships can be represented as sub-resources. Again, it makes things readable. For example, /authors/{id}/posts endpoint will represent posts for the specific author.
  • Stateless: The server does not need to hold state on behalf of the client. This makes it easy to scale REST APIs since a new request can hit any VM behind a load balancer. Maintaining temporary cursors or storing temporary files between requests is not stateless.
  • Handle Repeated Calls Gracefully:
    • Cacheability: GET and HEAD methods are usually cached. Your API should consider this when thinking of mutability.
    • Idempotence: For actions that that alters state of one resource, ‘PUT’ & DELETE’, it produces the same result for repeated calls with same data.
    • Safe: GET, HEAD, OPTIONS and TRACE, for readonly, and do not alter the state.

Of course, there are many opinionated recommendations on design, such as best ways to name the resources (camelCase vs. snake_case vs. spinal-case, plural vs singular), best way to setup the JSON schema names (Envelope vs no Envelope), compliant to HATEOAS, how to best handle filter & pagination etc. Do read them and understand them before making your choices, and these design decision should come before you make any technology decisions.

Main Layers of Tech Stack for Setting up a Restful API.

  • HTTP server and Router.
  • Data
  • Security
  • Proxy

HTTP server and router

NodeJS natively comes with a Http server.

var http = require('http');
http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.write(req.url);
    res.end();
}).listen(8080);
Enter fullscreen mode Exit fullscreen mode

This default server doesn’t handle routing which is what we use to define our endpoints. We want to be able to route GET /users to one function, and GET /items to a different function. Routes can get complex with many combinations of HTTP verbs, paths, and parameters, but luckily we have many frameworks that can handle routing in addition to other key middleware for building REST APIs.

  • express is by far the most popular framework for building REST APIs. It’s also the first framework Moesif published and our most popular integration. Express believes in composition and code over configuration. Your routes are coded directly next to where the business logic is. There is no centralized “routes.conf” or similar file. As old as the framework is, it’s kept lean by relying on middleware that’s optional. Thus, if you’re building a REST API, you don’t get extra bloat like HTML templating engines being enabled and cookie parsers. An example express route is below.
router.get('/:id', function (req, res) {
  // ... where id is parameterized.
});
Enter fullscreen mode Exit fullscreen mode
  • Koa Koa is listed even though it does not support routing. However, it is an alternative to Express for certain cases. , but people always list it as an alternative to Express and you can add Koa Router separately. Koa was originally created to get around callback hell, which can happen easily with express. Koa started with co to handle the async calls before ES2016 supporting async and await.

  • hapi is created by WalmartLabs. It follows the philosophy that configuration is better than code. It offers higher level of abstraction from node’s HTTP module than others.

The code looks like this:

server.route({
    method: 'GET',
    path: '/{name}',
    handler: function (request, reply) {
          // ... where name is parameterized
    }
});
Enter fullscreen mode Exit fullscreen mode
  • restify is specifically designed for RESTful API, so it removes some of the features from express such as HTML templating and views, but adds other built in things necessary for APIs such as rate limiting and SPDY support. Restify’s syntax is very similar to express.

We can always add middleware to add functionality and features to each of these frameworks. View an in-depth article on middleware here.

JSON de|serialization

Javascript natively supports JSON.parse(my_json_string) or JSON.stringify(my_javascript_object). However, life would be easier if this was automatic and behind the scenes.

  • If you are using Express, you can use the default body-parser middleware. It supports many types of text and binary data, but of course JSON, the4 most used format format RESTful APIs.
var express = require('express')
var bodyParser = require('body-parser')
var app = express()
// parse application/json
app.use(bodyParser.json())
Enter fullscreen mode Exit fullscreen mode

Databases

Once you pick a database, the library that you chose will primarily be driven by what’s compatible with that database. The Node.JS ecosystem includes drivers for many different database, from, mongojs, tomysql, and PostgreSQL.

While there are drivers in NodeJS for every database, you may want to consider using an ORM (Object Relational Mapping) regardless if SQL or No-SQL technology. ORM’s have long been used in Enterprise Java and C# worlds, and Node.js is no different even with native JSON support in Node.js and MongoDb. AN ORM allows you to model your database schema in code as objects, and then the ORM manages the retrieval/updating of data from the actual database and mapping them to domain objects in your code. For databases that require schema migration, ORMs can facilitate that process.

Some common ORMs in the Node.js ecosystem:

  • Mongoose: It is essentially ORM for MongoDB. Given the popularity of MEAN stack, this is very popular.
  • Sequelizejs: It is promised based, works for PostgreSQL, MySQL, SQLite and MSSQL.
  • orm: Creatively named.
  • bookshelf: Built on top of Knex.js, a query builder.
  • waterline: Waterline uses the concept of an adapter to translate a predefined set of methods into a query. It also supports a wide range of databases both SQL and No-SQL.

Security

We recommend reviewing steps to building authentication and authorization for RESTful APIs, to weigh the various options in your authentication architectire such as comparing JWT’s (JSON Web Tokens) vs. opaque tokens and comparing cookies vs. HTTP headers.

Resources for JWT Tokens

JWT Tokens are actually a full JSON Object that has been base64 encoded and then signed with either a symmetric shared key or using a public/private key pair. If you decided on JWT as your authentication token, there are a few libraries that can help you.

jsonwebtoken is a general utility library for signing JWTs.

To generate a token for your user:

var jwt = require('jsonwebtoken');
jwt.sign({
  exp: Math.floor(Date.now() / 1000) + (60 * 60),
  data: 'foobar'
}, 'secret');
Enter fullscreen mode Exit fullscreen mode

The token can contain any JSON such as the user_id and allowed scopes or roles.

jwt.sign({
  exp: Math.floor(Date.now() / 1000) + (60 * 60),
  admin: true
}, 'secret');
Enter fullscreen mode Exit fullscreen mode

Since the token is signed using your secret, you can guarantee the token has not been tampered with or modified by a malicious party.

Even though you can use the jsonwebtoken library to decode and verify the JWT you receive also, there is another library that makes it more easily integrated with the HTTP server and router.

express-jwt is an open-source library provided by Auth0 which can work with any standard router/server that follows the express like middleware convention. This ensures your token is already checked, and base64 decoded for your business logic to consume.

Using it is pretty simple:

var jwtMiddleware = require('express-jwt');

app.get('/protected',
  jwtMiddleware({secret: 'your secret'}),
  function(req, res) {
    if (!req.user.admin) return res.sendStatus(401);
    res.sendStatus(200);
  });
Enter fullscreen mode Exit fullscreen mode

You initialize the middleware with your verification key, which enables the middleware to check if the token was signed by your secret. The base64 decoded fields are populated in req.user.

Note, please audit your code before putting any code into production use. These examples are very simple and require much more work before they can be locked down and production ready.

Opaque Tokens

If you chose to use opaque token strategy, the information for authorization (i.e. what the user is allowed to access is not encoded in the token), so it would need an lookup in a database such as Redis.

So here you need two pieces of technology. A middleware to handle the logic, and a O(1) hash based database for lookup permission and other data. The O(1) is very important, as you want to invoke it for every API call. redis for example would be good option.

As for the middleware to use, the most popular one ispassport.js, as it supports many strategies (including JWT). However, you’ll most likely use the_bearer_ strategy for REST APIs. The authorization strategy here would be to use passport.jsto determine who the user is (e.g. userId), then look up in Redis for the permission you granted that userId, before deciding if an API can be invoked.

Rate limiting

Rate limiting is important to prevent DDoS attacks or ambitious free tier users. One language agnostic way is to use an API gateways such as Tyk orApigee to handle your API management needs. There are also middleware that takes care of this for you, such as express-rate-limit

Reverse Proxy

Many APIs we create will be placed behind a reverse proxy. A reverse proxy can handle high level routing to many services and versions of those services. A reverse proxy can also handle security, logging, and caching reasons.

Nginx and HaProxy are two very popular and high performing HTTP proxies, but requires a lot of work in configuration. The Node.js ecosystem, has a very simple, yet decent performing proxy called node-http-proxy which can be run directly in your Node.js app.

Additional options

Generating APIs automatically

Even with routing frameworks, it’s still a lot of manual work to write all the route callbacks. If your application requires mostly CRUD (Create, Read, Update, Delete) operations without a lot of custom logic, you can look into higher level boilerplate frameworks that can sit in front your database and generate endpoints based on the data model directly.

  • loopback is supported by StrongLoop, an IBM subsidiary. It’s positioned as a full fledged framework and allows you to create quickly create APIs primarily driven by your database. There are many tools that can plug into loopback with minimal effort such as: Swagger, ORM/ODM (Juggle), and access level controls. It adopts the philosophy of convention over configuration and generates routes based on your schema. Keep in mind, if you start coding stuff different from convention, the framework can be constraining.

  • Nodal is an opinionated framework that makes a lot of decision for you, and let’s you get started quickly. The generated routes are based on a controller class you define.

  • Deployd assumes that every API you build has collections of data objects which needs to support CRUD operations. It also provides an web UI interface for creating the APIs.

  • Generators and boilerplates: there are quite few generators based on Yeoman that setup your APIs automatically.

Web sockets

There are frameworks that let you serve your APIs using WebSocketsinstead of HTTP. This can be useful for certain real time apps like chat applications and games. Keep in mind, web sockets is still harder to scale that a typical HTTP REST API and has less tooling. In some ways, web sockets is the Anti-REST.

Conclusion

NodeJS ecosystem is probably one of the more flexible ecosystems and becomming the largest for building APIs driven by popular frameworks like Express and React. and thus allow more choices than most other ecosystem such as Ruby or Python. Node.js is the most popular for building REST APIs according to our usage data

Top comments (0)