Library / SDK
expressjs/morgan avatar
expressjs/morgan

morgan: HTTP request logging middleware for Express and Node.js

HTTP request logger middleware for node.js

8,204 stars570 forksJavaScriptMIT

At a glance

What is it?
morgan writes one log line per HTTP request, using predefined Apache-style formats or your own token string. It is small, MIT licensed, and depends on Express only by convention, not by code.
Who is it for?
Adopt morgan if you run an Express or plain Node HTTP server and want request lines in stdout, in a file, or forwarded to a structured logger through the stream option. Do not adopt it if you need correlation IDs across services, log rotation, or sampling; morgan writes one line per request and leaves those concerns to whatever receives the stream.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 19 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What morgan does that console.log does not

A Node HTTP server has no request log by default. You can add console.log inside a route handler, but you then have to remember to log the method, the URL, the status code, the response size, and the time the response took, and you have to place the call where the response has already been written. morgan is middleware that does this once, for every request, at a fixed point in the pipeline. The README describes it as HTTP request logger middleware for node.js, and the package is published on the npm registry as morgan.

The intended user is someone running an Express application, or any Node HTTP server where a middleware function of the shape (req, res, next) fits. The output is a line of text per request, by default on process.stdout. Nothing about the package is Express-specific in the dependency list: the dependencies are basic-auth, debug, depd, on-finished and on-headers. The Express association comes from the project living under the expressjs organization and from the middleware signature.

How a log line is assembled: formats, tokens and the response lifecycle

morgan(format, options) returns a middleware function. The format argument can be a predefined name such as combined, common, dev, short or tiny; a format string built from tokens, for example ':method :url :status :res[content-length] - :response-time ms'; or a function that receives tokens, req and res and returns the log line, or undefined or null to skip logging.

The predefined formats are token strings. The README shows combined expanding to :remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent", which is the Apache combined format. tiny is the minimal output, :method :url :status :res[content-length] - :response-time ms.

Two hooks explain the timing. on-headers fires when response headers are written, and on-finished fires when the response completes. The :response-time token is documented as the time between the request coming into morgan and when the response headers are written, in milliseconds. That is a header-write measurement, not a full body-transfer measurement, and it is worth knowing before you compare it against a client-side timer.

The immediate option changes the order: the log line is written on the request instead of the response. The README states the consequence plainly: requests are logged even if the server crashes, but response data such as the response code and content length cannot be logged. Tokens are extensible. morgan.token('type', function (req, res) { return req.headers['content-type'] }) defines a :type token, and defining a token with an existing name overwrites it.

Installing morgan and logging your first Express request

The README gives one installation command, run against the npm registry:

bash
npm install morgan

After that, import it. The README shows both module systems. For ES Modules:

js
import morgan from 'morgan'

For CommonJS:

js
var morgan = require('morgan')

A first real use is to pass a predefined format name to morgan. The README's own examples call morgan('tiny') and morgan('combined') directly. With the tiny format, the README shows a request to /tiny producing a line of the shape GET /tiny 200 2 - 0.188 ms. To restrict logging to failures, the README documents a skip function called as skip(req, res):

js
morgan('combined', {
  skip: function (req, res) { return res.statusCode < 400 }
})

That configuration logs only responses with a status code of 400 or above.

Where morgan stops: streams, rotation and structured output

morgan writes to process.stdout by default, and the stream option changes the destination. The README documents one sharp edge: if the stream is in object mode (stream.writableObjectMode is true) and the format function returns an object, the object is written to the stream as-is, without a trailing newline. A custom format function returning an object is therefore the supported path to structured log entries, and it only works with an object-mode stream. Return a string to a normal stream and you get a line, not a JSON document.

What morgan does not do is equally clear from the README. There is no rotation, no retention policy, no log level, no sampling and no request correlation ID. If you point it at stdout, rotation belongs to whatever collects stdout. If you point it at a file stream, you own the file lifecycle. The dev format's colouring is also stateful in a way that can surprise you: colouring is disabled when the NO_COLOR environment variable is set to any non-empty value, and the README states the variable is read once, when morgan is first required. Setting NO_COLOR after your app has already loaded morgan will not change the output.

One more boundary: morgan logs the request as seen by the process. Behind a proxy, :remote-addr uses req.ip when available, otherwise the socket address. If your Express app does not trust the proxy, the address in the log is the proxy's.

morgan versus pino-http and winston

The closest alternative in an Express codebase is pino-http, and the difference in approach is structural rather than cosmetic. pino-http emits JSON objects by default and is built around pino's serializers and transports, so the log record is a data structure from the start. morgan emits a formatted string by default and only produces objects when you write a custom format function and give it an object-mode stream. If your downstream pipeline parses JSON, pino-http removes a parsing step; if your downstream pipeline is a human reading a terminal or an Apache-style access log, morgan's predefined formats already match that shape, and the README shows the exact output for combined, common, dev, short and tiny.

winston is a general logging library with levels and multiple transports; morgan is request middleware with no notion of log level. Choosing between them is not a matter of quality. It is a question of whether the unit you want to record is a request or an event.

Maintenance status, licence and upgrade cost

The repository is not archived. The last push was on 2026-09-11, and the most recent release listed is 1.12.1 on the same date, with 1.12.0 on 2026-08-28 and 1.11.0 on 2026-06-02 before that. That is a release cadence within the last few months of the repository's history.

The package is MIT licensed, and package.json carries an Open Collective funding entry for the Express project. The runtime dependency list is short and old: basic-auth at ~2.0.1, debug at 2.6.9, depd at ~2.0.0, on-finished at ~2.4.1 and on-headers at ~1.1.0. The engines field says node >= 0.8.0, which is far below any Node version currently supported upstream, so that field is not a useful guide to what you should run. The published files array is only LICENSE, README.md and index.js, so the install footprint is one source file plus its dependencies.

Upgrade cost is low by construction. The public surface is one factory function, morgan.token, and the options object; the README documents immediate, skip and stream. There is no plugin registry and no configuration file format to migrate. The one thing to re-check on upgrade is the interaction between a custom format function and an object-mode stream, since that behaviour depends on both sides.

Editorial conclusion

Adopt morgan if you run an Express or plain Node HTTP server and want request lines in stdout, in a file, or forwarded to a structured logger through the stream option. Do not adopt it if you need correlation IDs across services, log rotation, or sampling; morgan writes one line per request and leaves those concerns to whatever receives the stream. Before wiring it into production, verify three things in your own code: the format you actually want (combined for Apache-style access logs, tiny for minimal output), the NO_COLOR behaviour if you rely on the dev format in a container, and whether your stream is in object mode, since that changes whether morgan appends a trailing newline.

Frequently asked questions

How do I install morgan in Node.js?

Install it from the npm registry with npm install morgan, then import it with import morgan from 'morgan' in ES Modules or var morgan = require('morgan') in CommonJS.

How do I use morgan in Express?

Call morgan with a format and mount the returned middleware, for example morgan('tiny'), before your routes. The format can be a predefined name, a token string, or a function that returns the log line.

How do I install morgan?

The README gives a single command, npm install morgan, which pulls the package from the npm registry.

Official sources

  1. expressjs/morgan on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/expressjs-morgan.svg)](https://hysenlabs.com/projects/expressjs-morgan)