# NestJS

NestJS wants the client as a **provider** — injectable, mockable, and shut down through the
framework's lifecycle hooks rather than a raw `process` listener.

## 1. Install

```bash
npm i bugboard
```

```dotenv
BUGBOARD_KEY_ID=bbk_xxxxxxxx
BUGBOARD_SIGNING_SECRET=bb_sec_xxxxxxxx
```

## 2. Register the client as a provider

```ts
// src/bugboard/bugboard.module.ts
import { Global, Module } from '@nestjs/common';
import { createClient, type BugBoardClient } from 'bugboard';

export const BUGBOARD = Symbol('BUGBOARD');

@Global()
@Module({
    providers: [
        {
            provide: BUGBOARD,
            useFactory: (): BugBoardClient =>
                createClient({
                    keyId: process.env.BUGBOARD_KEY_ID,
                    signingSecret: process.env.BUGBOARD_SIGNING_SECRET,
                    environment: process.env.NODE_ENV,
                }),
        },
    ],
    exports: [BUGBOARD],
})
export class BugBoardModule {}
```

`@Global()` means you import the module once in `AppModule` and inject `BUGBOARD` anywhere.
Because the provider is a singleton, there is exactly one queue and one shutdown hook.

## 3. Report from an exception filter

The natural reporting point:

```ts
// src/bugboard/bugboard-exception.filter.ts
import { ArgumentsHost, Catch, HttpException, Inject } from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';
import type { BugBoardClient } from 'bugboard';
import { BUGBOARD } from './bugboard.module';

@Catch()
export class BugBoardExceptionFilter extends BaseExceptionFilter {
    constructor(@Inject(BUGBOARD) private readonly bugboard: BugBoardClient) {
        super();
    }

    catch(exception: unknown, host: ArgumentsHost) {
        const status =
            exception instanceof HttpException ? exception.getStatus() : 500;

        if (status >= 500) {
            const req = host.switchToHttp().getRequest();
            this.bugboard.criticalHigh(
                `Unhandled error: ${req.method} ${req.route?.path}`,
                exception,
                ['nestjs'],
            );
        }

        super.catch(exception, host);
    }
}
```

Two things to keep: the `status >= 500` guard (4xx are client mistakes, not your bugs) and
`req.route?.path` — the route **pattern**, so repeated failures deduplicate into one card
instead of one per id.

Injecting anywhere else works the same way:

```ts
@Injectable()
export class CheckoutService {
    constructor(@Inject(BUGBOARD) private readonly bugboard: BugBoardClient) {}

    async charge(order: Order) {
        try {
            await this.payments.charge(order);
        } catch (err) {
            this.bugboard.criticalHigh('Payment capture failed', err, [
                'payments',
            ]);
            throw err;
        }
    }
}
```

## 4. Flush on shutdown

```ts
import { Inject, Injectable, type OnApplicationShutdown } from '@nestjs/common';

@Injectable()
export class BugBoardShutdown implements OnApplicationShutdown {
    constructor(@Inject(BUGBOARD) private readonly bugboard: BugBoardClient) {}

    async onApplicationShutdown(): Promise<void> {
        await this.bugboard.flush();
    }
}
```

This requires `app.enableShutdownHooks()` in `main.ts`. It is what delivers reports across a
`SIGTERM` — which a containerized service receives on **every deploy**, and where the SDK's
automatic `beforeExit` hook does not fire.

If you deploy NestJS to a serverless target instead of a long-running container, flushing is
mandatory on every invocation — see **[Serverless & edge](/docs/frameworks/serverless)**.

## Testing

Because the client is injected behind a token, tests override it with a spy — no module
mocking:

```ts
const module = await Test.createTestingModule({
    providers: [CheckoutService, { provide: BUGBOARD, useValue: fakeClient() }],
}).compile();

function fakeClient() {
    return {
        critical: vi.fn(),
        criticalHigh: vi.fn(),
        major: vi.fn(),
        flush: vi.fn().mockResolvedValue(undefined),
    } as unknown as BugBoardClient;
}
```

```ts
expect(bugboard.criticalHigh).toHaveBeenCalledWith(
    'Payment capture failed',
    expect.any(Error),
    ['payments'],
);
```

This is the payoff for injecting the client rather than importing a shared module deep in the
call stack.

## Next steps

- **[JavaScript SDK](/docs/sdks/javascript)** — the base guide and the full method surface.
- **[Node: Express, Fastify, Koa](/docs/frameworks/node)** — process-level handlers and
  `SIGTERM` flushing, which apply to NestJS too.
- **[API Reference](/docs/api-reference)** — every config option and the HTTP contract.
