Creating a Logger in Node Express using Morgan and Winston
In this blog, learn how to set up a robust logging system in a Node.js Express app using Morgan for HTTP request logging and Winston for general logging tasks like errors and debugging. Combine these two libraries for clearer, more structured logs, and boost application transparency.

Logging is one of the most crucial aspects of any backend application. Whether you're debugging, monitoring performance, or tracking user activities, a reliable logging system helps you maintain application transparency. In a Node.js Express application, you can use popular logging libraries like Morgan for HTTP request logging and Winston for general application logging.
In this blog post, we’ll walk through creating an efficient logging middleware using Morgan for HTTP logging and Winston for other logging tasks like errors, warnings, and debug messages.
Why Use Morgan and Winston?
- Morgan is a great tool for logging HTTP requests, providing insights into incoming requests and outgoing responses.
- Winston is a more comprehensive logger, allowing us to categorize logs based on severity, output them in a structured format, and transport them to different locations like files, the console, or external services.
By combining these two powerful libraries, we get a robust logging solution that covers both HTTP requests and general application logs.
Setting Up HTTP Logging with Morgan
First, let's start by setting up HTTP request logging with Morgan.
Morgan allows you to define custom formats and tokens. For example, you might want to log the response time, HTTP status code, the user associated with the request, and other metadata. Here’s how you can create a custom format using Morgan and integrate it with your Express app.
import morgan, { FormatFn } from "morgan";
import { Request, Response } from "express";
import { ExtendedRequest } from "../domains/user/types"; // A custom type for request
import moment from "moment";
// Custom token to get the userId (or username) from the request
morgan.token("userId", (req: Request) =>
(req as ExtendedRequest).user
? (req as ExtendedRequest).user.username
: "guest",
);
// Define a custom logging format
const logFormat: FormatFn<Request, Response> = (tokens, req, res) => {
const status = tokens.status(req, res);
const statusColor =
Number(status) >= 500
? "\x1b[31m" // Red for 500+ errors
: Number(status) >= 400
? "\x1b[33m" // Yellow for 400+ errors
: Number(status) >= 300
? "\x1b[36m" // Cyan for 300+ redirects
: "\x1b[32m"; // Green for 200+ success
const responseTime = tokens["response-time"](req, res) as string;
const responseTimeColor =
parseInt(responseTime, 10) >= 1000
? "\x1b[31m" // Red for response time > 1000ms
: parseInt(responseTime, 10) >= 500
? "\x1b[33m" // Yellow for response time > 500ms
: "\x1b[32m"; // Green for fast responses
const userId = tokens.userId(req, res);
const userIdColor = userId === "guest" ? "\x1b[90m" : "\x1b[37m"; // Gray for guests, white for users
const date = moment().format("YYYY-MM-DD HH:mm:ss");
// The custom format for logging
return `${date} http: ${tokens.method(req, res)} ${tokens.url(req, res)} ${statusColor}${status}\x1b[0m ${responseTimeColor}${responseTime}ms\x1b[0m - ${tokens.res(req, res, "content-length") || "0"} bytes - ${userIdColor}${userId}\x1b[0m`;
};
// Create a Morgan HTTP logger middleware using the custom format
const httpLogger = morgan(logFormat);
export default httpLogger;
Breakdown of the Morgan Logger
- Custom Token: We added a custom token
userId
that extracts the user's ID (or username) from the request object. If the user is not authenticated, it returns"guest"
. - Custom Format: The
logFormat
function customizes the way HTTP requests are logged:- Status Color: The HTTP status is color-coded for better readability. Errors (
500
and above) are logged in red, warnings (400
+) in yellow, redirects (300
+) in cyan, and success (200
+) in green. - Response Time: The response time is color-coded based on how fast the server responds.
- User Identification: It logs the
userId
to associate requests with users or guests. - Content-Length: Logs the size of the response.
- Status Color: The HTTP status is color-coded for better readability. Errors (
- Moment.js for Date Formatting: We use
moment.js
to format the date and time, making sure each log entry has a readable timestamp.

By adding this middleware to your Express app, every HTTP request will now be logged in a meaningful and visually distinct manner.
General Logging with Winston
While Morgan is great for HTTP request logging, you need a more flexible solution for other application logs, such as errors, warnings, and debugging information. This is where Winston comes in.
Setting Up Winston Logger
Winston allows you to create loggers with various transports (i.e., where the logs go: console, files, or external logging services). You can also assign log levels such as error
, warn
, info
, http
, and debug
, each representing a different severity.
import winston from "winston";
// Define custom log levels
const levels = {
error: 0,
warn: 1,
info: 2,
debug: 3,
};
// Assign colors to each log level for better visibility
const colors = {
error: "red",
warn: "yellow",
info: "blue",
debug: "white",
};
winston.addColors(colors);
// Configure Winston's logging format
const format = winston.format.combine(
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
winston.format.colorize(), // Colorize the output
winston.format.printf(({ timestamp, level, message }) => {
return `${timestamp} ${level}: ${message}`;
}),
);
// Define where the logs should go (console in this case)
const transports = [new winston.transports.Console()];
// Create the Winston logger instance
const logger = winston.createLogger({
level: "info", // Default logging level
levels, // Use the custom levels defined above
format, // Use the custom format defined above
transports, // Send logs to the console
});
export default logger;
Breakdown of the Winston Logger
- Custom Log Levels: We defined custom log levels (
error
,warn
,info
, anddebug
), each mapped to a severity. For instance,error
is the most severe (0), whiledebug
is the least severe (4). - Colorized Log Levels: We also define custom colors for each log level. Errors are logged in red, warnings in yellow, informational logs in blue, and debug logs in white.
- Log Format: Using
winston.format.combine()
, we define the log output. Each log entry has a timestamp and the log level, with the message that was logged. Thecolorize()
method makes the logs more readable. - Transports: In this example, we’re only logging to the console using
winston.transports.Console()
. However, Winston supports multiple transports, so you could easily add file-based logging or send logs to an external service like AWS CloudWatch or Loggly.

This is what the logs will look like in the console. The format is shown below. From the logs, you can identify the type of message, such as 'info', along with the color you configured in the Winston setup.
Using the Loggers
First, add the Morgan logger as middleware in your server.ts
file.
import express from "express";
import mainRouter from "./routes";
import httpLogger from "./libs/morgan";
const app = express();
app.use(httpLogger);
app.use(mainRouter);
app.get("/", (req, res) => {
res.send("The Server is Running Fine 🚀");
});
export default app;
You can use the Winston logger anywhere in your application by importing it and calling the appropriate logging methods based on the severity.
import logger from "./middlewares/logger";
// Log an info message
logger.info("Server started successfully.");
// Log an error message
logger.error("Unable to connect to the database.");
// Log a debug message
logger.debug("Fetching user details from the database.");
With Morgan handling HTTP request logs and Winston taking care of general logging, you now have a robust and scalable logging system for your Node.js Express application.
- Morgan logs incoming HTTP requests and responses with custom formats that include details like request time, status, response time, and user IDs.
- Winston is used for more granular, application-wide logging, with the ability to create custom log levels, colors, and formats.
This dual-logger setup is both simple and effective. It provides clear, readable logs that can help track down issues, monitor performance, and analyze application behavior over time.
Next Steps
- Consider adding file-based logging or cloud-based logging using Winston for production environments.
- Explore log rotation for file logs, which is especially useful in production to manage log size.
By using these tools effectively, you'll make your application much easier to debug, monitor, and maintain.
That's it for today. See ya 👋