# Connect a hapi app

> Add ApiTraffic to a hapi 21 API: plugin registration, credentials, payloads, error responses, firewall, APM and troubleshooting.

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

Add ApiTraffic to a hapi 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 the server starts.

## Before you begin

- **`@hapi/hapi` 21**, 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 hapi 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.hapi()` with `server.register()` **before `server.start()`**. In CommonJS there is no top-level `await`, so do it inside an async function:

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

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

  await server.register(apitraffic.hapi());

  server.route({
    method: 'GET',
    path: '/v1/orders/{orderId}',
    handler: (request) => orders.find(request.params.orderId)
  });

  await server.start();
};

init();
```

The plugin works through server extensions (`onRequest`, `onPreHandler` and `onPostResponse`), so it covers every route on the server, whether the route is added before or after registration. It is registered with `once: true`: registering it twice is skipped rather than failing, and each request is reported once.

### ES modules and TypeScript

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

const server = Hapi.server({ port: 3000 });
await server.register(apitraffic.hapi());
await server.start();
```

From an ES module, `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.hapi()`; 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. Requests made with `server.inject()` in tests are captured too. If nothing happens, set `API_TRAFFIC_DEBUG=true` and restart.

## 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:** `request.payload` as your app has it when the response is sent, including changes made before responding. Changes made later, for example in an `onPostResponse` extension, are not reported. When hapi compresses the response, the request is captured when the headers are written.
- **Response body:** the payload hapi sent. A stream your handler returns is captured from the bytes sent, up to `maxBodyBytes`; file downloads served with range support are not copied. Responses with status `204` or `304` have no body.
- **Errors:** a thrown error or a Boom error is reported with the error response hapi sent and its status.
- **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 path hapi matched, such as `/v1/orders/{orderId}`. Catch-all routes such as `/{p*}` 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
server.route({
  method: 'GET',
  path: '/v1/orders/{orderId}',
  handler: async (request) => {
    apitraffic.tag('customerId', String(request.auth.credentials.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. To add context to errors, tag them in an `onPreResponse` extension, which runs inside the request. 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 hapi

**Firewall.** Already registered by `apitraffic.hapi()` as an `onPreHandler` extension, so body rules see the parsed payload. 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. If your app also registers its own copy of `@apitraffic/hapi`, the firewall uses that copy's settings. 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/hapi` is request capture only, with the smallest footprint:

```bash
npm install @apitraffic/hapi
```

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

// inside your async init function, before server.start()
await server.register({
  plugin: apiTraffic.plugin,
  options: {}          // reads API_TRAFFIC_TOKEN and API_TRAFFIC_BUCKET; or pass { token, bucket }
});
```

It exports the same helpers (`tag`, `trace`, `span`, `startSpan`, `flush`, `shutdown`, `embedToken`), plus `deliveryStats()` for delivery counters. `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 `server.ext('onPreHandler', waf.hapi())` after registering the plugin; 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 after `server.stop()` in your `SIGTERM` handler. See [Shutting down](https://www.apitraffic.io/docs/reference/shutdown).

## Troubleshooting

**Requests are not captured**

- `server.register(apitraffic.hapi())` must be awaited before `server.start()`.
- 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.

**SyntaxError: await is only valid in async functions**

CommonJS has no top-level `await`. Put `server.register()` and `server.start()` inside an async function, as in the example above.

**Test runs with server.inject() never exit**

The SDK keeps a realtime connection open. Call `await apitraffic.shutdown()` in your test teardown.

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