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' });
});
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), and401
(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.
- The URI end points should be resources (i.e. nouns) and human readable such as
- 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);
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.
});
-
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
andawait
. -
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
}
});
- 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())
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, to mysql, 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');
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');
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);
});
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 is
passport.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.js
to 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 or Apigee 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 WebSockets instead 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
Moesif is the most advanced API Analytics platform. Thousands of platform companies leverage Moesif for debugging, monitoring and discovering insights.
Learn More