Morgan Guide (80-20)#
The 20% you need to know that covers 80% of use cases.
Installation#
npm install morgan
Basic Setup#
const express = require('express')
const morgan = require('morgan')
const app = express()
// Add morgan middleware
app.use(morgan('dev'))
app.get('/api/users', (req, res) => {
res.json({ users: [] })
})
app.listen(3000)
Predefined Formats#
dev (Development)#
Color-coded, concise output for development.
app.use(morgan('dev'))
Output:
GET /api/users 200 12.345 ms - 150
Colors: - Green: 2xx success - Cyan: 3xx redirect - Yellow: 4xx client error - Red: 5xx server error
combined (Production)#
Standard Apache combined log format.
app.use(morgan('combined'))
Output:
::1 - - [26/Nov/2025:15:30:15 +0000] "GET /api/users HTTP/1.1" 200 1234 "-" "Mozilla/5.0"
common#
Standard Apache common log format.
app.use(morgan('common'))
Output:
::1 - - [26/Nov/2025:15:30:15 +0000] "GET /api/users HTTP/1.1" 200 1234
tiny#
Minimal output.
app.use(morgan('tiny'))
Output:
GET /api/users 200 1234 - 12.345 ms
Custom Format#
Create your own format using tokens:
// Custom format: method url status response-time
app.use(morgan(':method :url :status :response-time ms'))
Output:
GET /api/users 200 12.345 ms
Available Tokens#
| Token | Description | Example |
|---|---|---|
:method |
HTTP method | GET |
:url |
Request URL | /api/users |
:status |
Response status | 200 |
:response-time |
Response time (ms) | 12.345 |
:date |
Current date/time | Wed, 26 Nov 2025 15:30:15 GMT |
:http-version |
HTTP version | 1.1 |
:remote-addr |
Client IP | ::1 |
:user-agent |
User agent | Mozilla/5.0... |
:referrer |
Referrer URL | - |
Custom Tokens#
Define your own tokens:
// Custom token for user ID
morgan.token('user-id', (req, res) => {
return req.user?.id || 'anonymous'
})
// Use in format
app.use(morgan(':method :url :status - User: :user-id'))
Output:
GET /api/users 200 - User: 123
Conditional Logging#
Log only errors (4xx, 5xx):
app.use(morgan('combined', {
skip: (req, res) => res.statusCode < 400
}))
Log only successful requests:
app.use(morgan('dev', {
skip: (req, res) => res.statusCode >= 400
}))
Logging to File#
Write to File Stream#
const fs = require('fs')
const path = require('path')
// Create write stream
const accessLogStream = fs.createWriteStream(
path.join(__dirname, 'access.log'),
{ flags: 'a' } // append mode
)
// Log to file
app.use(morgan('combined', { stream: accessLogStream }))
Separate Error Logs#
const accessLogStream = fs.createWriteStream(
path.join(__dirname, 'access.log'),
{ flags: 'a' }
)
const errorLogStream = fs.createWriteStream(
path.join(__dirname, 'error.log'),
{ flags: 'a' }
)
// Success logs
app.use(morgan('combined', {
stream: accessLogStream,
skip: (req, res) => res.statusCode >= 400
}))
// Error logs
app.use(morgan('combined', {
stream: errorLogStream,
skip: (req, res) => res.statusCode < 400
}))
Environment-Based Config#
Different logging for dev vs production:
const morgan = require('morgan')
if (process.env.NODE_ENV === 'production') {
// Production: detailed logs to file
const accessLogStream = fs.createWriteStream(
path.join(__dirname, 'access.log'),
{ flags: 'a' }
)
app.use(morgan('combined', { stream: accessLogStream }))
} else {
// Development: colored console output
app.use(morgan('dev'))
}
TypeScript Usage#
import express, { Express } from 'express'
import morgan from 'morgan'
import fs from 'fs'
import path from 'path'
const app: Express = express()
// Development
if (process.env.NODE_ENV !== 'production') {
app.use(morgan('dev'))
}
// Production
if (process.env.NODE_ENV === 'production') {
const accessLogStream = fs.createWriteStream(
path.join(__dirname, '../logs/access.log'),
{ flags: 'a' }
)
app.use(morgan('combined', { stream: accessLogStream }))
}
Custom Format with Colors (Dev)#
const morgan = require('morgan')
const chalk = require('chalk') // npm install chalk
morgan.token('colored-status', (req, res) => {
const status = res.statusCode
const color = status >= 500 ? 'red'
: status >= 400 ? 'yellow'
: status >= 300 ? 'cyan'
: 'green'
return chalk[color](status)
})
app.use(morgan(':method :url :colored-status :response-time ms'))
Log Request Body#
morgan.token('body', (req) => JSON.stringify(req.body))
app.use(express.json()) // Parse body first
app.use(morgan(':method :url :status - :body'))
Security
Be careful logging request bodies - they may contain sensitive data like passwords!
Skip Static Files#
Don't log requests for static assets:
app.use(morgan('combined', {
skip: (req, res) => req.url.startsWith('/static')
}))
Best Practices#
- Use 'dev' in development - Easy to read, color-coded
- Use 'combined' in production - Standard format, detailed
- Log to files in production - Don't rely on console logs
- Rotate log files - Use tools like
rotating-file-stream - Skip logging health checks - Reduce noise
- Never log sensitive data - Passwords, tokens, etc.
Example: Production Setup#
import express from 'express'
import morgan from 'morgan'
import fs from 'fs'
import path from 'path'
const app = express()
// Ensure logs directory exists
const logsDir = path.join(__dirname, '../logs')
if (!fs.existsSync(logsDir)) {
fs.mkdirSync(logsDir)
}
// Create write streams
const accessLogStream = fs.createWriteStream(
path.join(logsDir, 'access.log'),
{ flags: 'a' }
)
const errorLogStream = fs.createWriteStream(
path.join(logsDir, 'error.log'),
{ flags: 'a' }
)
// Success logs (2xx, 3xx)
app.use(morgan('combined', {
stream: accessLogStream,
skip: (req, res) => res.statusCode >= 400
}))
// Error logs (4xx, 5xx)
app.use(morgan('combined', {
stream: errorLogStream,
skip: (req, res) => res.statusCode < 400
}))
// Skip health check endpoint
app.use(morgan('dev', {
skip: (req, res) => req.url === '/health'
}))
app.listen(3000)
Quick Setup
For most apps, morgan('dev') in development and morgan('combined') in production is all you need!