# Connect a Fastify app

> Add ApiTraffic to a Fastify 4 or 5 API: plugin registration and encapsulation, credentials, hooks, what is captured, firewall, APM and troubleshooting.

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

Add ApiTraffic to a Fastify application to capture every request and response, the calls your app makes to other APIs, and, if you turn them on, firewall decisions and performance spans. Setup is one plugin, registered before your routes.

## Before you begin

- **Fastify 4 or 5**, on **Node.js 20 or later**.
- **An ingest token and a bucket ID.** The setup page in ApiTraffic creates the token for you if you are an owner or admin; see [API tokens](https://www.apitraffic.io/docs/account/api-tokens).
- **Outbound HTTPS** from your servers to `ingest.apitraffic.io` and `api.apitraffic.io`.

## Install the package

```bash npm
npm install @apitraffic/node
```

```bash pnpm
pnpm add @apitraffic/node
```

```bash yarn
yarn add @apitraffic/node
```

```bash bun
bun add @apitraffic/node
```

`@apitraffic/node` includes the Fastify adapter, the firewall and the APM bridge. To install only request capture, see [Use the standalone package](#use-the-standalone-package).

## Register the plugin

Register `apitraffic.fastify()` **before your routes and route plugins**, ideally as the first plugin:

```javascript server.js
const fastify = require('fastify')({ logger: true });
const apitraffic = require('@apitraffic/node');

// First, so its hooks run ahead of other plugins'. No `await` needed:
// Fastify loads plugins in order before listen(), and CommonJS has no top-level await.
fastify.register(apitraffic.fastify());

fastify.register(require('@fastify/cors'));
fastify.register(require('./routes/orders'), { prefix: '/v1' });

fastify.listen({ port: 3000 });
```

### Encapsulation

The plugin is wrapped with `fastify-plugin`, so its hooks are not encapsulated: registered at the root, they apply to the whole server, including routes in child plugins registered after it. Register it once, at the root. Registered inside a child plugin, it would only cover that plugin's scope.

**Why first?** ApiTraffic adds a hook to each route as the route is registered, so register it before your routes. Its `onSend` hook also runs in registration order: registered first, it sees the reply before your own `onSend` hooks compress or sign it.

### ES modules and TypeScript

```typescript server.ts
import Fastify from 'fastify';
import * as apitraffic from '@apitraffic/node';

const fastify = Fastify({ logger: true });
await fastify.register(apitraffic.fastify());
```

In an ES module, top-level `await` is allowed and optional. `import apitraffic from '@apitraffic/node'` also works. Options are typed by `ApiTrafficOptions`.

## Provide your credentials

With no options, the plugin reads `API_TRAFFIC_TOKEN` and `API_TRAFFIC_BUCKET` from the environment:

```bash .env
API_TRAFFIC_TOKEN=your-ingest-token
API_TRAFFIC_BUCKET=your-bucket-id
```

Node does not read `.env` by itself: start with `node --env-file=.env server.js`, or make `require('dotenv/config');` the first line of your entry file. You can also pass `{ token, bucket }` to `apitraffic.fastify()`; an option you pass wins over the environment. All options are in [SDK configuration](https://www.apitraffic.io/docs/integrations/configuration).

## Verify the connection

Restart the app, then call a route. The setup page in ApiTraffic shows **SDK connected - waiting for a request** once the SDK connects and **Traffic is flowing** when the request arrives. If nothing happens, set `API_TRAFFIC_DEBUG=true` and restart to see what the SDK is doing.

## What is captured

Each request is reported as the client received it, captured when the reply is sent - after its body is written, before any `onResponse` hook - so capture adds no latency for the client.

- **Request body:** `request.body` as your app has it when the reply is sent, including changes made by your hooks and handler. Changes made after `reply.send()` (or after an async handler returns) are not reported, and neither are changes in `onResponse` hooks. When an async `onSend` hook (such as `@fastify/compress`) delays the write, the request is captured at `reply.send()`.
- **Response body:** the payload as it was handed to the raw response, after your response schema, serializers and `onSend` hooks. A field an `onSend` hook removes is reported removed. For a stream, or a body an `onSend` hook compressed, it is the payload as it reached ApiTraffic's own `onSend` hook - another reason to register ApiTraffic first. Streamed bodies such as Server-Sent Events are captured from the bytes sent, up to `maxBodyBytes`.
- **Status and timing** are recorded when the response finishes. A client that disconnects first is reported with status `499`, unless it had already received the whole body.
- **Route:** the route Fastify matched, with any prefix, so `/:orderId` registered with `{ prefix: '/v1/orders' }` is grouped as `/v1/orders/{orderId}`. Wildcard routes (`*`) and unmatched requests are grouped by ApiTraffic's own endpoint detection. See [Endpoint handling](https://www.apitraffic.io/docs/endpoint-handling).

Bodies that cannot be captured safely are replaced with a `[body not captured: …]` placeholder and the rest of the request is still sent.

## Error handlers

The response your error handler sends - from `setErrorHandler` or Fastify's default - is what is reported, with its status. To add context:

```javascript
fastify.setErrorHandler((error, request, reply) => {
  apitraffic.tag('errorType', error.name);
  apitraffic.trace(`failed: ${error.message}`);
  reply.status(error.statusCode ?? 500).send({ error: 'Internal Server Error' });
});
```

## Tag and trace requests

```javascript
fastify.get('/v1/orders/:orderId', async (request) => {
  apitraffic.tag('customerId', String(request.user.id));
  return apitraffic.span('db.query', 'load order', () => orders.find(request.params.orderId));
});
```

Keys and values follow the [tag rules](https://www.apitraffic.io/docs/tags#the-tag-rules): numbers and booleans are stored as text, long values are cut to 255 characters, and a tag that breaks a rule is dropped and counted in [Settings → Dimensions](https://www.apitraffic.io/docs/tags#settings--dimensions), never thrown into your handler. Limits and the full API are in [Per-request APIs](https://www.apitraffic.io/docs/integrations/configuration#per-request-apis).

## Outbound calls

Calls your app makes with `http`, `https`, `fetch`, axios and similar clients are captured automatically and linked to the request that made them. Turn this off with `interceptOutbound: false`. See [Outbound requests](https://www.apitraffic.io/docs/outbound-requests).

## Firewall and APM on Fastify

**Firewall.** Already mounted by `apitraffic.fastify()` as a `preHandler` hook, so body rules see the parsed body. Turn it on by creating a policy under **Firewall (WAF)** in ApiTraffic. A blocked request gets the status and body your policy defines and never reaches your handler. See [Set up the firewall](https://www.apitraffic.io/docs/waf/setup).

**APM.** Install the OpenTelemetry packages and start the app with the preload:

```bash
npm install @opentelemetry/api @opentelemetry/sdk-trace-node @opentelemetry/instrumentation @opentelemetry/auto-instrumentations-node
node -r @apitraffic/node/register ./server.js
```

See [Set up APM](https://www.apitraffic.io/docs/apm/setup) for ES modules and apps that already run OpenTelemetry.

## Use the standalone package

`@apitraffic/fastify` is request capture only, with the smallest footprint. Register its `middleware` export, not the module itself - the module is an object of helpers, and Fastify refuses to start when given something that is not a plugin:

```bash
npm install @apitraffic/fastify
```

```javascript server.js
const fastify = require('fastify')();
const apiTraffic = require('@apitraffic/fastify');

fastify.register(apiTraffic.middleware);   // reads API_TRAFFIC_TOKEN and API_TRAFFIC_BUCKET
// or: fastify.register(apiTraffic.middleware, { token, bucket });
```

It exports the same helpers (`tag`, `trace`, `span`, `startSpan`, `flush`, `shutdown`, `embedToken`). `embedToken()` mints with an Administrator API token set as `API_TRAFFIC_EMBED_TOKEN`, not the ingest token; see [Embed tokens](https://www.apitraffic.io/docs/integrations/node#embed-tokens). To add the firewall, install `@apitraffic/waf` and add `fastify.addHook('preHandler', waf.fastify())`; see [Set up the firewall](https://www.apitraffic.io/docs/waf/setup). For APM, see [Set up APM](https://www.apitraffic.io/docs/apm/setup).

## Shut down

For a script, worker or test that must exit, call `await apitraffic.shutdown()` when you are done; the realtime connection otherwise keeps the process alive. In a server, call it in an `onClose` hook or your `SIGTERM` handler. See [Shutting down](https://www.apitraffic.io/docs/reference/shutdown).

## Troubleshooting

**Fastify fails to start with a plugin error**

With the standalone package, register `apiTraffic.middleware`, not `apiTraffic`. With `@apitraffic/node`, call the factory: `apitraffic.fastify()`, with parentheses.

**The response body is compressed or a placeholder**

An `onSend` hook (compression, signing) ran before ApiTraffic's. Register ApiTraffic before plugins that add `onSend` hooks, such as `@fastify/compress`.

**Requests are not captured**

- Restart the process after setting `API_TRAFFIC_TOKEN` and `API_TRAFFIC_BUCKET`, and check that they reach it.
- An `onRequest` hook registered before ApiTraffic that replies on its own ends the request before ApiTraffic sees it.
- An [exclusion](https://www.apitraffic.io/docs/data/exclusions) may match the request.

**Outbound calls are missing**

See [Outbound requests](https://www.apitraffic.io/docs/outbound-requests#when-an-outbound-call-is-not-captured) for the cases that are not captured.

## Related

- [SDK configuration](https://www.apitraffic.io/docs/integrations/configuration):
    Every option, environment variable and per-request API.

- [Node SDK](https://www.apitraffic.io/docs/integrations/node):
    Features, embed tokens, delivery health and failure behavior.

- [Request log](https://www.apitraffic.io/docs/traffic/request-log):
    See and search the requests you just captured.

- [Set up APM](https://www.apitraffic.io/docs/apm/setup):
    Database, cache and queue spans inside each request.
