# Connect a NestJS app

> Add ApiTraffic to a NestJS API: install the module, set credentials, see what is recorded, and turn on the firewall, APM, tagging and tracing.

Source: https://www.apitraffic.io/docs/integrations/nestjs

`@apitraffic/nestjs` is a Nest module. Import it once, in your root module, and every request your
application serves is recorded and checked by the ApiTraffic firewall - on the Express platform
(Nest's default) and on the Fastify platform alike, with nothing to change in `main.ts`.

It is [`@apitraffic/node`](https://www.apitraffic.io/docs/integrations/node), the one-install SDK, wired into Nest: the same
capture, the same firewall and APM bridge, the same helpers, all enabled from your ApiTraffic
account rather than in code.

## Installation

**npm**

```bash
npm install @apitraffic/nestjs
```

**yarn**

```bash
yarn add @apitraffic/nestjs
```

**pnpm**

```bash
pnpm add @apitraffic/nestjs
```

> **Note:** **Node 20+ and NestJS 10, 11 or 12 required.** NestJS 12 is an ES module, which this package loads
> with `require()`: that needs Node 20.19+ or 22.12+.

## Basic Setup

```typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { ApiTrafficModule } from '@apitraffic/nestjs';
import { UsersModule } from './users/users.module';

@Module({
  imports: [
    ApiTrafficModule.forRoot({
      token: process.env.API_TRAFFIC_TOKEN,
      bucket: process.env.API_TRAFFIC_BUCKET,
    }),
    UsersModule,
  ],
})
export class AppModule {}
```

The module is global: import it once, in the root module, and the helpers below work in every
module of the application.

### Options from configuration

`forRootAsync()` takes the options from a factory, with injected providers, as other Nest modules do:

```typescript
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { ApiTrafficModule } from '@apitraffic/nestjs';

@Module({
  imports: [
    ApiTrafficModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        token: config.get('API_TRAFFIC_TOKEN'),
        bucket: config.get('API_TRAFFIC_BUCKET'),
      }),
    }),
  ],
})
export class AppModule {}
```

## What is recorded

Every request, including the ones a controller never sees. The module runs ahead of Nest's body
parser, its middleware, guards and router, so these are all recorded, with their final status and
whatever your code tagged on the way:

- a request a **guard** rejects (`401`, `403`),
- a request a **middleware** answers itself (an auth middleware's `401`),
- a request **no route** matches (`404`),
- a request whose handler **throws**, as the response Nest's exception filters sent.

The route Nest matched is sent with each request, global prefix and URI version included, so
`@Get(':id')` in `@Controller('users')` under `setGlobalPrefix('api')` and version 1 is the endpoint
`/api/v1/users/{id}`. See [Endpoint Handling](https://www.apitraffic.io/docs/endpoint-handling#framework-routes).

On the Express platform requests are captured by `@apitraffic/express`, on the Fastify platform by
`@apitraffic/fastify`: exactly what the [Express](https://www.apitraffic.io/docs/integrations/express) and
[Fastify](https://www.apitraffic.io/docs/integrations/fastify) pages describe under "What is captured" holds here too.

## Tagging, tracing and spans

The helpers work anywhere a request is being handled: controllers, services, guards, interceptors,
pipes, exception filters and middleware.

```typescript
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { tag } from '@apitraffic/nestjs';

@Injectable()
export class AccountGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    // Searchable on this request, whether or not the guard lets it through.
    tag('accountId', request.headers['x-account-id']);
    return true;
  }
}
```

```typescript
import { Injectable } from '@nestjs/common';
import { span, trace } from '@apitraffic/nestjs';

@Injectable()
export class UsersService {
  async findOne(id: string) {
    trace(`Loading user ${id}`);
    return span('db.query', 'load user', () => this.repository.findOneBy({ id }));
  }
}
```

Outside a request (a scheduled job, a queue worker) the helpers do nothing and never throw.

`stats()` reports the SDK's delivery health (delivered, queued, dropped), for a health endpoint or
a log line.

## Shutdown

`app.close()`, and the signals `app.enableShutdownHooks()` listens for, deliver what was captured
and release the SDK's timers and connections once the last application using the module has
closed. The HTTP server has closed by then, so every request it served has been reported.

```typescript
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

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

bootstrap();
```

## Firewall

The ApiTraffic firewall is mounted by the module, and whether it runs is decided in your ApiTraffic
account: nothing is loaded until you switch a policy on, and a policy change reaches every server
without a deploy. It runs where Nest has parsed the body - after Nest's body parser on the Express
platform, in Fastify's `preHandler` on the Fastify platform - so body rules see the parsed body,
and ahead of your guards and controllers, so a request it blocks never reaches your code. Blocked
requests are recorded with the policy's status and tagged `waf.*`.

Middleware you apply with `MiddlewareConsumer` runs after the firewall on the Express platform. On
the Fastify platform Nest runs middleware in Fastify's `onRequest` stage, before the body is
parsed, so there it runs ahead of the firewall, as in any Fastify application that uses
middleware.

To refuse it locally whatever the dashboard says (change control, or an incident switch that does
not depend on the control plane), pass `waf: false` or set `API_TRAFFIC_WAF_ENABLED=false`:

```typescript
ApiTrafficModule.forRoot({ waf: false });
```

See [Firewall setup](https://www.apitraffic.io/docs/waf/setup) for policies and rules.

## Application Performance Monitoring (APM)

The APM bridge captures database, cache, queue, GraphQL, gRPC, and `fetch`/`undici` spans within
each ApiTraffic request. Install the OpenTelemetry peers it uses:

```bash
npm install @opentelemetry/api @opentelemetry/sdk-trace-node @opentelemetry/instrumentation @opentelemetry/auto-instrumentations-node
```

OpenTelemetry patches modules as they are loaded, so it has to start before Nest and your database
clients do. Preload it:

```bash
node -r @apitraffic/nestjs/register dist/main.js
```

or call `register()` on the very first line of `main.ts`, ahead of every other import:

```typescript
import { register } from '@apitraffic/nestjs';
register();
```

If your application already configures OpenTelemetry, add `ApiTrafficSpanProcessor` from
`@apitraffic/apm` to that provider instead, so spans are not double-instrumented.

## Configuration Options

| Option | Environment Variable | Required | Type | Description |
|--------|---------------------|----------|------|-------------|
| `token` | `API_TRAFFIC_TOKEN` | Yes | String | Ingest token from your ApiTraffic account |
| `bucket` | `API_TRAFFIC_BUCKET` | Yes | String | Bucket ID for data organization |
| `interceptOutbound` | `API_TRAFFIC_INTERCEPT_OUTBOUND` | No | Boolean | Monitor outbound HTTP requests (default: true) |
| `debug` | `API_TRAFFIC_DEBUG` | No | Boolean | Enable debug logging (default: false) |
| `waf` | `API_TRAFFIC_WAF_ENABLED` | No | Boolean or object | `false` refuses the firewall locally; an object passes settings to `@apitraffic/waf`. Normally left unset: the firewall is switched on from your account |

Every option falls back to its environment variable, so `ApiTrafficModule.forRoot()` with no
options at all reads its configuration from the environment. An option passed in code wins over
the environment.

The options are typed: `ApiTrafficOptions` lists every option the SDK accepts, a value of the wrong
type is a compile error, and so is a misspelled key in an object literal.

```typescript
import { ApiTrafficModule, type ApiTrafficOptions } from '@apitraffic/nestjs';

const options: ApiTrafficOptions = { debug: process.env.NODE_ENV !== 'production' };
ApiTrafficModule.forRoot(options);
```

## Embed tokens

The [embeddable request log](https://www.apitraffic.io/docs/integrations/embed) authenticates with a token your server
mints. `embedToken` is exported here, as from every ApiTraffic package:

```typescript
import { Controller, Get } from '@nestjs/common';
import { embedToken } from '@apitraffic/nestjs';

@Controller('embed')
export class EmbedController {
  @Get('token')
  token() {
    return embedToken({ filters: { tags: { customerId: 'cus_123' } }, ttl: 900 });
  }
}
```

## Troubleshooting

**No requests are recorded**

Check that `ApiTrafficModule` is imported in the module you pass to `NestFactory.create()` (or to
`Test.createTestingModule()`), and that `API_TRAFFIC_TOKEN` and `API_TRAFFIC_BUCKET` are set.
Set `debug: true` to see what the SDK is doing.

**Tags set in a scheduled job or queue consumer are missing**

Tags, traces and spans belong to an HTTP request. Code that does not run while a request is
being handled has none to attach them to, and the helpers do nothing there.

**Two applications in one process**

Each application records its own requests. The SDK is shut down when the last of them closes.

## Sample Application

A complete working example is available in the [GitHub repository](https://github.com/apitraffic/platform/tree/main/libs/client-libraries/apitraffic-nestjs/examples/basic).
