# NestJS-Pino Review: Structured Logging With Request Context

> NestJS-Pino connects NestJS logging to Pino and pino-http so request-related fields can accompany application logs. Version 5 targets NestJS 11 and 12, Node.js 22.12 or newer, and both Express and Fastify.

**iamolegga/nestjs-pino** — Platform agnostic logger for NestJS based on Pino with REQUEST CONTEXT IN EVERY LOG

- Repository: https://github.com/iamolegga/nestjs-pino
- Stars: 1,549 · Forks: 108
- Language: TypeScript
- License: MIT
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/iamolegga-nestjs-pino

## Why NestJS-Pino Exists in a NestJS Service

NestJS-Pino addresses the gap between NestJS's conventional application logging and structured logs that can carry HTTP request metadata. Its README describes it as a platform-agnostic logger based on Pino, with request context available in every log. It is intended for TypeScript developers building NestJS applications with either Express or Fastify and needing one logging mechanism across framework startup, services, controllers, and incoming requests.

The package offers two application-facing styles. Most NestJS code can continue using `Logger` from `@nestjs/common`. Alternatively, code can inject `PinoLogger` or use `@InjectPinoLogger(...)` when direct access to Pino methods is preferable. The standard NestJS logger remains the idiomatic choice according to the README, while `NativeLogger` is intended for applications already using `ConsoleLogger` with `{ json: true }` and seeking equivalent output without rewriting their logging calls.

This is a logging integration rather than a general observability system. The README identifies Pino, pino-http, NestJS, and request-aware logging as its scope. It does not document dashboards, alerting, trace visualization, retention policy, or log storage. An organization needing those functions would need separate systems.

## The Request Context Path Through pino-http

The integration starts when `LoggerModule.forRoot(...)` or `LoggerModule.forRootAsync(...)` is registered once in the root NestJS module. According to the README, this registration installs the pino-http middleware. NestFactory is then created with `bufferLogs: true`, and `app.useLogger(app.get(Logger))` makes NestJS emit its logs through Pino.

That setup matters because framework-generated logs and ordinary service logs can share Pino's structured output path. The project description promises request context in every log, while release 5.3.1 identifies Fastify response-header logging as a specific addition. The README therefore presents request-aware logging as part of the adapter's behavior rather than as an application feature that each service must reconstruct.

`LoggerModule` is global. Once it has been registered through `forRoot(...)` or `forRootAsync(...)`, both `Logger` and `PinoLogger` are available elsewhere without importing the bare module into feature modules. This design reduces repetitive configuration, but it also makes the registration boundary important. The middleware and its providers are initialized as part of the root registration, not through repeated feature-module imports.

A separate decorator constraint follows from that architecture. The README says providers behind `@InjectPinoLogger(...)` tokens are created from contexts registered by the time `forRoot(...)` runs. A module loaded through NestJS `LazyModuleLoader` is evaluated later, so the documented decorator token may be unavailable. The documented alternative is to inject `PinoLogger` directly and call `setContext(...)`, which does not depend on that context-specific token.

## Installing nestjs-pino and Enabling the Root Logger

The documented installation adds both the NestJS adapter and its HTTP integration. For the v5 documentation, this requires Node.js 22.12 or newer. The compatibility table associates v5 with NestJS 11.0.8 or 12.0.2 and newer, Pino 10, and pino-http 11.

Run this command in a NestJS TypeScript project:

```bash
npm i nestjs-pino pino-http
```

The package entry ends with these two imports. `forRoot()` registers the module once, and the named `AppModule` is the project's root module:

```ts
import { LoggerModule } from 'nestjs-pino';

@Module({
  imports: [LoggerModule.forRoot()],
})
class AppModule {}
```

The application entry point then buffers startup logs and installs the retrieved logger. Existing startup messages should pass through the configured logger rather than staying on a separate console path:

```ts
import { Logger } from 'nestjs-pino';

const app = await NestFactory.create(AppModule, { bufferLogs: true });
app.useLogger(app.get(Logger));
```

At this stage, a service can keep using NestJS's built-in logger. The next section shows the first application log call. The shortest documented setup does not require a custom transport, output destination, or Pino option.

## Logging From a NestJS Service With Request-Aware Pino

The lowest-change path is to instantiate `Logger` from `@nestjs/common` inside a service. NestJS-Pino adapts its calls to Pino. The README notes that Pino's `trace` and `info` map to NestJS's `verbose` and `log`, respectively, because those are the method names expected by NestJS's `LoggerService` interface. Object logging and format-string arguments remain available.

A first service log can use this pattern:

```ts
import { Logger } from '@nestjs/common';

export class MyService {
  private readonly logger = new Logger(MyService.name);
  foo() {
    this.logger.verbose({ foo: 'bar' }, 'baz %s', 'qux');
    this.logger.debug('foo %s %o', 'bar', { baz: 'qux' });
    this.logger.log('foo');
  }
}
```

The class name becomes the logger context passed to NestJS, while `foo` gives the example method a visible call site. With the root module and application logger configured, the README says these calls are produced through Pino internally. Request context is added by the integration rather than manually passed into every method.

Code that needs the Pino interface can inject `PinoLogger` instead. The README describes its methods as matching a Pino instance, including `trace`, `debug`, and `info`. This route is useful when an existing service already relies on Pino call shapes. However, direct injection does not create the context-specific decorator token, so the service must either call `setContext(MyService.name)` or receive the context through `@InjectPinoLogger(MyService.name)` outside a lazily loaded module.

The distinction has a practical cost. Choosing `Logger` preserves idiomatic NestJS service code. Choosing `PinoLogger` makes the coupling to Pino explicit and requires more attention to context registration.

## Duplicate Registration and LazyModule Failure Modes

The most serious operational mistake is adding the bare `LoggerModule` class to a feature module after registering it in the root. The README says this instantiates the global module again and registers pino-http middleware a second time. The documented result is two request logs for every request, with no compile error, injection failure, or warning.

This failure is particularly awkward during incremental adoption. A developer may add `imports: [LoggerModule]` while trying to make `PinoLogger` available, even though the root `forRoot()` registration already exposes it globally. The correct feature-module pattern is therefore no second `LoggerModule` import at all. Existing service code should inject an already registered logger.

`@InjectPinoLogger(...)` has a narrower boundary. The README says it fails when used in a service belonging to a module loaded through `LazyModuleLoader`, producing a dependency-resolution error for the context-specific `PinoLogger:<context>` token. The providers are assembled from contexts registered by `forRoot(...)`, while lazy modules are evaluated only after that root configuration. In that case, inject `PinoLogger` and assign the context with `setContext(...)` during construction.

NestJS-Pino is the wrong choice for an application that depends on decorator-generated contextual tokens inside dynamically loaded modules and cannot change the injection pattern. The README documents the limitation but does not provide a configuration that makes that token pattern work in a lazily loaded module.

## NativeLogger, NestJS ConsoleLogger, and Version 5 Maintenance

NestJS's built-in `ConsoleLogger` is the clearest alternative for code that does not need the pino-http request path. It is already part of NestJS and supports JSON console output, but the README does not document request-context propagation comparable to NestJS-Pino. NestJS-Pino adds pino-http and delegates output to Pino while allowing existing NestJS `Logger` calls to remain in services.

For applications already configured with `ConsoleLogger` and `{ json: true }`, `NativeLogger` narrows the migration gap. The README says it produces identical JSON field names, argument handling, and error format to `ConsoleLogger`, while using Pino and retaining request context. Registration uses the same root-module pattern but retrieves `NativeLogger` for `app.useLogger(...)`:

```ts
import { NativeLogger, nativeLoggerOptions } from 'nestjs-pino';

@Module({
  imports: [LoggerModule.forRoot({ pinoHttp: nativeLoggerOptions })],
})
class AppModule {}
```

That option is narrower than a general Pino configuration. The README presents it specifically as the drop-in compatibility mode. It does not claim that arbitrary `ConsoleLogger` options map identically.

The repository is not archived, and the last push was on 2026-10-10. Releases 5.3.1 and 5.3.0 were published on 2026-10-02, while 5.2.1 added separate ESM and CJS builds on 2026-09-25. The package metadata requires Node.js `>=22.12.0` and publishes both `import` and `require` export paths. Upgrading therefore includes checking the runtime version and NestJS compatibility, not only replacing the package.

The repository declares an MIT license. That identifies the stated licensing model, but this review does not provide legal advice about how an organization must use or distribute the package.

## Express, Fastify, Module Formats, and Upgrade Checks

The README badge names Express and Fastify as supported platforms, and release 5.3.1 specifically records response-header logging under Fastify. That makes adapter behavior relevant when selecting a version, particularly for services that expose headers in logs or parse incoming requests in different ways. The README excerpt does not define the complete Fastify configuration or enumerate every request-context field, so those details should be checked against the project's configuration documentation before adoption.

The package build also introduces a concrete upgrade boundary. Version 5.2.1 added separate ESM and CJS builds, and the current `package.json` exposes a conditional `import` path and a `require` path. The CommonJS entry points to `dist/cjs/index.js`, while the ESM entry points to `dist/esm/index.js`. This split is relevant to NestJS projects that use module systems, bundlers, or mixed import conditions.

Compatibility is narrower for v5 than for v4. The v5 row requires NestJS 11.0.8 or 12.0.2 and newer, Pino 10, pino-http 11, and Node.js 22.12 or newer. The README directs users of older NestJS versions to v4, whose table covers NestJS 8 through 11, Node.js 14 and newer, and a wider range of Pino and pino-http versions. An upgrade should therefore begin with the application's NestJS and Node.js versions, not with the adapter version alone.

The repository includes source, tests, TypeScript build configurations, and an example application. Its `package.json` defines `test` as `vitest run`, `lint` as a TypeScript check followed by Biome, and separate ESM and CJS build passes. Those are maintainer scripts rather than installation commands for consumers, but they show how release compatibility and module-format changes are handled inside the project.

## Conclusion

Adopt NestJS-Pino when a NestJS service needs structured Pino output and request context while retaining the familiar NestJS Logger API. Do not adopt v5 on Node.js below 22.12 or older NestJS releases without moving to the documented v4 line. Verify root module registration first, because a second LoggerModule import silently duplicates request logs, then check that the emitted fields meet the service's logging contract.

## FAQ

### How do I install nestjs-pino in a NestJS project?

Install `nestjs-pino` and `pino-http` with npm. For v5, Node.js 22.12 or newer is required by the documented compatibility table.

### How do I configure nestjs-pino as the NestJS application logger?

Register `LoggerModule.forRoot()` once in the root module, create NestFactory with `bufferLogs: true`, then pass `app.get(Logger)` to `app.useLogger(...)`.

### Why are request logs duplicated with nestjs-pino?

The README attributes duplicate request logs to importing the bare `LoggerModule` more than once. Register it only through `forRoot(...)` or `forRootAsync(...)` in the root module, then use dependency injection without another module import.

### Can I use @InjectPinoLogger with LazyModuleLoader?

The README states that `@InjectPinoLogger(...)` does not work in a module loaded through `LazyModuleLoader`. It documents injecting `PinoLogger` and calling `setContext(...)` as the alternative.

## Sources

- [iamolegga/nestjs-pino on GitHub](https://github.com/iamolegga/nestjs-pino)
- [Issues](https://github.com/iamolegga/nestjs-pino/issues)
- [License: MIT](https://github.com/iamolegga/nestjs-pino/blob/master/LICENSE)
- [README](https://github.com/iamolegga/nestjs-pino/blob/master/README.md)
- [Releases](https://github.com/iamolegga/nestjs-pino/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/iamolegga-nestjs-pino
