# Connect a Sails app

> Add ApiTraffic to a Sails API as a hook: install, credentials, what is recorded, firewall, APM, tagging, tracing and troubleshooting.

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

`@apitraffic/sails` is a Sails hook. Install it and every request your application serves is
recorded and checked by the ApiTraffic firewall: Sails loads installable hooks from your
dependencies when it lifts, so there is no code to add.

It is [`@apitraffic/node`](https://www.apitraffic.io/docs/integrations/node), the one-install SDK, wired into Sails: the same
capture, the same firewall and APM bridge, the same helpers, all enabled from your ApiTraffic
account rather than in code.

## Installation

**npm**

```bash
npm install @apitraffic/sails
```

**yarn**

```bash
yarn add @apitraffic/sails
```

**pnpm**

```bash
pnpm add @apitraffic/sails
```

> **Note:** **Node 20+ and Sails 1.x required.** Sails only loads a hook that is listed in your app's
> `package.json` dependencies, which `npm install` does for you.

## Basic Setup

Set `API_TRAFFIC_TOKEN` and `API_TRAFFIC_BUCKET` in the environment and lift as usual. To configure
the hook in code instead, add `config/apitraffic.js`:

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

Like any Sails config, it can be set per environment (`config/env/production.js`) or in
`config/local.js`.

## What is recorded

Every request, including the ones an action never sees. The hook adds an `apitraffic` entry to the
front of `sails.config.http.middleware.order`, ahead of the cookie parser, the session, the body
parser and the router, so these are all recorded, with their final status and whatever your code
tagged on the way:

- a request a **policy** rejects (`401`, `403`),
- a request your **HTTP middleware** answers itself,
- a request **no route** matches (`404`),
- a request whose action **throws**, as the response `res.serverError()` sent.

The route Sails matched is sent with each request, so `'GET /users/:id'` in `config/routes.js` is the
endpoint `/users/{id}`. See [Endpoint Handling](https://www.apitraffic.io/docs/endpoint-handling#framework-routes).

Sails serves HTTP through Express, and requests are captured by `@apitraffic/express`: exactly what
the [Express](https://www.apitraffic.io/docs/integrations/express) page describes under "What is captured" holds here too.
Socket.io requests to your actions (`io.socket.get()`) do not go through Sails' HTTP middleware:
they are neither recorded nor checked by the firewall.

### Placing it yourself

The hook keeps an `apitraffic` (or `apitrafficFirewall`) entry you list in `order` where you put
it, for middleware that must run first, such as a health check you do not want recorded:

```javascript
// config/http.js
module.exports.http = {
  middleware: {
    order: [
      'healthCheck',
      'apitraffic',
      'cookieParser',
      'session',
      'bodyParser',
      'apitrafficFirewall',
      'compress',
      'poweredBy',
      'router',
      'www',
      'favicon',
    ],
    healthCheck: (req, res, next) => (req.path === '/health' ? res.status(200).send('ok') : next()),
  },
};
```

### ApiTraffic already in `config/http.js`

An application that already mounts ApiTraffic middleware itself as `apitraffic` in
`config/http.js` (`@apitraffic/express`'s `middleware()`, say) keeps it after installing this
package: the hook sees that `apitraffic` is the application's own, logs that it does not set
ApiTraffic up, and leaves it alone, along with any `apitrafficFirewall` entry, which then does
nothing. To switch to the hook, delete the `apitraffic` function from `config/http.js` and move
its options to `config/apitraffic.js`; the entries in `order` can stay where they are.

## Tagging, tracing and spans

The helpers work anywhere a request is being handled: actions, helpers, policies and HTTP
middleware.

```javascript
// api/policies/is-account.js
const { tag } = require('@apitraffic/sails');

module.exports = async function (req, res, proceed) {
  // Searchable on this request, whether or not the policy lets it through.
  tag('accountId', req.headers['x-account-id']);
  if (!req.headers['x-account-id']) return res.forbidden();
  return proceed();
};
```

```javascript
// api/controllers/user/find.js
const { span, trace } = require('@apitraffic/sails');

module.exports = async function find(req, res) {
  trace(`Loading user ${req.param('id')}`);
  const user = await span('db.query', 'load user', () => User.findOne({ id: req.param('id') }));
  return res.json(user);
};
```

Outside a request (a script, a bootstrap function, a scheduled job) the helpers do nothing and
never throw.

`stats()` reports the SDK's delivery health (delivered, queued, dropped), for a health endpoint or
a log line.

## Shutdown

`sails.lower()`, which Sails also runs on Ctrl-C and SIGTERM, delivers what was captured before
Sails closes the server, after your own `beforeShutdown`, and releases the SDK's timers and
connections once the last application using the hook has lowered.

On a signal Sails exits the moment its server has closed, so a request still being served when
lowering began is recorded only if the process stays up long enough to deliver it. To let them
finish first, stop sending traffic to the instance (drain it at the load balancer) before you
signal it.

## Firewall

The ApiTraffic firewall is mounted by the hook, and whether it runs is decided in your ApiTraffic
account: nothing is loaded until you switch a policy on, and a policy change reaches every server
without a deploy. It runs as the `apitrafficFirewall` entry right after `bodyParser`, so body rules
see the parsed body, and ahead of your policies and actions, so a request it blocks never reaches
your code. Blocked requests are recorded with the policy's status and tagged `waf.*`. It guards
HTTP requests only: Socket.io requests reach your policies and actions without passing through it. Without a
`bodyParser` in `order`, it goes just ahead of the `router`.

To refuse it locally whatever the dashboard says (change control, or an incident switch that does
not depend on the control plane), set `waf: false` or `API_TRAFFIC_WAF_ENABLED=false`:

```javascript
// config/apitraffic.js
module.exports.apitraffic = { waf: false };
```

See [Firewall setup](https://www.apitraffic.io/docs/waf/setup) for policies and rules.

## Application Performance Monitoring (APM)

The APM bridge captures database, cache, queue, GraphQL, gRPC, and `fetch`/`undici` spans within
each ApiTraffic request. Install the OpenTelemetry peers it uses:

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

OpenTelemetry patches modules as they are loaded, so it has to start before Sails and your
database adapters do. Preload it:

```bash
node -r @apitraffic/sails/register app.js
```

or call `register()` on the very first line of `app.js`, ahead of every other `require`:

```javascript
require('@apitraffic/sails').register();
```

If your application already configures OpenTelemetry, add `ApiTrafficSpanProcessor` from
`@apitraffic/apm` to that provider instead, so spans are not double-instrumented.

## Configuration Options

| Option | Environment Variable | Required | Type | Description |
|--------|---------------------|----------|------|-------------|
| `token` | `API_TRAFFIC_TOKEN` | Yes | String | Ingest token from your ApiTraffic account |
| `bucket` | `API_TRAFFIC_BUCKET` | Yes | String | Bucket ID for data organization |
| `interceptOutbound` | `API_TRAFFIC_INTERCEPT_OUTBOUND` | No | Boolean | Monitor outbound HTTP requests (default: true) |
| `debug` | `API_TRAFFIC_DEBUG` | No | Boolean | Enable debug logging (default: false) |
| `waf` | `API_TRAFFIC_WAF_ENABLED` | No | Boolean or object | `false` refuses the firewall locally; an object passes settings to `@apitraffic/waf`. Normally left unset: the firewall is switched on from your account |

Every option falls back to its environment variable, so with no `config/apitraffic.js` at all the
hook reads its configuration from the environment. An option in config wins over the environment.

`apitraffic: false` (`module.exports.apitraffic = false` in `config/env/test.js`, say) turns the
hook off. `sails.config.hooks.apitraffic = false` does too, by not loading it at all, so use it
only when `config/http.js` does not list `apitraffic` or `apitrafficFirewall`: Sails will not lift
with an entry in `order` that nothing provides.

The options are typed, for a TypeScript Sails app: `ApiTrafficOptions` lists every option the SDK
accepts.

```typescript
import type { ApiTrafficOptions } from '@apitraffic/sails';

export const apitraffic: ApiTrafficOptions = { debug: process.env.NODE_ENV !== 'production' };
```

## Embed tokens

The [embeddable request log](https://www.apitraffic.io/docs/integrations/embed) authenticates with a token your server
mints. `embedToken` is exported here, as from every ApiTraffic package:

```javascript
// api/controllers/embed/token.js
const { embedToken } = require('@apitraffic/sails');

module.exports = async function token(req, res) {
  return res.json(await embedToken({ filters: { tags: { customerId: 'cus_123' } }, ttl: 900 }));
};
```

## Troubleshooting

**No requests are recorded**

Check that `@apitraffic/sails` is listed in your app's `package.json` dependencies (Sails
ignores hooks that are not) and installed in the app's own `node_modules` (see the monorepo
note below), that neither `sails.config.apitraffic` nor
`sails.config.hooks.apitraffic` is `false`, and that
`API_TRAFFIC_TOKEN` and `API_TRAFFIC_BUCKET` are set. Set `debug: true` to see what the SDK is
doing; when the hook is mounted, `sails.config.http.middleware.order` contains `apitraffic`
(first, unless you placed it yourself) and `apitrafficFirewall`.
If `config/http.js` defines its own `apitraffic` middleware, the hook does not set ApiTraffic
up (see "ApiTraffic already in `config/http.js`" above).

**The hook is not loaded in a monorepo**

Sails looks for installable hooks only in the app's own `node_modules`. If your package
manager hoists `@apitraffic/sails` to the workspace root, Sails does not find it: add
`api/hooks/apitraffic/index.js` with `module.exports = require('@apitraffic/sails');` and Sails
loads it from there, configured the same way.

**Tags set in a script or scheduled job are missing**

Tags, traces and spans belong to an HTTP request. Code that does not run while a request is
being handled has none to attach them to, and the helpers do nothing there.

**Socket.io requests are missing**

Requests over Sails' WebSocket transport are virtual: they reach your actions without passing
through the HTTP server or its middleware, so there is nothing for the hook to record, and the
firewall does not check them.

## Sample Application

A complete working example is available in the [GitHub repository](https://github.com/apitraffic/platform/tree/main/libs/client-libraries/apitraffic-sails/examples/basic).
