The ability to detect what happens with API applications is an important aspect of monitoring, tracing and debugging back and applications. But how do you distinguish between two API applications notifications at the same API closing point?
The purpose of this article is to tell you how:
Properly assign API requests to your Express applications, properly,
Store and access the ID by using AsynClocalstorage API in Node Dot J, and
Use it in application logging.
Along with this guide, it will be helpful to make API’s closing points and use middleware in the express as you follow you. You can also apply the ideas to the framework like Nest JS and AA from this article.
The table of content
Starting with Starter Repeatory
To make it easier to follow, I have developed a starter project and hosted it on the Gut Hub. You can Clone it from here. Install the dependence on your local computer using your preferred Javascript Package Package Manager (NPM, Yarn, PNPM, Bin) to run and run it on your local computer. Then start the application by running npm start
Command in the project terminal.
If the application begins successfully, it should log in to the bottom piece to the terminal:
Listening on port 3333
The application is currently just an API closing point. GET /
. When you use API at the closing point using curl
Or by visiting the browser you will get the “OK” string as a payload response:
$ curl -i
OK%
If the above piece is what you see, congratulations! You have set the project correctly.
Ligger Utilites
The first step is to set customs lags for login messages in the terminal. The Lugars will log in the events that have arisen during the API application process and log in to the application summary.
You will need to install two Express middleware to achieve this goal. Moangan And Winston – Use your preferred package manager. If you use npm
You can run the command down the project folder in the terminal:
$ npm install morgan winston
If the above command is successful, Morgan and Winston will be added to it dependencies
In objection package.json
. Create a file that has a name logger.js
In the project route folder. logger.js
Customs will consist of a code for the utility.
The first ligar utility that you will make logger
Winston is prepared from the package you have previously installed. logger
Is the item with two ways:
const winston = require("winston");
const = winston.format;
const logHandler = winston.createLogger({
level: "debug",
levels: winston.config.npm.levels,
format: combine(
timestamp(),
errors( "500"),
content_length: tokens.res(req, res, "content-length") + " bytes",
response_time: Number(tokens("response-time")(req, res) ),
json(),
colorize({
all: true,
colors: {
info: "gray",
error: "red",
},
})
),
transports: (new winston.transports.Console()),
});
exports.logger = {
info: function (message) {
logHandler.child({}).info(message);
},
error: function (message) {
logHandler.child({}).error(message);
},
};
In a piece of code mentioned above, winston.createLogger
Used to create logHandler
. logger
Is exported out of logger.js
Module and logger.info
And logger.error
Are the functions that use logHandler
Log in messages in the terminal.
The second Logar Utility will be a middleware that will log in to the application before sending information about the application. This will log information, such as how long it took to run the application and the application status code. It will be called logRequestSummary
And Morgan will package and use http
Method logHandler
.
const winston = require("winston");
const morgan = require("morgan");
const { combine, errors, json, timestamp, colorize } = winston.format;
const logHandler = winston.createLogger({
format: combine(
colorize({
all: true,
colors: {
http: "blue",
},
})
),
});
exports.logger = {
};
exports.logRequestSummary = morgan(
function (tokens, req, res) {
return JSON.stringify({
url: tokens.url(req, res),
method: tokens.method(req, res),
status_code: Number(tokens.status(req, res) || "500"),
content_length: tokens.res(req, res, "content-length") + " bytes",
response_time: Number(tokens("response-time")(req, res) || "0") + " ms",
});
},
{
stream: {
write: (message) => {
const httpLog = JSON.parse(message);
logHandler.http(httpLog);
},
},
}
);
JSON string returned by the first function when morgan
The function is hanged write
The job of stream
In the second argument, the function of the object was transferred to the function. Then he was passed to JSON and moved logHandler.http
To be logged in with winston.npm
http
Level of intensity.
At this point, two items are recovered from logger.js
: logger
And logRequestSummary
.
I index.js
Create a new controller to handle GET
To the requests /hello
Even import and use the way exported items logger.js
. Use logger
Information to log in when events occur in controllers and add logRequestSummary
As a middleware for application.
const express = require("express");
const { logRequestSummary, logger } = require("./logger");
const app = express();
app.use(
express.json(),
express.urlencoded({ extended: true }),
logRequestSummary
);
app.get("/", function (req, res) {
logger.info(`${req.method} request to ${req.url}`);
return res.json({ method: req.method, url: req.url });
});
app.get("/hello", function (req, res) {
logger.info(`${req.method} request to ${req.url}`);
return res.json({ method: req.method, url: req.url });
});
Stop the application (with CTRL
+ C
Or OPTION
+ C
), And resume with her npm start
. Make API applications at the closing locations of both APIs, you will see output like a piece below in the terminal – an event log and summary of the application summary.
{
"level": "info",
"message": "GET request to /",
"timestamp": "2025-08-16 10:35:06.831 PM"
}
{
"level": "http",
"message": {
"content_length": "26 bytes",
"method": "GET",
"response_time": "9.034 ms",
"status_code": 200,
"url": "/"
},
"timestamp": "2025-08-16 10:35:06.844 PM"
}
You can see the latest condition of the code 2-custom-logger-middleware
Using the Branch git checkout 2-custom-logger-middleware
Or by visiting the branch 2-Custom-Logar-Medalware Of storage
Now that you are able to log and view the events that occur for each API application, how do you distinguish between two consecutive requests at the same closing point? How do you know which API application logged in a specific message? How do you explain the API request when you talk to your fellow fellow? By attaching a unique ID to each request, you will be able to answer all these questions.
What is AsynClocalstorage and why is it important?
Ago AsynClocalstorageInformation related to application context in Express Storage users res.locals
The object with asyncalstorage, node. The JS provides a local way to store information that is necessary to perform contradictory functions. According to its documents, it is a performance and memory -stored implementation that includes significant improvements that will be difficult for you to implement yourself.
When you use AsynClocalstorage, you can store and access information in the same way Local storage In the browser you have passed the Store Value (usually a item, but it may also be an ancient value) as a first argument and a contradictory function that should be accessed by another argument when you process it. run
Method
James Steel, one of the leading partners of Node.com JS, explain this in this video further ASYNC Local Storage API with ASYNC Context to track in the node with API.
Store the application ID in AsynClocalstorage
In the project, create a file with the name context-storage.js
. In this file, you will exemplify and export it asyncalstorage (if these are not yet configured). This example of the Asniccoal storage will be used in storing and recovering application identities for any other context, which requires the identification of the application.
const { AsyncLocalStorage } = require("node:async_hooks");
let store;
module.exports.contextStorage = function () {
if (!store) {
store = new AsyncLocalStorage();
}
return store;
};
You will create another file called set-request-id.js
Which will build and export a middleware. Middleware stops API requests, will produce an ID of application, and will store it asyncalstorage for example context-storage.js
If it does not already exist.
You can use your desired ID -making library, but we’ll use here randomUUID
From nod dot j crypto
Package
const { randomUUID } = require("node:crypto");
const { contextStorage } = require("./context-storage");
module.exports.setRequestId = function () {
return function (_req, _res, next) {
requestId = randomUUID();
const store = contextStorage().getStore();
if (!store) {
return contextStorage().run({ requestId }, next);
}
if (store && !store.requestId) {
store.requestId = requestId;
return next();
}
return next();
};
};
I setRequestId
Function in the above piece, examples of asyniccoal storage context-storage.js
Has been recovered from the return price of the execution contextStorage
As if store
. Unless store
Is wrong, run
Executes the procedure next
Express callback, providing requestId
For access to anywhere in anything next
By contextStorage
.
Unless store
It has a value but not requestId
Property, Set requestId
Property and its price and execution on next
Ceremony
Finally, the place setRequestId
As the first middleware of the Express application index.js
So that each application can identify the identity before conducting another operation.
const express = require("express");
const { logRequestSummary, logger } = require("./logger");
const { setRequestId } = require("./set-request-id");
const app = express();
app.use(
setRequestId(),
express.json(),
express.urlencoded({ extended: true }),
logRequestSummary
);
You can check out the current state of this project if you run git checkout 3-async-local-storage-req-id
Command on your terminal or by visiting 3-eSync-Local-Storage-Req-Eid Of the Gut Hub
Use the application ID in Lagar’s utility
Now that requestId
Placed in the property store, you can access it from anywhere next
Using contextStorage
. You will have access to the functions in it logger.js
And connect it to the logs so that when the messages for an application are logged into the terminal, the application ID will appear with the login message.
const winston = require("winston");
const morgan = require("morgan");
const { contextStorage } = require("./context-storage");
const { combine, errors, json, timestamp, colorize } = winston.format;
const logHandler = winston.createLogger({
level: "debug",
levels: winston.config.npm.levels,
format: combine(
winston.format((info) => {
info.request_id = contextStorage().getStore()?.requestId;
return info;
})(),
timestamp({ format: "YYYY-MM-DD hh:mm:ss.SSS A" }),
errors({ stack: true }),
),
transports: (new winston.transports.Console()),
});
I combine
Function from Winston, you will add a function argument that accepts the login message. info
– as an argument and attach request_id
Property for this. Its value of value requestId
Retrieved from contextStorage
. With this amendment, any message login for application will be the ID of the application associated with this application.
With this complete, stop running the project if it is already running and running it again npm start
Order. Make API applications to two closing locations and you will see outpatted like a bottom piece on the terminal:
{
"level": "info",
"message": "GET request to /hello",
"request_id": "c80e92d0-eafe-42c7-b093-e5ffce014819",
"timestamp": "2025-08-17 07:58:13.571 PM"
}
{
"level": "http",
"message": {
"content_length": "31 bytes",
"method": "GET",
"response_time": "9.397 ms",
"status_code": 200,
"url": "/hello"
},
"request_id": "c80e92d0-eafe-42c7-b093-e5ffce014819",
"timestamp": "2025-08-17 07:58:13.584 PM"
}
Unlike the previous logout, it contains ID for each application. Using AsyncLocalStorage
Application ID value effectively and using it in Ligers to access it, you can properly find login messages on their API requests.
If you drive you can access the current state of the project git checkout 4-use-context-in-logger
Command on the terminal or by visiting 4 in use context Of the Gut Hub
You have been able to store, access and connect the request identification with its login message. Can you compose a request as a header on reply? The challenge is to set the header, X-Request-Id
On the answer, so that the request is worth the identification of the application in response to each request. X-Request-Id
Answer Header.
It is useful to communicate with Front End when trying to debugging requests.
Conclusion
When API applications can be monitored, you can track performance matrix to discover areas that require improvement and attention, failed requests and server errors and because it has happened, and the application of the application for the planning and scalebuability in the volume matrix.
When you attach a unique identifier with an API application, you can use it to detect the events that occur within the application life and separate it from other similar requests.
In addition to using Asyncalstorage to store application identities, you can also use it to store other application information, such as authentic user details. The use of Asyncalstorage to store information related to application context is considered an excellent process.