DEV Community

Tanmay Gupta
Tanmay Gupta

Posted on

Logging in Express with Morgan and Winston

When building a Node.js/Express app, logging is essential for debugging, monitoring, and keeping track of application behavior — both during development and in production.

In this post, we'll explore two powerful logging tools for Express:

  • Morgan – A lightweight HTTP request logger
  • Winston – A robust, customizable logger for application-level events

Why Logging Matters

Logging helps you:

  • Track user requests and responses
  • Debug server-side errors
  • Monitor performance and behavior in production
  • Audit events or suspicious activity

Morgan: HTTP Request Logger

📌 What is Morgan?

Morgan logs incoming HTTP requests in a predefined format. It's ideal for development and debugging routes.


Step 1: Install Morgan

npm install morgan
Enter fullscreen mode Exit fullscreen mode

Step 2: Use Morgan in Your App

import express from 'express';
import morgan from 'morgan';

const app = express();

// Use 'dev' preset for concise logging
app.use(morgan('dev'));

app.get('/', (req, res) => {
  res.send('Hello World');
});

app.listen(3000, () => console.log('Server running'));
Enter fullscreen mode Exit fullscreen mode

Output Example:

GET / 200 8.104 ms - 11
Enter fullscreen mode Exit fullscreen mode

You can also use other formats like 'combined', 'short', or define your own:

app.use(morgan(':method :url :status :response-time ms'));
Enter fullscreen mode Exit fullscreen mode

Winston: Flexible Application Logger

📌 What is Winston?

Winston is a general-purpose logging library. It allows you to:

  • Log messages to console, files, or external services
  • Set log levels (info, warn, error, etc.)
  • Format logs (JSON, timestamps, custom formats)

Step 1: Install Winston

npm install winston
Enter fullscreen mode Exit fullscreen mode

Step 2: Create a Logger Configuration

Create a logger.js file:

import winston from 'winston';

const logger = winston.createLogger({
  level: 'info', // minimum level to log
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.printf(
      ({ timestamp, level, message }) =>
        `[${timestamp}] ${level.toUpperCase()}: ${message}`
    )
  ),
  transports: [
    new winston.transports.Console(), // log to console
    new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
    new winston.transports.File({ filename: 'logs/combined.log' })
  ]
});

export default logger;
Enter fullscreen mode Exit fullscreen mode

Step 3: Use Winston in Your Routes

import logger from './logger.js';

app.get('/health', (req, res) => {
  logger.info('Health check route hit');
  res.send('OK');
});

app.get('/error', (req, res) => {
  logger.error('Something went wrong');
  res.status(500).send('Error');
});
Enter fullscreen mode Exit fullscreen mode

Combining Morgan with Winston

You can use Morgan to log HTTP requests and pipe those logs into Winston for consistent formatting and file storage.

import morgan from 'morgan';
import logger from './logger.js';

const stream = {
  write: (message) => logger.info(message.trim())
};

app.use(morgan('combined', { stream }));
Enter fullscreen mode Exit fullscreen mode

Summary

Tool Purpose Best Use Case
Morgan HTTP request logger Log incoming HTTP requests
Winston Application-level logger Track custom events/errors

Final Thoughts

  • Use Morgan during development to trace HTTP traffic.
  • Use Winston in production for advanced logging, error tracking, and audit logs.
  • Combine both for full-stack visibility.

Top comments (0)