Library / SDK
gremo/nest-winston avatar
gremo/nest-winston

nest-winston gives NestJS one logger instead of two

A Nest module wrapper form winston logger

732 stars97 forksTypeScriptMIT

At a glance

What is it?
nest-winston is a Nest module wrapping the winston logger, and its real purpose is to stop a NestJS application having two loggers: the framework's own internal one and whatever your code uses. It offers a global module you inject by token, and a WinstonLogger implementation of the LoggerService interface so the framework's system logs and your application logs share one format and one destination.
Who is it for?
Adopt nest-winston if you run NestJS 11 or 12 on Node 20 or later and want your application logs and the framework's internal logs in one winston pipeline. Skip it if you are on an older NestJS version, since the guidance is to use [email protected] for NestJS 5 to 10, or if you are on Node 18.
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 1 day ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

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

Editorial analysis

The problem is two loggers, and the package exists to remove one

nest-winston is a Nest module wrapper for the winston logger, and the reason it exists is visible in the way it is documented.

A NestJS application has its own internal logger that the framework uses for system logging, and separately you will want to log from your own code. If the two are configured differently, you get two formats, two destinations and two sets of configuration, and correlating a framework message with an application message becomes guesswork. The module's stated purpose is to ensure consistent behavior and formatting across both Nest system logging and your application event and message logging.

The package is a thin wrapper, and the documentation says so plainly. The `forRoot()` method accepts the same options object as the `createLogger()` function from the winston package, so there is no separate winston-configuration dialect to learn. Anything you can configure in winston, you configure here.

The requirements are specific. NestJS 11 or 12 and Node.js 20 or later, matching NestJS's own engines, and for older NestJS versions, 5 to 10, you use `[email protected]`.

The install is one command:

bash
npm install --save nest-winston winston

The package is MIT licensed, TypeScript, version 2.0.0, with the last push on 2026-10-16 and no GitHub releases, so the npm version is the number to track. The author is Marco Polichetti.

One global module, one injection token

The quick start is a module import and an injection, and the design is deliberately small.

You import `WinstonModule` into the root `AppModule` and use the `forRoot()` method to configure it:

typescript
import { Module } from '@nestjs/common';
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';

@Module({
  imports: [
    WinstonModule.forRoot({
      // options
    }),
  ],
})
export class AppModule {}

Afterward, the winston instance is available to inject across the entire project, and in your feature modules too, because WinstonModule is a global one, using the `WINSTON_MODULE_PROVIDER` injection token:

typescript
import { Controller, Inject } from '@nestjs/common';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';

@Controller('cats')
export class CatsController {
  constructor(@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) { }
}

The typing matters here and is easy to miss. The token is injected as a `Logger` from winston, not as a Nest `LoggerService`. That means the object you receive is the real winston logger with its full API, transports and levels, rather than the narrow Nest logging interface. If you want the Nest interface, that is the other provider, covered below.

The global module is what makes this ergonomic. Without it, every feature module would have to import the logging module itself, and every service would need the token in its constructor. With it, the logger is just there.

Async configuration has a caveat that is a Nest limitation, not a bug

If your options need a configuration service, you use `forRootAsync()`, returning an options object from a `useFactory` method:

typescript
import { Module } from '@nestjs/common';
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';

@Module({
  imports: [
    WinstonModule.forRootAsync({
      useFactory: () => ({
        // options
      }),
      inject: [],
    }),
  ],
})
export class AppModule {}

The factory might be async, can inject dependencies with the `inject` option and import other modules using the `imports` option. Alternatively you can use the `useClass` syntax, `WinstonModule.forRootAsync({ useClass: WinstonConfigService })`, and then Nest will create a new instance of that class and call its `createWinstonModuleOptions` method to provide the module options.

The caveat is stated before any of that, and it is worth understanding rather than working around. Because of the way Nest works, you cannot inject dependencies exported from the root module itself, using `exports`. If you use `forRootAsync()` and need to inject a service, that service must be either imported using the `imports` options or exported from a global module.

This is the standard circular dependency problem in Nest, and the module inherits it rather than solving it. A configuration service that lives in the root module and is exported there cannot be injected into the root module's own async factory. The workarounds are the ones Nest offers everywhere: import a separate configuration module, or make the configuration module global.

It is the kind of caveat that costs an afternoon the first time and is invisible afterwards, which is why it is in the documentation above the examples rather than in a footnote.

The WinstonLogger is the piece that unifies the framework logs

The module also provides the `WinstonLogger` class, a custom implementation of the `LoggerService` interface, to be used by Nest for system logging. This will ensure consistent behavior and formatting across both Nest system logging and your application event and message logging.

The wiring is in main.ts:

typescript
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useLogger(app.get(WINSTON_MODULE_NEST_PROVIDER));
  await app.listen(3000);
}
bootstrap();

And the same token gives you the logger in your controllers, typed as the Nest interface this time:

typescript
import { Controller, Inject, LoggerService } from '@nestjs/common';
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';

@Controller('cats')
export class CatsController {
  constructor(@Inject(WINSTON_MODULE_NEST_PROVIDER) private readonly logger: LoggerService) { }
}

Under the hood, the `WinstonLogger` class uses the configured winston logger instance, through `forRoot` or `forRootAsync`, forwarding all calls to it.

So there are two tokens, and the distinction is the one to internalise. `WINSTON_MODULE_PROVIDER` is the winston logger itself, with the full winston API. `WINSTON_MODULE_NEST_PROVIDER` is the Nest `LoggerService` wrapper around it, and passing that to `app.useLogger()` is what redirects the framework's own output.

That distinction is what makes the two halves of the library work together. The application logs go through winston directly, and the framework logs go through WinstonLogger, which forwards to the same winston instance, so both end up in the same transport with the same format.

The alternative is to drop dependency injection entirely

There is a second way to wire the logger, and it comes with a warning that is worth reading before you choose it.

The important note is that by doing this, you give up the dependency injection, meaning that `forRoot` and `forRootAsync` are not needed and shouldn't be used. Remove them from your main module.

The reason it exists is a bootstrapping problem. Using the dependency injection has one minor drawback. Nest has to bootstrap the application first, instantiating modules and providers and injecting dependencies, and during this process the instance of `WinstonLogger` is not yet available, which means that Nest falls back to the internal logger.

So with the DI approach, the first few log lines your application emits go to Nest's built-in logger rather than to winston. For most applications that is harmless. For an application where log format and destination matter from the very first line, it is not.

The alternative is to create the logger outside the application lifecycle, using the `createLogger` function, and pass it to Nest directly, which is what the second sample directory in the repository, replace-nest-logger-bootstrap, demonstrates.

The trade-off is real and symmetric. The DI approach gives you a globally injectable logger, at the cost of a few bootstrap messages going to the wrong place. The bootstrap approach gives you every message in the right place, at the cost of a logger that exists outside the module system and cannot be injected. The documentation points at both samples, replace-nest-logger and replace-nest-logger-bootstrap, so the decision is one of which sample you start from.

Editorial conclusion

Adopt nest-winston if you run NestJS 11 or 12 on Node 20 or later and want your application logs and the framework's internal logs in one winston pipeline. Skip it if you are on an older NestJS version, since the guidance is to use [email protected] for NestJS 5 to 10, or if you are on Node 18. Verify first which of the two integration modes you want, the dependency injection route or the bootstrap replacement, because the documentation says the second means giving up dependency injection and removing forRoot and forRootAsync.

Frequently asked questions

How do I install nest-winston?

Run `npm install --save nest-winston winston`. The requirements are NestJS 11 or 12 and Node.js 20 or later, matching NestJS's own engines. For older NestJS versions, 5 to 10, use `[email protected]`.

What is the difference between WINSTON_MODULE_PROVIDER and WINSTON_MODULE_NEST_PROVIDER?

WINSTON_MODULE_PROVIDER injects the winston logger itself, typed as winston's Logger, and is available across the project because WinstonModule is a global module. WINSTON_MODULE_NEST_PROVIDER injects the Nest LoggerService wrapper, and passing it to app.useLogger() in main.ts is what redirects Nest's own system logging into winston.

How do I configure nest-winston asynchronously?

Use WinstonModule.forRootAsync with a useFactory returning the options object, plus inject for dependencies and imports for other modules, or use the useClass syntax where Nest calls createWinstonModuleOptions on the class. The documented caveat is that you cannot inject dependencies exported from the root module itself, so an injected service must be imported or come from a global module.

Official sources

  1. gremo/nest-winston on GitHub
  2. Issues
  3. License: MIT
  4. README
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/gremo-nest-winston.svg)](https://hysenlabs.com/projects/gremo-nest-winston)

Was this useful?