How do Express and hapi compare to each other? - node.js

From web application design and development point of view, how do Express and Hapi compare to each other? For basic examples they seem similar, however I'm interested to learn more about key differences in overall application structure.
For example, as far as I have learned, Hapi uses a different routing mechanism which does not take registration order into account, can do faster lookups, but is limited comparing to Express. Are there other important differences?
There is also an article about choosing Hapi (over Express) for developing the new npmjs.com website, this article states that "Hapi’s plugin system means that we can isolate different facets and services of the application in ways that would allow for microservices in the future. Express, on the other hand, requires a bit more configuration to get the same functionality", what does it exactly mean?

This is a big question and requires a long answer to be complete, so I'll just address a subset of the most important differences. Apologies that it's still a lengthy answer.
How are they similar?
You're absolutely right when you say:
For basic examples they seem similar
Both frameworks are solving the same basic problem: Providing a convenient API for building HTTP servers in node. That is to say, more convenient than using the lower-level native http module alone. The http module can do everything we want but it's tedious to write applications with.
To achieve this, they both use concepts that have been around in high level web frameworks for a long time: routing, handlers, plugins, authentication modules. They might not have always had the same names but they're roughly equivalent.
Most of the basic examples look something like this:
Create a route
Run a function when the route is requested, preparing the response
Respond to the request
Express:
app.get('/', function (req, res) {
getSomeValue(function (obj) {
res.json({an: 'object'});
});
});
hapi:
server.route({
method: 'GET',
path: '/',
handler: function (request, reply) {
getSomeValue(function (obj) {
reply(obj);
});
}
});
The difference is not exactly groundbreaking here right? So why choose one over the other?
How are they different?
The simple answer is hapi is a lot more and it does a lot more out-of-the-box. That might not be clear when you just look at the simple example from above. In fact, this is intentional. The simple cases are kept simple. So let's examine some of the big differences:
Philosophy
Express is intended to be very minimal. By giving you a small API with just a thin dusting on top of http, you're still very much on your own in terms of adding additional functionality. If you want to read the body of an incoming request (quite a common task), you need to install a separate module. If you're expecting various content-types to be sent to that route, you also need to check the Content-type header to check which it is and parse it accordingly (form-data vs JSON vs multi-part for example), often using separate modules.
hapi has a rich feature set, often exposed through configuration options, rather than requiring code to be written. For instance, if we want to make sure a request body (payload) is fully read into memory and appropriately parsed (automatically based on content-type) before the handler is ran, it's just a simple option:
server.route({
config: {
payload: {
output: 'data',
parse: true
}
},
method: 'GET',
path: '/',
handler: function (request, reply) {
reply(request.payload);
}
});
Features
You only need to compare the API documentation on both projects to see that hapi offers a bigger feature set.
hapi includes some of the following features built-in that Express does not (as far as I know):
Input and response validation (through Joi)
Server-side caching with multiple storage options (mongo, S3, redis, riak), that can be enabled with a few lines of configuration
Cookie-parsing
Sessions
File uploading/multipart parsing
CORS support
Logging
Extensibility & modularity
hapi and Express go about extensibility in quite a different way. With Express, you have middleware functions. Middleware functions are kind of like filters that you stack up and all requests run through them before hitting your handler.
hapi has the request lifecycle and offers extension points, which are comparable to middleware functions but exist a several defined points in the request lifecycle.
One of the reasons that Walmart built hapi and stopped using Express was a frustration with how difficult it was to split a Express app into separate parts, and have different team members work safely on their chunk. For this reason they created the plugin system in hapi.
A plugin is like a sub-application, you can do everything you can in a hapi app, add routes, extensions points etc. In a plugin you can be sure that you're not breaking another part of the application, because the order of registrations for routes doesn't matter and you can't create conflicting routes. You can then combine this plugins into a server and deploy it.
Ecosystem
Because Express gives you so little out of the box, you need to look outside when you need to add anything to your project. A lot of the times when working with hapi, the feature that you need is either built-in or there's a module created by the core team.
Minimal sounds great. But if you're building a serious production app, the chances are you're going to need all of this stuff eventually.
Security
hapi was designed by the team at Walmart to run Black Friday traffic so security and stability have always been a top concern. For this reason the framework does a lot of things extra such as limiting incoming payload size to prevent exhausting your process memory. It also has options for things like max event loop delay, max RSS memory used and max size of the v8 heap, beyond which your server will respond with a 503 timeout rather than just crashing.
Summary
Evaluate them both yourself. Think about your needs and which of the two addresses your biggest concerns. Have a dip in the two communities (IRC, Gitter, Github), see which you prefer. Don't just take my word. And happy hacking!
DISCLAIMER: I am biased as the author of a book on hapi and the above is largely my personal opinion.

My organization is going with Hapi. This is why we like it.
Hapi is:
Backed by major corps. This means the community support will be strong, and there for you throughout future releases. It's easy to find passionate Hapi people, and there are good tutorials out there (though not as numerous and sprawling as ExpressJs tutorials). As of this post date npm and Walmart use Hapi.
It can facilitate the work of distributed teams working on various parts of the backend services without having to have comprehensive knowledge of the rest of the API surface (Hapi's plugins architecture is the epitome of this quality).
Let the framework do what a framework is supposed to: configure things. After that the framework should be invisible and allow devs to focus their real creative energy on building out business logic. After using Hapi for a year, I definitely feel Hapi accomplishes this. I... feel happy!
If you want to hear directly from Eran Hammer (Hapi's lead)
Over the past four years hapi grew to be the framework of choice for many projects, big or small. What makes hapi unique is its ability to scale to large deployments and large teams. As a project grows, so does its complexity – engineering complexity and process complexity. hapi’s architecture and philosophy handles the increased complexity without the need to constantly refactor the code [read more]
Getting started with Hapi won't be as easy as ExpressJs because Hapi doesn't have the same "star power"... but once you feel comfortable you'll get A LOT of mileage. Took me about ~2 months as a new hacker who irresponsibly used ExpressJs for a few years. If you're a seasoned backend developer you'll know how to read the docs, and you probably won't even notice this.
Areas the Hapi documentation can improve on:
how to authenticate users and create sessions
handling Cross-Origin-Requests (CORS)
uploading files (multipart, chunked)
I think authentication would be the most challenging part of it because you have to decide on what kinda auth strategy to use (Basic Authentication, Cookies, JWT Tokens, OAuth). Though it's technically not Hapi's problem that the sessions/authentication landscape is so fragmented... but I do wish that they provided some hand-holding for this. It would greatly increase developer happiness.
The remaining two aren't actually that difficult, the docs could just be written slightly better.

Quick Facts about Hapi Or Why Hapi JS ?
Hapi is configuration-centric
It has authentication and authorization built into the framework
It was released in a battle-tested atmosphere and has really proven its worth
All the modules have 100% test coverage
It registers the highest level of abstraction away from core HTTP
Easily compassable via the plugin architecture
Hapi is a better choice performance wise
Hapi uses a different routing mechanism, which can do faster lookups, and take registration order into account.
Nevertheless, it is quite limited when compared to Express. And thanks to the Hapi plugin system, it is possible
to isolate the different facets and services that would help the application in many ways in the future.
Usage
Hapi is the most preferred framework when compared to Express. Hapi is used mainly for large-scale enterprise applications.
A couple of reasons why developers do not choose Express when creating enterprise applications are:
Routes are harder to compose in Express
Middleware gets in the way most of the time; each time you are defining the routes, you have to write as many numbers of codes.
Hapi would be the best choice for a developer looking to build RESTful API. Hapi has micro-service architecture and it is also possible to transfer the control from one handler to another based on certain parameters. With the Hapi plugin, you can enjoy a
greater level of abstraction around HTTP because you can divvy up the business logic into pieces easily manageable.
Another huge advantage with Hapi is that it provides detailed error messages when you misconfigure. Hapi also lets you configure your file upload size by default. If the maximum upload size is limited, you can send an error message to the user conveying that the file size is too large. This would protect your server from crashing because the file uploads will no longer try to buffer a whole file.
Whatever you can achieve using express can also be easily achieved using hapi.js.
Hapi.js is very stylish and organizes the code very well. If you see how it does routing and puts the core logic in controllers
you will defiantly be going to love it.
Hapi.js officially provide several plugins exclusively for hapi.js ranges from token based auth to session management and many more,
which is an ad on. It doesn't mean the traditional npm can't be used, all of them are supported by hapi.js
If you code in hapi.js, a code would be very maintainable.

I've started using Hapi recently and I'm quite happy with it. My reasons are
Easier to test. For example:
server.inject allows you to run the app and get a response without it running and listening.
server.info gives the current uri, port etc.
server.settings accesses the configuration e.g. server.settings.cache gets current cache provider
when in doubt look at the /test folders for any part of the app or supported plugins to see suggestions on how to mock/test/stub etc.
my sense is that the architectural model of hapi allows you to trust but verify e.g. Are my plugins registered? How can I declare a module dependency?
It works out of the box e.g. file uploads, return streams from endpoints etc.
Essential plugins are maintained along with the core library. e.g template parsing, caching etc. The added benefit is the same coding standards are applied across the essential things.
Sane errors and error handling. Hapi validates config options and keeps an internal route table to prevent duplicate routes. This is quite useful while learning because errors are thrown early instead of unexpected behaviours which require debugging.

Just another point to add, Hapi has started supporting 'http2' calls from version 16 onwards (if I am not wrong ). However, express is yet to support 'http2' module directly till express 4. Although they have released the feature in the alpha version of express 5.

'use strict';
const Hapi = require('hapi');
const Basic = require('hapi-auth-basic');
const server = new Hapi.Server();
server.connection({
port: 2090,
host: 'localhost'
});
var vorpal = require('vorpal')();
const chalk = vorpal.chalk;
var fs = require("fs");
var utenti = [{
name: 'a',
pass: 'b'
},
{
name: 'c',
pass: 'd'
}
];
const users = {
john: {
username: 'john',
password: 'secret',
name: 'John Doe',
id: '2133d32a'
},
paul: {
username: 'paul',
password: 'password',
name: 'Paul Newman',
id: '2133d32b'
}
};
var messaggi = [{
destinazione: 'a',
sorgente: 'c',
messsaggio: 'ciao'
},
{
destinazione: 'a',
sorgente: 'c',
messsaggio: 'addio'
},
{
destinazione: 'c',
sorgente: 'a',
messsaggio: 'arrivederci'
}
];
var login = '';
var loggato = false;
vorpal
.command('login <name> <pass>')
.description('Effettua il login al sistema')
.action(function (args, callback) {
loggato = false;
utenti.forEach(element => {
if ((element.name == args.name) && (element.pass == args.pass)) {
loggato = true;
login = args.name;
console.log("Accesso effettuato");
}
});
if (!loggato)
console.log("Login e Password errati");
callback();
});
vorpal
.command('leggi')
.description('Leggi i messaggi ricevuti')
.action(function (args, callback) {
if (loggato) {
var estratti = messaggi.filter(function (element) {
return element.destinazione == login;
});
estratti.forEach(element => {
console.log("mittente : " + element.sorgente);
console.log(chalk.red(element.messsaggio));
});
} else {
console.log("Devi prima loggarti");
}
callback();
});
vorpal
.command('invia <dest> "<messaggio>"')
.description('Invia un messaggio ad un altro utente')
.action(function (args, callback) {
if (loggato) {
var trovato = utenti.find(function (element) {
return element.name == args.dest;
});
if (trovato != undefined) {
messaggi.push({
destinazione: args.dest,
sorgente: login,
messsaggio: args.messaggio
});
console.log(messaggi);
}
} else {
console.log("Devi prima loggarti");
}
callback();
});
vorpal
.command('crea <login> <pass>')
.description('Crea un nuovo utente')
.action(function (args, callback) {
var trovato = utenti.find(function (element) {
return element.name == args.login;
});
if (trovato == undefined) {
utenti.push({
name: args.login,
pass: args.pass
});
console.log(utenti);
}
callback();
});
vorpal
.command('file leggi utenti')
.description('Legge il file utenti')
.action(function (args, callback) {
var contents = fs.readFileSync("utenti.json");
utenti = JSON.parse(contents);
callback();
});
vorpal
.command('file scrivi utenti')
.description('Scrive il file utenti')
.action(function (args, callback) {
var jsontostring = JSON.stringify(utenti);
fs.writeFile('utenti.json', jsontostring, function (err) {
if (err) {
return console.error(err);
}
});
callback();
});
vorpal
.command('file leggi messaggi')
.description('Legge il file messaggi')
.action(function (args, callback) {
var contents = fs.readFileSync("messaggi.json");
messaggi = JSON.parse(contents);
callback();
});
vorpal
.command('file scrivi messaggi')
.description('Scrive il file messaggi')
.action(function (args, callback) {
var jsontostring = JSON.stringify(messaggi);
fs.writeFile('messaggi.json', jsontostring, function (err) {
if (err) {
return console.error(err);
}
});
callback();
});
// leggi file , scrivi file
vorpal
.delimiter(chalk.yellow('messaggi$'))
.show();
const validate = function (request, username, password, callback) {
loggato = false;
utenti.forEach(element => {
if ((element.name == username) && (element.pass == password)) {
loggato = true;
console.log("Accesso effettuato");
return callback(null, true, {
name: username
})
}
});
if (!loggato)
return callback(null, false);
};
server.register(Basic, function (err) {
if (err) {
throw err;
}
});
server.auth.strategy('simple', 'basic', {
validateFunc: validate
});
server.route({
method: 'GET',
path: '/',
config: {
auth: 'simple',
handler: function (request, reply) {
reply('hello, ' + request.auth.credentials.name);
}
}
});
//route scrivere
server.route({
method: 'POST',
path: '/invia',
config: {
auth: 'simple',
handler: function (request, reply) {
//console.log("Received POST from " + request.payload.name + "; id=" + (request.payload.id || 'anon'));
var payload = encodeURIComponent(request.payload)
console.log(request.payload);
console.log(request.payload.dest);
console.log(request.payload.messaggio);
messaggi.push({
destinazione: request.payload.dest,
sorgente: request.auth.credentials.name,
messsaggio: request.payload.messaggio
});
var jsontostring = JSON.stringify(messaggi);
fs.writeFile('messaggi.json', jsontostring, function (err) {
if (err) {
return console.error(err);
}
});
console.log(messaggi);
reply(messaggi[messaggi.length - 1]);
}
}
});
//route leggere (json)
server.route({
method: 'GET',
path: '/messaggi',
config: {
auth: 'simple',
handler: function (request, reply) {
messaggi = fs.readFileSync("messaggi.json");
var estratti = messaggi.filter(function (element) {
return element.destinazione == request.auth.credentials.name;
});
var s = [];
console.log(request.auth.credentials.name);
console.log(estratti.length);
estratti.forEach(element => {
s.push(element);
//fare l'array con stringify
//s+="mittente : "+element.sorgente+": "+element.messsaggio+"\n";
});
var a = JSON.stringify(s);
console.log(a);
console.log(s);
reply(a);
}
}
});
server.start(function () {
console.log('Hapi is listening to ' + server.info.uri);
});
function EseguiSql(connection, sql, reply) {
var rows = [];
request = new Request(sql, function (err, rowCount) {
if (err) {
console.log(err);
} else {
console.log(rowCount + ' rows');
console.log("Invio Reply")
reply(rows);
}
});
request.on('row', function (columns) {
var row = {};
columns.forEach(function (column) {
row[column.metadata.colName] = column.value;
});
rows.push(row);
});
connection.execSql(request);
}
server.route({
method: 'POST',
path: '/query',
handler: function (request, reply) {
// Qui dovrebbe cercare i dati nel body e rispondere con la query eseguita
var connection = new Connection(config);
// Attempt to connect and execute queries if connection goes through
connection.on('connect', function (err) {
if (err) {
console.log(err);
} else {
console.log('Connected');
console.log(request.payload.sql);
EseguiSql(connection, request.payload.sql, reply);
}
});
}
});
server.connection({
host: process.env.HOST || 'localhost',
port: process.env.PORT || 8080
});
var config = {
userName: process.env.DB_USER,
password: process.env.DB_PASSWORD,
server: process.env.DB_SERVER,
options: {
database: process.env.DB_NAME,
encrypt: true
}
}

Related

Nodejs proxy request coalescing

I'm running into an issue with my http-proxy-middleware stuff. I'm using it to proxy requests to another service which i.e. might resize images et al.
The problem is that multiple clients might call the method multiple times and thus create a stampede on the original service. I'm now looking into (what some services call request coalescing i.e. varnish) a solution that would call the service once, wait for the response and 'queue' the incoming requests with the same signature until the first is done, and return them all in a single go... This is different from 'caching' results due to the fact that I want to prevent calling the backend multiple times simultaneously and not necessarily cache the results.
I'm trying to find if something like that might be called differently or am i missing something that others have already solved someway... but i can't find anything...
As the use case seems pretty 'basic' for a reverse-proxy type setup, I would have expected alot of hits on my searches but since the problemspace is pretty generic i'm not getting anything...
Thanks!
A colleague of mine has helped my hack my own answer. It's currently used as a (express) middleware for specific GET-endpoints and basically hashes the request into a map, starts a new separate request. Concurrent incoming requests are hashed and checked and walked on the separate request callback and thus reused. This also means that if the first response is particularly slow, all coalesced requests are too
This seemed easier than to hack it into the http-proxy-middleware, but oh well, this got the job done :)
const axios = require('axios');
const responses = {};
module.exports = (req, res) => {
const queryHash = `${req.path}/${JSON.stringify(req.query)}`;
if (responses[queryHash]) {
console.log('re-using request', queryHash);
responses[queryHash].push(res);
return;
}
console.log('new request', queryHash);
const axiosConfig = {
method: req.method,
url: `[the original backend url]${req.path}`,
params: req.query,
headers: {}
};
if (req.headers.cookie) {
axiosConfig.headers.Cookie = req.headers.cookie;
}
responses[queryHash] = [res];
axios.request(axiosConfig).then((axiosRes) => {
responses[queryHash].forEach((coalescingRequest) => {
coalescingRequest.json(axiosRes.data);
});
responses[queryHash] = undefined;
}).catch((err) => {
responses[queryHash].forEach((coalescingRequest) => {
coalescingRequest.status(500).json(false);
});
responses[queryHash] = undefined;
});
};

Azure mobile apps CRUD operations on SQL table (node.js backend)

This is my first post here so please don't get mad if my formatting is a bit off ;-)
I'm trying to develop a backend solution using Azure mobile apps and node.js for server side scripts. It is a steep curve as I am new to javaScript and node.js coming from the embedded world. What I have made is a custom API that can add users to a MSSQL table, which is working fine using the tables object. However, I also need to be able to delete users from the same table. My code for adding a user is:
var userTable = req.azureMobile.tables('MyfUserInfo');
item.id = uuid.v4();
userTable.insert(item).then( function (){
console.log("inserted data");
res.status(200).send(item);
});
It works. The Azure node.js documentation is really not in good shape and I keep searching for good example on how to do simple things. Pretty annoying and time consuming.
The SDK documentation on delete operations says it works the same way as read, but that is not true. Or I am dumb as a wet door. My code for deleting looks like this - it results in exception
query = queries.create('MyfUserInfo')
.where({ id: results[i].id });
userTable.delete(query).then( function(delet){
console.log("deleted id ", delet);
});
I have also tried this and no success either
userTable.where({ id: item.id }).read()
.then( function(results) {
if (results.length > 0)
{
for (var i = 0; i < results.length; i++)
{
userTable.delete(results[i].id);
});
}
}
Can somebody please point me in the right direction on the correct syntax for this and explain why it has to be so difficult doing basic stuff here ;-) It seems like there are many ways of doing the exact same thing, which really confuses me.
Thanks alot
Martin
You could issue SQL in your api
var api = {
get: (request, response, next) => {
var query = {
sql: 'UPDATE TodoItem SET complete=#completed',
parameters: [
{ name: 'completed', value: request.params.completed }
]
};
request.azureMobile.data.execute(query)
.then(function (results) {
response.json(results);
});
}};
module.exports = api;
That is from their sample on GitHub
Here is the full list of samples to take a look at
Why are you doing a custom API for a table? Just define the table within the tables directory and add any custom authorization / authentication.

MEAN stack: Wondering api.js and crud.js

I'm studying MEAN stack these day, So I make some sample apps following guidance. I made up "Bookshelf" application just few hours ago, this is provided by google cloud service, so I should delve into sample code to understand how it works.
Whole source code : https://github.com/GoogleCloudPlatform/nodejs-getting-started/tree/master/2-structured-data
Sample application : http://mymongo-1165.appspot.com/books
books/api.js
router.get('/', function list(req, res) {
model.list(10, req.query.pageToken,
function(err, entities, cursor) {
if (err) { return handleRpcError(err, res); }
res.json({
items: entities,
nextPageToken: cursor
});
});
});
books/curd.js
router.get('/', function list(req, res) {
model.list(10, req.query.pageToken,
function(err, entities, cursor) {
if (err) { return handleRpcError(err, res); }
res.render('books/list.jade', {
books: entities,
nextPageToken: cursor
});
}
);
});
these 2 codes are similar, but I don't know why these similar codes comes up. I think crud.js enough, but why api.js comes up. Could you explain how these 2 codes work?
In this sample application, there are two interface:
graphic user interface (GUI) - curd.js handles generating HTML that is rendered later in the browser (in our case jade tempting language is involved)
application programming interface (API) - api.js provides the way to interact with application programmatically, without browser (ex: create new record in database, or query some data by making specific call to particular route)
For deeper understanding I would suggest learning more about express.js, that will give better idea what those outputs are.
P.S. Welcome to MEAN world :)

Reasonable design of using socket.io for RPC

I am building a web app that uses socket.io to trigger remote procedure calls that passes sessions specific data back go the client. As my app is getting bigger and getting more users, I wanted to check to see if my design is reasonable.
The server that receives websocket communications and triggers RPCs looks something like this:
s.on('run', function(input) {
client.invoke(input.method, input.params, s.id, function(error, res, more) {
s.emit('output', {
method: input.method,
error: error,
response: res,
more: more,
id: s.id
});
});
});
However, this means that the client has to first emit the method invocation, and then listen to all method returns and pluck out its correct return value:
socket.on('output', function(res) {
if (res.id === socket.sessionid) {
if (!res.error) {
if(res.method === 'myMethod') {
var myResponse = res.response;
// Do more stuff with my response
});
}
}
});
It is starting to seem like a messy design as I add more and more functions... is there a better way to do this?
The traditional AJAX way of attaching a callback to each function would be a lot nicer, but I want to take advantage of the benefits of websockets (e.g. less overhead for rapid communications).

How to identify request (by ID) through middleware chain in Express.

I am developping a RESTful server in node.js, using Express as framework, and Winston, for the moment, as logger module.
This server will handle a big amount of simultaneous request, and it would be very useful to me to be able to track the log entries for each specific request, using something like a 'request ID'. The straight solution is just to add this ID as another piece of logging information each time I want to make a log entry, but it will mean to pass the 'request ID' to each method used by the server.
I would like to know if there is any node.js/javascript module or technique that would allow me to do this in an easier way, without carrying around the request ID for each specific request.
If you auto-increment, your later log analytics won't be able to uniquely identify requests, because different instances will generate colliding IDs, and restarting the app will automatically cause ID collisions.
Here's another possible solution.
Install cuid:
npm install --save cuid
Then in your main app file:
var cuid = require('cuid');
var requestId = function requestId(req, res, next) {
req.requestId = cuid();
next();
};
// Then, at the top of your middleware:
app.use(requestId);
Now you'll get a friendly request ID that is unlikely to collide, and you'll be able to uniquely identify your requests for your log analytics and debugging, even across multiple instances, and server restarts.
You can use req object that does comes with every request in express.
So the first route you would do in your application would be:
var logIdIterator = 0;
app.all('*', function(req, res, next) {
req.log = {
id: ++logIdIterator
}
return next();
});
And then anywhere within express, you can access that id in req object: req.log.id;
You will still need to pass some data into functions that do want to create some logs. In fact you might have logging function within req.log object, so that way it will be guaranteed that logging will happen only when there is access to req.log object.
I was struggling search for a solution for this problem.
The thing I didn't like it about solutions suggested here was that they imply to share the req object among all the functions along the project.
I found out a solution mixing your approach (creating an uuid per request) and with a library (continuation-local-storage) that allows sharing namespaces among modules.
You can find the explanation in this other answer: https://stackoverflow.com/a/47261545/5710581
If you want more info, I wrote down all these ideas and all the code in a post, in order to explain everything in one place:
Express.js: Logging info with global unique request ID – Node.js
You shouldn't be using Global Variables.
What I like to do is to populate a META object before each request.
I use a UUID generator (https://github.com/kelektiv/node-uuid) to ID a request
Here's an example
app.all('*', function(req, res, next) {
req.meta = {
ip: req.headers['x-forwarded-for'] || req.connection.remoteAddress,
timestamp: uuid(),
user_agent: req.headers['user-agent'],
body: req.body,
}
return next();
})
As mentioned by #moka , Using the request ID in each request is the crux of solving the problem. Another way of abstracting all these is by making use of http-context and uuid
So set a UUID in the httpContext before all your middlewares (set as an application middleware and not as a router middlware). now you can get the uuid anywhere in your code and log it.
Here is a sample implementation I have used
You can get the complete reference here uuid in request
const uuid = require('node-uuid');
const httpContext = require('express-http-context');
....
this.expressApp.use(httpContext.middleware);
this.expressApp.use((req, res, next) => {
httpContext.set('reqId', uuid.v4());
next();
});
Now I have used the reqId set here in my custom pino logger'
public infoLogService (fileName): pino.Logger {
return pino({
level: 'info',
name: this.appService.getApp_name(),
messageKey: 'XXX-Logs',
base: {pid: process.pid, hostname: os.hostname,
timestamp: this.getTimeStamp(),
appName: this.appService.getApp_name(),
fileName: fileName,
request_id: **isNullOrUndefined(httpContext.get('reqId'))** ? 'Not an actual request ' : httpContext.get('reqId')
},
enabled: true,
useLevelLabels: true,
});
}
If the reqId is null it means that the loggers have been inserted in code that is used before starting the express App. Hope you can use this as an alternate solution

Resources