# Block secrets in a NestJS app

URL: https://tailrace.dev/docs/guides/block-secrets-in-nestjs

> Register TailraceModule so OpenAI-compatible routes respect Tailrace policy on Nest + Express.



Use `@tailrace/nestjs` to enforce data policy on OpenAI-compatible chat routes in NestJS - in-process, with no proxy.

## Prerequisites [#prerequisites]

* [Quickstart](/docs/get-started/quickstart) or equivalent [`createTailrace`](/docs/get-started/quickstart) setup
* NestJS (`@nestjs/common` / `@nestjs/core` `>=10`) with the **Express** adapter (primary CI target)
* `reflect-metadata` loaded before Nest bootstraps

## Step 1: Install [#step-1-install]

```bash
pnpm add @tailrace/core @tailrace/nestjs @nestjs/common @nestjs/core reflect-metadata
```

## Step 2: Import TailraceModule [#step-2-import-tailracemodule]

```ts
import { createTailrace } from "@tailrace/core";
import { TailraceModule } from "@tailrace/nestjs";
import { Module } from "@nestjs/common";

@Module({
  imports: [
    TailraceModule.forRoot({
      tailrace: createTailrace(),
      forRoutes: ["v1/*path"],
      agent: (req) => String(req.headers["x-agent-id"] ?? "default"),
      workflowId: (req) => String(req.headers["x-workflow-id"] ?? "default"),
    }),
  ],
})
export class AppModule {}
```

Nest 11 route globs use named splats (`v1/*path`, not `v1*`). Prefer scoping `forRoutes` to your
chat proxy paths instead of the default all-routes matcher.

## How it works [#how-it-works]

`TailraceModule.forRoot` registers [`TailraceMiddleware`](/docs/reference/nestjs) on the routes you
pass. The middleware applies the same openai-compat contract as Hono and Express:

| Location                | Tailrace boundary                                   | Direction            |
| ----------------------- | --------------------------------------------------- | -------------------- |
| Chat request messages   | `{ kind: "model", provider }` (`model` field as-is) | Outbound to upstream |
| JSON chat completion    | same model boundary                                 | Inbound              |
| SSE `text/event-stream` | same; carry-buffer across chunks                    | Inbound              |

When policy resolves to `block`, the middleware returns **422** with
`{ error: { type: "policy_violation", entity, rule } }` - never the raw value. That maps from
[`PolicyViolationError`](/docs/reference/errors/POLICY_VIOLATION).

Only `mode: "openai-compatible"` is supported. Shared helpers live in [`@tailrace/http`](/docs/reference/http).

## Common variations [#common-variations]

### Nest + Fastify [#nest--fastify]

For Nest's Fastify adapter, register [`@tailrace/fastify`](/docs/guides/block-secrets-in-fastify)
on the underlying Fastify instance rather than relying on Express-shaped Nest middleware.

### Scope agent and workflow ID [#scope-agent-and-workflow-id]

Pass `agent` and `workflowId` as functions of the Express `Request` (or a static string for
`workflowId`). Defaults are `"default"` when omitted.

### Verify with a synthetic key [#verify-with-a-synthetic-key]

Send a chat completion body whose messages include `sk_test_…FAKE`. Expect **422**. An
`example.com` email should tokenize under the default policy.

## See also [#see-also]

* [NestJS integration](/docs/integrations/nestjs)
* [NestJS reference](/docs/reference/nestjs)
* [Block secrets in a Fastify app](/docs/guides/block-secrets-in-fastify)
* [Boundaries](/docs/concepts/boundaries)
* Spec: [`docs/integrations.md`](https://github.com/TailraceHQ/tailrace/blob/main/docs/integrations.md) §12
