Adapting Express.js
Now, it actually is possible to recreate some of this behavior with Express.js using a set of middleware.
The express-async-handler
npm module provides a wrapper function that can interpose and allow a async
controller function to interact nicely with the Express.js app.use
API. Unfortunately, this requires the developer to manually wrap each controller function:
const asyncHandler = require('express-async-handler')
app.post('/user', asyncHandler(async (req, res, next) => {
const bar = await foo.findAll();
res.send(bar);
}))
The response tuple unwrapping can also be handled by middleware. Such a middleware would need to run after the controller code has run and would replace the array with a representation Express.js is expecting.
The ability to promise the request body stream parsing can also be built in a generic manner:
app.use((req, res, next) => {
req.bodyToJson = requestBodyJson(req);
next();
});
function requestBodyJson(req) {
return new Promise((resolve, reject) => {
let body = '';
req.on('data', (data) => {
// This function is called as chunks of body are received
body += data;
});
req.on('end', () => {
// This function is called once the body has been fully received
let parsed;
try {
parsed = JSON.parse(body);
} catch (e) {
reject(e);
return;
}
resolve(parsed);
});
});
}
With the above code, we can then await the parsing using Express.js (and really any other situation where we’re given an instance of an HTTP Request
object):
// When using the Express.js middleware:
const parsed = await req.bodyToJson();
// Using the function generically:
const parsed = await requestBodyJson(req);
Using another framework
It is true that we can reproduce some of these desired patterns using Express.js, but there are frameworks that have been built from the ground up with support for promises and the async/await paradigm. Let’s see what our example controller might look like when written using different web server frameworks.
Fastify
Fastify, as its name implies, was built with the intention of being a very fast Node.js web framework. Despite its main goal of speed, it actually does a very nice job of achieving our ideal controller syntax.
This example is so terse that it almost feels like cheating:
const fastify = require('fastify');
const app = fastify();
app.post('/user', async (req, reply) => {
return {
error: false,
username: req.body.username
};
});
app.listen(3000).then(() => {
console.log('Server running at http://localhost:3000/');
});
Fastify not only supports functions for use as controller code, but it also automatically parses incoming requests into JSON if the header suggests the body is JSON. This is why the example code ends up being so tiny.
This also means that we can rely on Fastify to respond with a sane error when parsing fails. For example, when the client sends an invalid JSON to Fastify, the response will look something like this:
{
"statusCode": 400,
"error": "Bad Request",
"message": "Unexpected string in JSON at position 19"
}
Koa
Koa is a sort of spiritual successor to Express.js, having been written by some of the original Express.js authors. It does support functions out the door, but it doesn’t come with a router of its own. We can make use of koa-router
to provide routing.
Here’s what our example controller might look like with Koa:
const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();
router.post('/user', async (ctx) => {
try {
const parsed = await requestBodyJson(ctx.req);
ctx.body = {
error: false,
username: parsed.username
};
} catch (e) {
ctx.status = 400;
ctx.body = {
error: 'CANNOT_PARSE'
};
}
});
app.use(router.routes());
app.listen(3000);
This Koa example isn’t as succinct as the Fastify version. It doesn’t perform the automatic JSON parsing, but we’re able to reuse the requestBodyJson()
the method we created earlier. It also doesn’t use the returned/resolved value from our controller but instead works by consuming data attached to the ctx
argument.
Takeaways
When Node.js was still in its infancy, Express.js became the obvious choice for building web applications. Express.js had the goal of being a convenient web server that followed the callback paradigm. It achieved that goal, and the product is now essentially complete.
However, as the JavaScript ecosystem has matured, we’ve gained new language tools and syntax. Dozens if not hundreds of frameworks have arisen since then, many of which have embraced these new language features.
If you find yourself working on a new project written in Node.js that acts as a web server, I encourage you to consider newer contenders such as Koa and Fastify instead of defaulting to the familiar Express.js.
200’s only
Monitor failed and slow network requests in production
Deploying a Node-based web app or website is the easy part. Making sure your Node instance continues to serve resources to your app is where things get tougher. If you’re interested in ensuring requests to the backend or third-party services are successful, try LogRocket. 
LogRocket is like a DVR for web apps, recording literally everything that happens on your site. Instead of guessing why problems happen, you can aggregate and report on problematic network requests to quickly understand the root cause.
LogRocket instruments your app to record baseline performance timings such as page load time, time to the first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. Start monitoring for free.
Comments
Post a Comment