# Install the Node SDK

> Add @apitraffic/node to an Express, Fastify, Koa or hapi app: one package with request capture, the firewall and APM, enabled from your account.

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

`@apitraffic/node` is the whole ApiTraffic SDK behind one install: the framework adapter for Express, Fastify, Koa or hapi, the [web application firewall](https://www.apitraffic.io/docs/waf/overview) and the [APM bridge](https://www.apitraffic.io/docs/apm/overview), mounted with a single call. Use it for any Node.js API on those frameworks; it is what the setup page in ApiTraffic installs.

Nothing is switched on in code. What runs - the firewall, redaction, exclusions, span capture and server health - is decided in your ApiTraffic account and pushed to every connected server, so turning a feature on is a dashboard change, not a deploy. The one exception is APM, which needs a startup flag (see [Performance monitoring](#performance-monitoring)).

## Before you begin

- **Node.js 20 or later.** Version 2.x of the SDK requires Node 20; the 1.x packages still run on Node 18.
- **A supported framework**, installed in your app: Express 4.17+ or 5, Fastify 4 or 5, Koa 2.13+ or 3, or `@hapi/hapi` 21. The SDK does not install your framework for you.
- **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
```

## Register ApiTraffic in your app

Register ApiTraffic **before your routes**. Middleware added after a route never sees the requests that route answers.

**Express**

```javascript server.js
const express = require('express');
const apitraffic = require('@apitraffic/node');

const app = express();

app.use(express.json());          // body parsers first, so the firewall can inspect bodies
app.use(apitraffic.express());    // then ApiTraffic, before your routes

app.get('/health', (req, res) => res.json({ ok: true }));

app.listen(3000);
```

`apitraffic.express()` returns an array of middleware, which `app.use()` accepts directly. See [Express](https://www.apitraffic.io/docs/integrations/express).

**Fastify**

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

// No `await` needed: Fastify loads plugins before listen(),
// and CommonJS has no top-level await.
fastify.register(apitraffic.fastify());

fastify.get('/health', async () => ({ ok: true }));

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

The plugin applies to the whole server, not only to the scope you register it in. See [Fastify](https://www.apitraffic.io/docs/integrations/fastify).

**Koa**

```javascript server.js
const Koa = require('koa');
const { bodyParser } = require('@koa/bodyparser');
const apitraffic = require('@apitraffic/node');

const app = new Koa();

app.use(bodyParser());            // body parser first, so the firewall can inspect bodies
app.use(apitraffic.koa());        // then ApiTraffic, before your router

app.listen(3000);
```

See [Koa](https://www.apitraffic.io/docs/integrations/koa).

**hapi**

```javascript server.js
const Hapi = require('@hapi/hapi');
const apitraffic = require('@apitraffic/node');

const init = async () => {
  const server = Hapi.server({ port: 3000 });

  // Register before server.start(), inside an async function.
  await server.register(apitraffic.hapi());

  server.route({ method: 'GET', path: '/health', handler: () => ({ ok: true }) });
  await server.start();
};

init();
```

See [hapi](https://www.apitraffic.io/docs/integrations/hapi).

**NestJS**

Use [`@apitraffic/nestjs`](https://www.apitraffic.io/docs/integrations/nestjs), this package as a Nest module: import it once, in your root module, on the Express or the Fastify platform.

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

@Module({ imports: [ApiTrafficModule.forRoot()] })
export class AppModule {}
```

**Meteor**

Use [`@apitraffic/meteor`](https://www.apitraffic.io/docs/integrations/meteor), this package mounted on Meteor's `WebApp`: call it once, from your server's main module.

```javascript server/main.js
import { mount } from '@apitraffic/meteor';

mount();
```

**LoopBack**

Use [`@apitraffic/loopback`](https://www.apitraffic.io/docs/integrations/loopback), this package as a LoopBack 4 component: register it in your application's constructor.

```typescript src/application.ts
import { ApiTrafficComponent } from '@apitraffic/loopback';

// in your RestApplication's constructor
this.component(ApiTrafficComponent);
```

**AdonisJS**

Use [`@apitraffic/adonisjs`](https://www.apitraffic.io/docs/integrations/adonisjs), this package as an AdonisJS provider and middleware: one command installs it and registers them.

```bash
node ace add @apitraffic/adonisjs
```

**Sails**

Use [`@apitraffic/sails`](https://www.apitraffic.io/docs/integrations/sails), this package as a Sails hook: install it and Sails loads it when it lifts. Options go in `config/apitraffic.js`.

```javascript config/apitraffic.js
module.exports.apitraffic = {
  token: process.env.API_TRAFFIC_TOKEN,
  bucket: process.env.API_TRAFFIC_BUCKET,
};
```

### ES modules and TypeScript

The package is CommonJS and works from ES modules and TypeScript as well. From an ES module, `import apitraffic from '@apitraffic/node'` and named imports such as `import { express } from '@apitraffic/node'` both work. In TypeScript:

```typescript server.ts
import express from 'express';
import * as apitraffic from '@apitraffic/node';
import type { ApiTrafficOptions } from '@apitraffic/node';

const options: ApiTrafficOptions = { debug: process.env.NODE_ENV !== 'production' };

const app = express();
app.use(express.json());
app.use(apitraffic.express(options));
```

`ApiTrafficOptions` types every option, including `waf`. A value of the wrong type, or a misspelled key in an object literal, is a compile error. Unknown keys are still passed through at runtime.

## Provide your credentials

The SDK needs two values: your ingest token and your bucket ID. Every framework call reads them from the environment when you pass no options, which keeps the token out of source control:

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

Node does not read `.env` files on its own. Start your app with `node --env-file=.env server.js` (Node 20.6 or later), or install `dotenv` and make `require('dotenv/config');` the first line of your entry file.

You can pass them in code instead. An option you pass always wins over the environment variable of the same setting:

```javascript
app.use(apitraffic.express({
  token: process.env.API_TRAFFIC_TOKEN,
  bucket: process.env.API_TRAFFIC_BUCKET
}));
```

> **Warning:** Treat the ingest token as a secret. It can't read captured traffic or mint [embed tokens](https://www.apitraffic.io/docs/integrations/embed), but whoever holds it can send traffic into your bucket and read the SDK's settings, which include your redaction rules and firewall policy. Keep it in a secret store, never in client-side code, images or a repository, and rotate it if it may have been exposed.

Every option and environment variable is listed in [SDK configuration](https://www.apitraffic.io/docs/integrations/configuration).

## Verify the connection

**Restart your app**

The token and bucket are read at startup, so restart the process after setting them.

**Make a request your app serves**

Call any route registered after ApiTraffic. A health check answered before ApiTraffic is not captured.

**Check ApiTraffic**

The setup page changes from **Listening for your application** to **SDK connected - waiting for a request** as soon as the SDK connects, and to **Traffic is flowing** when the first request arrives. Choose **View your traffic** to open the request log. The server icon in the top bar shows how many SDK processes are connected to the bucket.

*The setup page confirms when the first request arrives.*

![Connection status card reading Traffic is flowing, with View your traffic and Open the dashboard buttons](https://www.apitraffic.io/docs/images/integrations/setup-connection-status.png)

If nothing arrives, set `API_TRAFFIC_DEBUG=true` and restart: the SDK explains what it is doing on the console. See [Troubleshooting](#troubleshooting).

## Turn on features

Everything below is already mounted by the call you added. You turn each one on in ApiTraffic, and running servers pick up the change within seconds.

| Feature | Where to turn it on | Needs a code change |
|---|---|---|
| [Redaction](https://www.apitraffic.io/docs/data/redactions) and [exclusions](https://www.apitraffic.io/docs/data/exclusions) | **Account** → **Redactions** and **Account** → **Exclusions** | No |
| [Firewall](https://www.apitraffic.io/docs/waf/setup) | **Account** → **Firewall (WAF)** | No |
| [Server health (host telemetry)](https://www.apitraffic.io/docs/integrations/servers) | **Bucket Settings** → **Host Telemetry** | No |
| [APM capture switches](https://www.apitraffic.io/docs/apm/capture-settings) | **Bucket Settings** → **APM Capture** | No |
| [Performance monitoring (APM spans)](https://www.apitraffic.io/docs/apm/setup) | Your start command | Yes: a preload flag, once |

### Firewall

The firewall is mounted but not loaded until your account has a policy that is switched on, so an app that never uses it pays one check per request and nothing more. Create a policy under **Firewall (WAF)**, start it in **Monitor** mode to see what it would block, then switch to enforcing. See [Set up the firewall](https://www.apitraffic.io/docs/waf/setup).

Mount your body parsers **before** ApiTraffic (Express and Koa) so the firewall's body rules can inspect the parsed body. On Fastify and hapi the firewall runs after the body is parsed automatically.

Two local switches override the dashboard. Both turn the firewall **off**; neither can turn it on:

```javascript
app.use(apitraffic.express({ waf: false }));   // refuse the firewall in code
```

```bash
API_TRAFFIC_WAF_ENABLED=false                  # kill switch, no code change
```

Use `waf: false` when your change process requires that anything able to reject production traffic ships through a deploy. Use the environment variable during an incident: it works even when ApiTraffic is unreachable. To pass firewall settings such as `offlineWafMode` or an `onEvent` callback, give `waf` an object instead; see [SDK configuration](https://www.apitraffic.io/docs/integrations/configuration#firewall-options).

### Performance monitoring

APM records the database, cache, queue and outbound work inside each request. It is the one feature that needs a change to how you start your app, because OpenTelemetry instruments libraries as they are loaded and has to start before your app loads `pg`, `redis` and the rest.

**Install the OpenTelemetry packages**

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

**Preload the SDK when you start the app**

```bash
node -r @apitraffic/node/register ./server.js
```

With credentials in a `.env` file: `node --env-file=.env -r @apitraffic/node/register ./server.js`. The preload reads its configuration from `API_TRAFFIC_*` environment variables.

If you cannot change the start command, make `require('@apitraffic/node').register();` the very first line of your entry file, above every other `require`. If the OpenTelemetry packages are missing or fail to start, APM is skipped with a one-line reason on the console and your app boots normally.

The preload is for apps with no other tracing. If your app already runs OpenTelemetry, add `ApiTrafficSpanProcessor` from `@apitraffic/apm` to your own tracer provider instead; with Datadog or New Relic loaded, the preload steps aside on its own. ES-module apps start with `node --import` and a loader hook. All three cases are covered in [Set up APM](https://www.apitraffic.io/docs/apm/setup).

## Add context to requests

Call these from anywhere in your request-handling code. Outside a request they do nothing, and they never throw:

```javascript
const apitraffic = require('@apitraffic/node');

app.post('/v1/checkout', async (req, res) => {
  apitraffic.tag('customerId', String(req.user.id));      // searchable in the request log
  apitraffic.trace('applied the loyalty discount');         // a timestamped note on this request

  const order = await apitraffic.span('custom', 'price cart', () => priceCart(req.body));
  res.json(order);
});
```

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. `tag`, `trace`, `span`, `startSpan` and the rest are described, with their limits, in [SDK configuration](https://www.apitraffic.io/docs/integrations/configuration#per-request-apis).

## Embed tokens

The [embeddable request log](https://www.apitraffic.io/docs/integrations/embed) authenticates with a short-lived token that can only be minted on your server, with an **Administrator** [API token](https://www.apitraffic.io/docs/account/api-tokens). The ingest token the SDK reports with can't mint embed tokens. Set the Administrator token as `API_TRAFFIC_EMBED_TOKEN` (or pass it as `token`); the host, bucket and account come from the SDK you already configured:

```javascript
app.get('/api/embed-token', async (req, res) => {
  const { token, expiresAt } = await apitraffic.embedToken({
    filters: { tags: { customerId: String(req.user.id) } },   // escaped for you
    ttl: 900
  });
  res.json({ token, expiresAt });
});
```

`embedToken()` uses `token` first, then `API_TRAFFIC_EMBED_TOKEN`, then the SDK's own token. If it falls back to the SDK's ingest token, the API answers `403` and the error tells you to set `API_TRAFFIC_EMBED_TOKEN`.

> **Warning:** `API_TRAFFIC_EMBED_TOKEN` is a read credential: an Administrator token can mint embed tokens for any bucket in the account, with any filters or none. Keep it in a secret store, never in client-side code, images or a repository, and rotate it immediately if it may have been exposed.

Unlike the rest of the SDK, `embedToken()` rejects on failure, so a problem is a 500 on your route instead of a broken embed. All options are on the [embed page](https://www.apitraffic.io/docs/integrations/embed).

## Check delivery from your own monitoring

`apitraffic.stats()` returns live delivery counters: payloads in flight and queued, delivered, recovered after a retry, dropped by cause, and whether the circuit breaker is open. Use it to assert that telemetry is leaving the process:

```javascript
console.log(apitraffic.stats());
// { pending, queued, delivered, recovered, dropped, droppedRejected, breakerOpen, ... }
```

## Shut down cleanly

The SDK keeps a realtime connection open for live configuration. That is right for a server and wrong for a script, worker or test run that should exit. Call `shutdown()` from your own shutdown path; the SDK installs no signal handlers:

```javascript
process.once('SIGTERM', async () => {
  await apitraffic.shutdown({ flushTimeoutMs: 2000 });
  server.close();
});
```

`shutdown()` also stops APM tracing that `register()` started. To deliver what was captured without stopping, for example before a serverless function is frozen, use `await apitraffic.flush({ timeoutMs: 1000 })`. See [Shutting down](https://www.apitraffic.io/docs/reference/shutdown).

## How the SDK fails

The SDK is built so that it is never the reason your application fails:

- A missing or rejected token disables telemetry and logs one clear message. Your application still boots and serves.
- If ApiTraffic is unreachable, requests are served normally; a short outage is absorbed by a small retry queue, and a longer one sheds data rather than using more memory. See [If ApiTraffic is unreachable](https://www.apitraffic.io/docs/reference/unreachable).
- If the realtime connection is blocked, you lose live configuration updates and nothing else.
- Nothing is sent before your account's redaction rules have loaded.
- `tag()`, `trace()` and the span helpers never throw into your handlers.

Pass `strict: true` if you would rather a misconfigured SDK stopped the process at startup than ran unmonitored.

## Use the individual packages instead

`@apitraffic/node` delegates to packages that are also published on their own: `@apitraffic/express`, `@apitraffic/fastify`, `@apitraffic/koa`, `@apitraffic/hapi`, `@apitraffic/waf` and `@apitraffic/apm`. Each framework page shows the standalone setup. Choose them for the smallest possible install; you then mount the firewall and APM yourself if you need them. Mounting both a standalone package and `@apitraffic/node` is safe: each request is still reported once.

## Troubleshooting

**The setup page never leaves Listening for your application**

The SDK has not connected. Check, in order:

- The process was restarted after you set `API_TRAFFIC_TOKEN` and `API_TRAFFIC_BUCKET`.
- The variables actually reach the process. Node does not load `.env` by itself.
- The console. Without debug mode the SDK still prints `ApiTraffic: An APITraffic API Token must be set…` for a missing token, and `ApiTraffic: token rejected (HTTP 401)…` or `(HTTP 403)` for a token that is invalid or cannot access the bucket.
- Outbound HTTPS to `api.apitraffic.io` and `ingest.apitraffic.io` is allowed from where the app runs.

**SDK connected, but no requests appear**

The SDK is talking to ApiTraffic, so the token and bucket are right. Usually:

- ApiTraffic is registered after the route you are calling. Move it above your routes.
- The request is answered by middleware mounted before ApiTraffic, such as a health check or a static file handler.
- An [exclusion](https://www.apitraffic.io/docs/data/exclusions) matches the request.

**APM shows no spans**

APM needs the preload (`node -r @apitraffic/node/register`) or `register()` as the first line of your entry file, and the OpenTelemetry packages installed. If `register()` ran after your database client was loaded, that client produces no spans; the console says which modules were already loaded. See [Set up APM](https://www.apitraffic.io/docs/apm/setup).

**The firewall does not inspect request bodies**

On Express and Koa, mount your body parser before `apitraffic.express()` or `apitraffic.koa()`. If a router has its own body parser mounted after ApiTraffic, the firewall evaluates the request again once the body is parsed, without counting it twice against rate limits.

**My script or test run never exits**

The realtime connection keeps the event loop alive. Call `await apitraffic.shutdown()` when the work is done.

## Related

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

- [Outbound requests](https://www.apitraffic.io/docs/outbound-requests):
    How calls your app makes to other APIs are captured.

- [Set up the firewall](https://www.apitraffic.io/docs/waf/setup):
    Create a policy, monitor it, then enforce it.

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