In this blog post, we are going to take a look at how to create a well-structured log system in Node JS using Express JS, Morgan, and Winston.
I was looking for a robust logging system for a crypto dashboard API that I built one month ago and I found this article: https://levelup.gitconnected.com/better-logs-for-expressjs-using-winston-and-morgan-with-typescript-1c31c1ab9342 very useful. The sample made in the article is TypeScript based. So, if you're trying to use TypeScript instead of Js, you don't need to continue reading the current post, just check out the link. In my crypto project, I hadn't used TypeScript, that's why I am writing this blog post.
To follow along with this article, you will need:
express-morgan-winston for your projectnpm init commandexpress, morgan, and winston as dependenciesnpm init command: app.js in my case). This is where you will handle the main logic in your Express servermkdir express-morgan-winston
cd express-morgan-winston
npm init
npm install morgan winston --save
touch src/app.js # or mdkdir src && echo "" > src/app.js
Basically, we need to define :
Create a file src/utils/logger.js and copy-paste the code below. All parts of the configuration are well explained.
const winston = require('winston');
// Define your severity levels.
// With them, You can create log files,
// see or hide levels based on the running ENV.
const levels = {
error: 0,
warn: 1,
info: 2,
http: 3,
debug: 4,
}
// This method set the current severity based on
// the current NODE_ENV: show all the log levels
// if the server was run in development mode; otherwise,
// if it was run in production, show only warn and error messages.
const level = () => {
const env = process.env.NODE_ENV || 'development'
const isDevelopment = env === 'development'
return isDevelopment ? 'debug' : 'warn'
}
// Define different colors for each level.
// Colors make the log message more visible,
// adding the ability to focus or ignore messages.
const colors = {
error: 'red',
warn: 'yellow',
info: 'green',
http: 'magenta',
debug: 'white',
}
// Tell winston that you want to link the colors
// defined above to the severity levels.
winston.addColors(colors)
// Chose the aspect of your log customizing the log format.
const format = winston.format.combine(
// Add the message timestamp with the preferred format
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss:ms' }),
// Tell Winston that the logs must be colored
winston.format.colorize({ all: true }),
// Define the format of the message showing the timestamp, the level and the message
winston.format.printf(
(info) => `${info.timestamp} ${info.level}: ${info.message}`,
),
)
// Define which transports the logger must use to print out messages.
// In this example, we are using three different transports
const transports = [
// Allow the use the console to print the messages
new winston.transports.Console(),
// Allow to print all the error level messages inside the error.log file
new winston.transports.File({
filename: 'logs/error.log',
level: 'error',
}),
// Allow to print all the error message inside the all.log file
// (also the error log that are also printed inside the error.log(
new winston.transports.File({ filename: 'logs/all.log' }),
]
// Create the logger instance that has to be exported
// and used to log messages.
const logger = winston.createLogger({
level: level(),
levels,
format,
transports,
})
module.exports = logger
We have a logger utility file that is ready for use anywhere in our express app. Now let's configure Morgan.
The configuration we need to make here is very simple:
So I created the file src/middlewares/morgan.middleware.js and add the following code:
const morgan = require("morgan");
const logger = require("../utils/logger");
const stream = {
// Use the http severity
write: (message) => logger.http(message),
};
const skip = () => {
const env = process.env.NODE_ENV || "development";
return env !== "development";
};
const morganMiddleware = morgan(
// Define message format string (this is the default one).
// The message format is made from tokens, and each token is
// defined inside the Morgan library.
// You can create your custom token to show what do you want from a request.
":remote-addr :method :url :status :res[content-length] - :response-time ms",
// Options: in this case, I overwrote the stream and the skip logic.
// See the methods above.
{ stream, skip }
);
module.exports = morganMiddleware;
const express = require("express");
const morganMiddleware = require("./middlewares/morgan.middleware");
// The morgan middleware does not need this.
// This is for a manual log
const logger = require("./utils/logger");
const app = express();
// Add the morgan middleware
app.use(morganMiddleware);
app.get("/api/status", (req, res) => {
logger.info("Checking the API status: Everything is OK");
res.status(200).send({
status: "UP",
message: "The API is up and running!"
});
});
// Startup
app.listen(3000, () => {
logger.info('Server is running on port 3000');
});
Run the app using the command node src/app.js and call the API status endpoint. Below is how my terminal looks like:

You can notice that a log file named logs/all.log was automatically generated and contains all log messages.
Our final project structure looks like below:
📘express-morgan-winston/
┣ 📁logs/
┃ ┣ ✨all.log
┃ ┗ ✨error.log
┣ 📁src/
┃ ┣ 📁middlewares/
┃ ┃ ┗ ✨morgan.middleware.js
┃ ┣ 📁utils/
┃ ┃ ┗ ✨logger.js
┃ ┗ ✨app.js
┣ ✨package-lock.json
┗ ✨package.json
Check out the source code on my GitHub organization Lioncoding-oss.
If you find this blog post useful, please share it on your favorite social media. Don't forget to follow me on GitHub and Twitter. To send me a message, please use the contact form or DM me on Twitter.
Quick Links