# Connect an Express app

> Add ApiTraffic to an Express 4.17+ or 5 API: install, middleware order, credentials, what is captured, firewall, APM and troubleshooting.

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

Add ApiTraffic to an Express 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 middleware, registered before your routes.

## Before you begin

- **Express 4.17 or later, or Express 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 Express adapter, the firewall and the APM bridge. To install only request capture, see [Use the standalone package](#use-the-standalone-package).

## Register the middleware

Add `apitraffic.express()` after your body parsers and **before your routes**:

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

const app = express();

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.use(apitraffic.express());     // after body parsers, before routes

app.use(authMiddleware);
app.use('/v1', apiRouter);

app.listen(3000);
```

The order matters for two reasons:

- **Before your routes.** Middleware added after a route never sees the requests that route answers. Anything mounted above ApiTraffic that ends a request itself - a health check, `express.static`, a rate limiter that rejects - is not captured either.
- **After your body parsers.** The captured request body is read when the response is sent, so it is recorded wherever the parser sits. The firewall, however, inspects the body when the request passes through ApiTraffic, so body rules only see a body that has already been parsed.

`apitraffic.express()` returns an array of two middleware (request capture, then the firewall), which `app.use()` accepts as is.

### ES modules and TypeScript

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

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

From an ES module (`.mjs` or `"type": "module"`), `import apitraffic from '@apitraffic/node'` also works. Options are typed by `ApiTrafficOptions`.

## Provide your credentials

With no options, the middleware 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.express()`; 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 registered after ApiTraffic. 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. Request and response are captured when the response is committed - its first bytes handed to the socket - after those bytes are written, so capture never delays the response.

- **Request body:** `req.body` as your app has it at that moment, from `express.json()`, `express.urlencoded()`, `express.text()` or another parser, including changes your middleware made before responding. Changes made afterwards, for example enriching `req.body` for a background job, are not reported. If `compression` or `express-session` mounted after ApiTraffic holds the response back, the request is captured when your `res.json()` or `res.end()` runs instead.
- **Response body:** the value passed to `res.send()` or `res.json()`, or the whole body passed to `res.end()`. A body written in pieces with `res.write()` or piped from a stream, such as Server-Sent Events, is captured from the bytes sent, up to `maxBodyBytes`. File downloads served with range support (`res.sendFile`, `express.static`) are not copied.
- **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 Express matched, including router mount paths, so `router.get('/:orderId')` under `app.use('/v1/orders', router)` is grouped as `/v1/orders/{orderId}`. Catch-all routes (`*`), unmatched requests, and routers mounted at a path with parameters (`/users/:userId/posts`) are grouped by ApiTraffic's own endpoint detection instead. 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

When a route throws or calls `next(err)`, the response your error handler sends is what is reported, with its status. You do not need to change your error handler. To add context to failed requests, tag them there:

```javascript
app.use((err, req, res, next) => {
  apitraffic.tag('errorType', err.name);
  apitraffic.trace(`failed: ${err.message}`);
  res.status(500).json({ error: 'Internal Server Error' });
});
```

Mount the error handler after your routes as usual. Express error handlers run inside the request, so tags and traces attach to it.

## Tag and trace requests

```javascript
app.get('/v1/orders/:orderId', async (req, res) => {
  apitraffic.tag('customerId', String(req.user.id));
  const order = await apitraffic.span('db.query', 'load order', () => orders.find(req.params.orderId));
  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. 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 Express

**Firewall.** Already mounted by `apitraffic.express()`; turn it on by creating a policy under **Firewall (WAF)** in ApiTraffic. Keep your body parsers above ApiTraffic so body rules can inspect the body. 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. A blocked request gets the status and body your policy defines and never reaches your route. 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/express` is request capture only, with the smallest footprint:

```bash
npm install @apitraffic/express
```

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

const app = express();
app.use(express.json());
app.use(apiTraffic.middleware());   // reads API_TRAFFIC_TOKEN and API_TRAFFIC_BUCKET

app.use('/v1', apiRouter);
```

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 mount `waf.express()` right after `apiTraffic.middleware()`; 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 from your `SIGTERM` handler before `server.close()`. See [Shutting down](https://www.apitraffic.io/docs/reference/shutdown).

## Troubleshooting

**Requests are not captured**

- ApiTraffic must be mounted before the routes you are calling. Move `app.use(apitraffic.express())` above them.
- A request ended by middleware mounted above ApiTraffic (health checks, static files) is not captured.
- Restart the process after setting `API_TRAFFIC_TOKEN` and `API_TRAFFIC_BUCKET`, and check that they reach it.
- An [exclusion](https://www.apitraffic.io/docs/data/exclusions) may match the request.

**The request body is missing**

The body is captured from `req.body`. Without a body parser for the request's content type, `req.body` is empty. Add the matching parser (`express.json()`, `express.urlencoded()`, `express.text()`).

**Requests are grouped under the wrong endpoint**

Express reports no route for catch-all routes or for routers mounted at a path with parameters. Create an [endpoint rule](https://www.apitraffic.io/docs/data/endpoint-rules) for those paths.

**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, such as calls made before the middleware is created.

## 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 the firewall](https://www.apitraffic.io/docs/waf/setup):
    Monitor, then enforce, a policy on your Express app.
