# Connect a Koa app

> Add ApiTraffic to a Koa 2.13+ or 3 API: middleware order, body parsing, error handling, @koa/router routes, firewall, APM and troubleshooting.

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

Add ApiTraffic to a Koa 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, added before your router.

## Before you begin

- **Koa 2.13 or later, or Koa 3**, 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 Koa 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.koa()` after your body parser and **before your router**:

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

const app = new Koa();
const router = new Router({ prefix: '/v1' });

router.get('/orders/:orderId', async (ctx) => {
  ctx.body = await orders.find(ctx.params.orderId);
});

app.use(errorHandler);            // your own error middleware, if you have one
app.use(bodyParser());
app.use(apitraffic.koa());        // after the body parser, before the router
app.use(router.routes());
app.use(router.allowedMethods());

app.listen(3000);
```

Koa middleware wraps everything added after it, so the order matters:

- **Before your router.** ApiTraffic sees only requests that reach it. A middleware above it that answers on its own - a health check, `koa-static` - is not captured.
- **After your body parser.** 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 its body rules only see a body that has already been parsed.
- **Your error middleware can stay first.** ApiTraffic reports the response the client finally received, including one your error middleware wrote after catching an error.

`apitraffic.koa()` returns a single middleware that runs request capture and then the firewall.

### ES modules and TypeScript

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

const app = new Koa();
app.use(apitraffic.koa());
```

From an ES 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.koa()`; 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 response is committed and after its first bytes are written, so capture never delays the response.

- **Request body:** `ctx.request.body` as set by your body parser (`@koa/bodyparser`, `koa-body` and similar), including changes made before responding. Without a body parser there is no parsed body to capture.
- **Response body:** `ctx.body` as your middleware left it. If a compression middleware mounted above ApiTraffic (as `koa-compress` usually is) replaces the body with a compressed stream, the uncompressed value is reported. A stream body, or bytes written directly with `ctx.respond = false`, is captured from the bytes sent, up to `maxBodyBytes`. File downloads served with range support are not copied.
- **Errors:** a request whose middleware throws is reported with the status and body the client received, whether your error middleware or Koa's default error handling wrote it.
- **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:** with `@koa/router`, the route it matched, including the router prefix, so the example above is grouped as `/v1/orders/{orderId}`. With other routers, and for catch-all routes such as `(.*)`, 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.

## Tag and trace requests

```javascript
router.get('/orders/:orderId', async (ctx) => {
  apitraffic.tag('customerId', String(ctx.state.user.id));
  ctx.body = await apitraffic.span('db.query', 'load order', () => orders.find(ctx.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 Koa

**Firewall.** Already part of `apitraffic.koa()`. Turn it on by creating a policy under **Firewall (WAF)** in ApiTraffic. Keep your body parser above ApiTraffic so body rules can inspect the body; if a router has its own body parser below 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 router. 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/koa` is request capture only, with the smallest footprint:

```bash
npm install @apitraffic/koa
```

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

const app = new Koa();
app.use(apiTraffic.middleware());   // reads API_TRAFFIC_TOKEN and API_TRAFFIC_BUCKET
app.use(router.routes());
```

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 `app.use(waf.koa())` 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. See [Shutting down](https://www.apitraffic.io/docs/reference/shutdown).

## Troubleshooting

**Requests are not captured**

- `apitraffic.koa()` must be added before `router.routes()`.
- A middleware above ApiTraffic that sets a response without calling `next()` ends the request before ApiTraffic sees it.
- 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**

Koa does not parse bodies itself. Add a body parser such as `@koa/bodyparser` so `ctx.request.body` is set.

**Every request is grouped under one endpoint, or by raw path**

ApiTraffic reads the route from `@koa/router`. With another router, create [endpoint rules](https://www.apitraffic.io/docs/data/endpoint-rules) for your paths, or rely on endpoint grouping in [Endpoint handling](https://www.apitraffic.io/docs/endpoint-handling).

**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.

- [Strapi](https://www.apitraffic.io/docs/integrations/strapi):
    The Strapi plugin, built on the Koa adapter.

- [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 Koa app.
