# NestJS

> Monitor a NestJS API with the in-process SDK, using a global middleware for requests and your auth service for sign-in events.

Source: https://docs.agentbeam.com/cookbooks/frameworks/nestjs

NestJS runs on Express (or Fastify), so it uses the SDK and reports after the response.

## What you'll have

- Every request checked with its status.
- Sign-ins, admin grants, new keys and LLM spend reported from your services.
- New outbound hosts noticed, and dependencies checked in CI.

## Before you start

Do steps 1 and 2 of the [general cookbook](https://docs.agentbeam.com/cookbooks/app-monitoring#step-1-turn-it-on-and-create-the-apps-token) first: App monitoring is on, the app is added, and its `beam_app_` token is in your secret store as `BEAM_APP_TOKEN`. Everything below needs `@agent-beam/beam` 0.2.32 or newer.

## Install and provide one monitor

```sh
npm install @agent-beam/beam
```

```ts
// beam.ts
import { createAppMonitor } from "@agent-beam/beam/monitor";

export const beam = createAppMonitor({ stack: "nestjs", watchOutbound: true });
```

## Report each request after the response

```ts
// beam.middleware.ts
import { Injectable, NestMiddleware } from "@nestjs/common";
import type { Request, Response, NextFunction } from "express";
import { beam } from "./beam";

@Injectable()
export class BeamMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    const started = Date.now();
    res.on("finish", () =>
      beam.request({
        method: req.method,
        url: req.originalUrl,
        ip: req.ip,
        userAgent: req.get("user-agent"),
        status: res.statusCode,
        durationMs: Date.now() - started,
        headers: req.headers,
      }),
    );
    next();
  }
}
```

```ts
// app.module.ts
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(BeamMiddleware).forRoutes("*");
  }
}
```

In `main.ts`, set `app.set("trust proxy", 1)` (Express adapter) so `req.ip` is the client behind your load balancer.

## Pause attackers

To also refuse senders the workspace [paused](https://docs.agentbeam.com/agent-cli/app-monitoring#pausing-attackers) (scanners, password guessers, LLM abusers), replace `BeamMiddleware` with the SDK's own middleware in `main.ts`. It answers a paused sender with a 429 and `Retry-After`, and reports every other request after its response, with the status:

```ts
// main.ts
const app = await NestFactory.create(AppModule);
app.set("trust proxy", 1);
app.use(beam.middleware()); // before your routes
```

Nothing is refused until an admin turns pausing on under **Settings > Pausing** (it starts in dry run) or pauses someone. With `trust proxy` set to your proxy count, `req.ip` is the address your proxy added, which Beam can pause; Beam never pauses one the client could have written into `X-Forwarded-For`. Pausing needs `@agent-beam/beam` 0.2.35.

## Report sign-ins, admins and keys

In your auth service, where you validate a user (for example a Passport `LocalStrategy.validate`):

```ts
async validate(username: string, password: string) {
  const user = await this.users.verify(username, password);
  if (!user) {
    beam.event("login.failed", { user: await this.users.idFor(username) });
    throw new UnauthorizedException();
  }
  beam.event("login.succeeded", { user: user.id });
  return user;
}
```

Add `ip` from the request where you have it. Call `beam.event("admin.granted", ...)` and `beam.event("apikey.created", ...)` in the services that change roles and keys, and `beam.usage()` after each model call.

## Prove it works

```sh
beam monitor status   # app, level, stack, usage
beam monitor test     # a test finding through your alert workflows
curl -s "https://your-app.example.com/?id=1%20UNION%20SELECT%201" -o /dev/null
```

Within a minute, the SQL injection test shows on **App monitoring**: under **Needs action** if the app answered 2xx, or counted under "needed nothing from you" if it refused it. Then sign in to a test account with a wrong password ten times and the right one once: **Sign-in succeeded after 10 failed attempts** appears as critical.
## Check dependencies on every pull request

```yaml
# .github/workflows/beam-cve.yml
name: Dependency check
on: [pull_request]
jobs:
  cve:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npx -y @agent-beam/beam cve check . --fail-on high
        env:
          BEAM_APP_TOKEN: ${{ secrets.BEAM_APP_TOKEN }}
```
## What Beam watches for on this stack

Besides every [built-in rule](https://docs.agentbeam.com/agent-cli/app-monitoring#levels-and-detection) and the [attack-in-progress signals](https://docs.agentbeam.com/agent-cli/app-monitoring#signs-an-attack-is-underway):

| Rule | Means | Severity |
| --- | --- | --- |
| Node.js package files served | `.npmrc`, `package-lock.json` or `node_modules` answered 2xx | High |
| GraphQL introspection | A `__schema` query in a GET, if you use `@nestjs/graphql` | Medium |

Attacks on other frameworks (WordPress probes, Laravel or Spring exploits, and so on) are rolled up as **Not your stack** when there's no status to say otherwise, and never alert.
## Close what Beam would catch

Beam raises these when they're found; better that there's nothing to find:

- Turn off GraphQL `introspection` and the playground in production unless they're meant to be public.
- Don't serve Swagger (`SwaggerModule.setup`) publicly in production, or put it behind auth.
- Use a global `ValidationPipe` with `whitelist: true`.
## Coverage checklist

| Feature | Done when |
| --- | --- |
| Middleware on every route | Findings show **Answered** or **Refused** |
| Stack declared | `stack: "nestjs"` |
| Sign-in events | `login.failed` and `login.succeeded` in the auth service |
| Admin, key and LLM events | Reported where they happen |
| Dependency checks | The CI job runs on pull requests |
| Alerts | Critical pages someone, high reaches a channel ([step 8](https://docs.agentbeam.com/cookbooks/app-monitoring#step-8-send-alerts-to-the-right-place)) |
| Pausing | `beam.middleware()` in `main.ts`, and **Settings > Pausing** on after a dry run |
| Routine | Someone reads **Needs action** daily and **Attack analytics** weekly |
