New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@apitraffic/adonisjs

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@apitraffic/adonisjs

AdonisJS package for ApiTraffic: request capture and the ApiTraffic firewall, with APM through @apitraffic/apm, as a provider and two middleware.

latest
Source
npmnpm
Version
3.0.0
Version published
Weekly downloads
255
-51.7%
Maintainers
1
Weekly downloads
 
Created
Source




Website   •   Docs   •   AdonisJS Docs   •   X   •   Discord

npm license

@apitraffic/adonisjs is ApiTraffic for AdonisJS, as a provider and two middleware. One command installs and registers them, and every request your application serves is recorded - request and response, headers, bodies, timing, the route AdonisJS matched, and the outbound HTTP calls made while serving it - and checked by the ApiTraffic firewall.

It is @apitraffic/node, the one-install SDK, wired into AdonisJS. It contains no capture, firewall or instrumentation logic of its own: requests are recorded by @apitraffic/express, run on the raw Node request and response AdonisJS serves, and checked by @apitraffic/waf's AdonisJS middleware. What actually runs - the firewall, redaction, exclusions, span capture - is switched on from your ApiTraffic account, not in code.

Contents

Which package should I use?

On AdonisJS, use this package. AdonisJS has no request object Express or Koa middleware could use, so the framework packages cannot be mounted on it directly; this package runs @apitraffic/express on the raw Node request and response, reads what Express would have known (client address, parsed body, matched route) from the Adonis context, and places the firewall after the body parser.

For other frameworks, see the "Which package should I install?" table in the @apitraffic/node README.

Requirements

  • AdonisJS 6 or 7 (@adonisjs/core ^6.2.0 || ^7.0.0, a peer dependency), on the Node version it supports: Node 20.6+ for AdonisJS 6, Node 24+ for AdonisJS 7.
  • Outbound HTTPS from your servers to ingest.apitraffic.io (telemetry) and api.apitraffic.io (settings and live configuration). Live configuration changes (redaction rules, exclusions, firewall policy) are pushed over a realtime connection to Ably, so also allow its realtime hosts; if that connection is blocked, live updates wait for a restart and nothing else is affected.
  • An ingest token and a bucket ID from your ApiTraffic account. See API tokens.

Installation

One command installs the package with your package manager and configures it:

node ace add @apitraffic/adonisjs

To install it yourself, run configure afterwards:

npm install @apitraffic/adonisjs
# or: pnpm add @apitraffic/adonisjs
# or: yarn add @apitraffic/adonisjs
node ace configure @apitraffic/adonisjs

@apitraffic/node comes with it as a dependency.

What configure adds

Running it again is safe: nothing already registered is added twice, no existing line of .env or .env.example is changed, and an existing config/apitraffic.ts is kept unless you pass --force.

  • config/apitraffic.ts, the package's options (see Configuration).
  • .env and .env.example (in ENV_PATH when it is set): API_TRAFFIC_TOKEN= and API_TRAFFIC_BUCKET=, added only if missing, and their validation in start/env.ts as Env.schema.string.optional().
  • adonisrc.ts: the provider, @apitraffic/adonisjs/apitraffic_provider.
  • start/kernel.ts: the ApiTraffic middleware first in the server middleware, and the firewall last in the router middleware, after the body parser.

Quick start

After configure, set your ingest token and bucket ID in .env:

API_TRAFFIC_TOKEN=your-ingest-token
API_TRAFFIC_BUCKET=your-bucket-id

That is the whole setup. For reference, this is what configure wrote, and what to write by hand if you set the package up yourself:

// config/apitraffic.ts
import env from '#start/env'
import { defineConfig } from '@apitraffic/adonisjs'

const apiTrafficConfig = defineConfig({
  token: env.get('API_TRAFFIC_TOKEN'),
  bucket: env.get('API_TRAFFIC_BUCKET'),
})

export default apiTrafficConfig
// adonisrc.ts
providers: [
  // ...
  () => import('@apitraffic/adonisjs/apitraffic_provider'),
],
// start/kernel.ts
server.use([
  () => import('@apitraffic/adonisjs/apitraffic_middleware'),
  () => import('#middleware/container_bindings_middleware'),
  // ...the rest of your server middleware
])

router.use([
  () => import('@adonisjs/core/bodyparser_middleware'),
  // ...the rest of your router middleware
  () => import('@apitraffic/adonisjs/firewall_middleware'),
])

Where it mounts

Three pieces, each with a fixed place:

  • The provider (adonisrc.ts) reads config/apitraffic.ts, starts the SDK when the HTTP server starts - before it listens, so settings (redaction rules among them) are loading while the server comes up - and delivers what was captured when the application terminates. Ace commands and the REPL serve no requests and never start the SDK. In the test environment it starts with the first request a test sends.
  • The ApiTraffic middleware goes first in server.use(). Server middleware runs on every request, whether or not a route matches, and everything after it - the rest of the server middleware, the router, route middleware, the controller and the exception handler - runs inside the request it records, which is what makes tag(), trace() and span() work there. It also runs the firewall on the request as it arrived.
  • The firewall middleware goes last in router.use(), after @adonisjs/core/bodyparser_middleware, so body rules see the parsed body.

Keep the provider registered: without it the middleware still records requests (with options from the environment only), but nothing delivers what is still queued when the application stops.

Configuration

Options go in config/apitraffic.ts, through defineConfig(), or are set as environment variables. You can mix the two.

Precedence: an option set in config/apitraffic.ts wins over its environment variable, which wins over the built-in default. env.get() returns undefined for a variable that is not set, which leaves the option to the environment and default, so an empty defineConfig({}) reads everything from the environment. On top of that, settings from your ApiTraffic account - redaction rules, exclusions, the firewall policy, APM capture switches - are loaded at startup and pushed to every connected server in real time, without a restart or a deploy.

A few settings combine differently:

SettingRule
Firewall on or offOff if either waf: false is set or API_TRAFFIC_WAF_ENABLED is exactly false or 0, or off in any case. This variable does not use the switch parser below: FALSE, no or a value with spaces leaves the firewall on. Neither can turn on a firewall your account has not enabled.
waf.offlineWafModeAPI_TRAFFIC_OFFLINE_WAF_MODE wins over the config option, so you can change it during an incident without a deploy. Only exactly closed (lowercase) means closed: any other non-empty value, such as CLOSED, still overrides the option and fails open.
captureStatementParams, captureRedisKeysOn if your config or environment turns it on, or the bucket's APM Capture settings do.
captureCallSiteThe bucket's APM Capture setting applies unless you set it in config or the environment. Set locally it can only turn code location on for the SDK's own spans: a local false does not override a bucket that has it on. The APM bridge reads API_TRAFFIC_CAPTURE_CALL_SITE (or its own captureCallSite option) itself, so a false there does turn it off for OpenTelemetry spans.
captureStackDepthThe bucket's APM Capture setting applies unless you set it in config or the environment, which then wins.
interceptOutboundA blank value ("", null, an empty variable) turns outbound capture off. For the other switches, limits, sample rates, token, bucket and accountSid, a blank value counts as not set (apiHost and ingestHost fall back only when empty; a value of spaces is used as given). Two exceptions: passing debug at all, even as undefined, overrides API_TRAFFIC_DEBUG, and a blank projectRoot in code skips API_TRAFFIC_PROJECT_ROOT.

Switches and numeric limits also accept strings (true/false, yes/no, on/off, 1/0; whole numbers for byte caps), so { captureSpans: env.get('CAPTURE_SPANS') } works. An invalid value is reported once on the console and ignored. debug and strict are not parsed from strings in config - pass true or false - and API_TRAFFIC_DEBUG turns debug on only when it is exactly true (any case).

The SDK is set up once per process, with the first application's config/apitraffic.ts.

Options

OptionEnvironment variableTypeDefaultDetails
tokenAPI_TRAFFIC_TOKENstring-Required. Ingest token from your ApiTraffic account.
bucketAPI_TRAFFIC_BUCKETstring-Required. ID of the bucket captured traffic is sent to.
wafAPI_TRAFFIC_WAF_ENABLEDfalse or objectunsetLocal override for the firewall. false refuses it whatever the dashboard says; an object passes settings to @apitraffic/waf (and enabled: false in it turns the firewall off too). Normally left unset.
debugAPI_TRAFFIC_DEBUGbooleanfalseLog SDK activity to the console.
strict-booleanfalseMakes the base client's setup() throw on misconfiguration (missing token or bucket) instead of logging. This package runs setup in the background and catches that error, so strict does not stop your app from booting, and the missing-token message is then printed only when API_TRAFFIC_DEBUG is set. Leave it off.
accountSidAPI_TRAFFIC_ACCOUNT_SIDstringlearned from settingsOnly needed by a process that mints embed tokens without serving requests.
apiHostAPI_TRAFFIC_API_HOSTstringapi.apitraffic.ioSettings and configuration host.
ingestHostAPI_TRAFFIC_INGEST_HOSTstringingest.apitraffic.ioHost captured traffic is sent to.
interceptOutboundAPI_TRAFFIC_INTERCEPT_OUTBOUNDswitchtrueCapture outbound http, https and fetch calls.
captureFetchAPI_TRAFFIC_CAPTURE_FETCHswitchtrueCapture outbound global fetch (needs interceptOutbound).
redactSensitiveKeysAPI_TRAFFIC_REDACT_SENSITIVE_KEYSswitchtrueMask credential-shaped body keys and query parameters.
maxBodyBytesAPI_TRAFFIC_MAX_BODY_BYTESbytes524288 (512 KB)Largest request or response body sent; a larger one is cut to the cap and ends with …[truncated by ApiTraffic: N bytes total].
maxRedactBodyBytesAPI_TRAFFIC_MAX_REDACT_BODY_BYTESbytes1048576 (1 MB)Largest body the SDK redacts; a larger one becomes a placeholder first.
maxFetchBodyLengthAPI_TRAFFIC_MAX_FETCH_BODY_LENGTHbytes524288 (512 KB)Largest outbound body captured.
requestSampleRateAPI_TRAFFIC_REQUEST_SAMPLE_RATE0–11Fraction of requests sent. 5xx responses are always sent.
ingestBatchAPI_TRAFFIC_INGEST_BATCHswitchtrueBatch payloads when your account and the ingest host support it.
ingestGzipAPI_TRAFFIC_INGEST_GZIPswitchtrueCompress those batches when your account asks for it.
captureSpansAPI_TRAFFIC_CAPTURE_SPANSswitchtrueMaster switch for spans and per-request span rollups.
captureStatementsAPI_TRAFFIC_CAPTURE_STATEMENTSswitchtrueInclude normalized SQL/command text in spans.
captureStatementParamsAPI_TRAFFIC_CAPTURE_STATEMENT_PARAMSswitchfalseAlso keep statements as issued, with bind values (your users' data).
captureRedisKeysAPI_TRAFFIC_CAPTURE_REDIS_KEYSswitchfalseInclude the concrete Redis key, unmasked.
captureCallSiteAPI_TRAFFIC_CAPTURE_CALL_SITEswitchtrue (bucket setting)Record the application code location that started each span.
captureStackDepthAPI_TRAFFIC_CAPTURE_STACK_DEPTHwhole number ≥ 010 (bucket setting)Application frames kept per span.
projectRootAPI_TRAFFIC_PROJECT_ROOTstringdetectedDirectory code locations are reported relative to.
redisKeyPatterns-array of strings or RegExps[]Redis key patterns the automatic masking cannot infer.
maxSpansPerRequestAPI_TRAFFIC_MAX_SPANS_PER_REQUESTcount500Spans kept per request.
spanSampleRateAPI_TRAFFIC_SPAN_SAMPLE_RATE0–11Fraction of requests that keep full span detail.
maxSpanMetaBytesAPI_TRAFFIC_MAX_SPAN_META_BYTESbytes2048Size cap on each span's metadata, after redaction.

Delivery to ApiTraffic honours HTTPS_PROXY, HTTP_PROXY, ALL_PROXY and NO_PROXY. The configuration reference covers the long tail: firewall options, the APM preload's own options, offline definitions and server labels.

What gets captured

Every request the application serves, including the ones a controller never sees, with its final status and whatever your code tagged on the way:

  • a request a server middleware answers itself (CORS, an auth check),
  • a request a route middleware refuses (auth, guest, your own),
  • a request no route matches (404),
  • a request whose handler throws, as the response your exception handler sent.

The route AdonisJS matched is sent with each request, group prefixes included, so router.get('/users/:id') in a group with .prefix('/api/v1') is the endpoint /api/v1/users/{id}. A wildcard route (/files/*) sends none, and ApiTraffic detects the endpoint itself. See Endpoint handling.

The request is recorded as your application sees it: the client's address from request.ip() (your trustProxy setting honoured), the method AdonisJS routed, the URL as the client sent it (query included), and the body the body parser parsed - a JSON or form body as fields, a raw type such as text/plain as text. The response is recorded exactly as it was sent, streamed responses and downloads included.

  • Inbound: method, URL, headers, request and response bodies, status and timing. Bodies over maxBodyBytes (512 KB) are cut to that size and marked as truncated, and bodies over maxRedactBodyBytes (1 MB) are replaced by a placeholder.
  • Outbound: calls made with http, https and global fetch while serving a request, linked to it. See Outbound requests.
  • Sampling: requestSampleRate sends a fraction of requests; server errors are always sent.

Capture is the same code as @apitraffic/express, so what that page describes under "What is captured" holds here too.

Data security

Nothing here needs a code change: it is configured in your ApiTraffic account and pushed to every connected server.

  • Client-side redaction. Redaction rules are applied in your process before anything leaves it. Nothing is sent until your account's rules have loaded, and a request whose redaction cannot be completed is dropped, never sent unredacted.
  • Sensitive key masking. Credential headers are always masked. With redactSensitiveKeys (on by default), credential-shaped body keys and query parameters (password, token, secret, api_key and similar) are masked too.
  • Exclusions. Exclusions stop matching requests from being sent at all.

Firewall

The ApiTraffic firewall is mounted by the package, and whether it runs is decided in your ApiTraffic account: nothing is loaded until a policy is live, and a policy change reaches every server without a deploy. It checks each request as it arrives, and a request with a body once more after the body parser, counting it once against rate limits:

  • in the server middleware, as the request arrives, so a request no route matches is checked too, before any of your other server middleware runs;
  • in the router middleware, after the body parser, so body rules see the parsed body, ahead of named middleware (auth) and your controller.

A request it blocks never reaches the router middleware after it, your named middleware or your controller, and is still recorded, with the policy's status and waf.* tags. A request blocked as it arrives is answered ahead of your server middleware, CORS included, so a browser sees it without Access-Control-* headers. Without the firewall middleware in router.use() the first check still runs, but body rules have no body to inspect.

To refuse it locally whatever the dashboard says, set waf: false or API_TRAFFIC_WAF_ENABLED=false:

// config/apitraffic.ts
const apiTrafficConfig = defineConfig({
  waf: false,
})

Use the option when your change process requires that anything able to reject production traffic ships through a deploy; use the variable as an incident kill switch, which works even when ApiTraffic is unreachable. See Firewall setup.

APM

The APM bridge records database, cache, queue, GraphQL, gRPC and fetch/undici spans inside each request. OpenTelemetry instruments libraries by patching them as they are loaded, so it has to start before AdonisJS and your database clients do - by the time the provider runs, that moment has passed. So it needs a preload.

Install @apitraffic/apm, which brings the OpenTelemetry packages it needs:

npm install @apitraffic/apm

An AdonisJS application is an ES module, so the bridge is preloaded with --import, after OpenTelemetry's loader hook (which lets it patch the packages the application imports):

// otel-hook.mjs
import { register } from 'node:module'

register('@opentelemetry/instrumentation/hook.mjs', import.meta.resolve('@apitraffic/apm'))
node --import ./otel-hook.mjs --import @apitraffic/adonisjs/register build/bin/server.js

@apitraffic/adonisjs/register is @apitraffic/node/register, re-exported so it resolves from an application that installs only this package (with pnpm, @apitraffic/node is not resolvable from your app). It reads its configuration from the API_TRAFFIC_* environment variables (not config/apitraffic.ts, which loads too late) and never throws: if the OpenTelemetry packages are missing or fail to start, APM is skipped with a one-line reason and your application boots normally.

The preload is for applications with no other tracing. If yours already configures OpenTelemetry, skip it and add ApiTrafficSpanProcessor from @apitraffic/apm to your own provider; with Datadog or New Relic loaded the preload steps aside on its own. See Set up APM.

Tagging, tracing and spans

The helpers work anywhere a request is being handled: controllers, services, middleware, route middleware and your exception handler. Outside a request (an Ace command, a scheduled job, a queue worker) they do nothing, and they never throw.

Tagging

tag(key: string, value: string) makes a request searchable, for example by account or user ID:

// app/middleware/account_middleware.ts
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
import { tag } from '@apitraffic/adonisjs'

export default class AccountMiddleware {
  async handle(ctx: HttpContext, next: NextFn) {
    // Searchable on this request, whether or not it gets past this middleware.
    tag('accountId', ctx.request.header('x-account-id') ?? 'none')
    return next()
  }
}

Keys are 1–64 characters (a letter first, then letters, digits, _, . or -); the prefixes redaction., apitraffic. and at. are reserved and dropped, and waf. is the firewall's own namespace (such a key is stored among the firewall's tags), so do not use it. Values are cut to 255 characters, and a request holds at most 32 tag keys. A tag that breaks a rule is dropped, never thrown. See Tags.

Tracing

trace(content: string) is console.log for production: a timestamped note on the request (up to 255 characters, 250 per request; a longer message is dropped, not cut).

import { trace } from '@apitraffic/adonisjs'

trace('cart total exceeded the free-shipping threshold')

Spans

span(type, name, fn, meta?) runs fn, times it and records it on the request's waterfall, returning what fn returns; time() is the same function. startSpan(type, name, meta?) returns a handle to end() for work that does not fit one function, and addSpan() records a span you built yourself.

// app/services/users_service.ts
import { span, trace } from '@apitraffic/adonisjs'
import User from '#models/user'

export default class UsersService {
  async find(id: string) {
    trace(`Loading user ${id}`)
    return span('db.query', 'load user', () => User.findOrFail(id))
  }
}

type is one of db.query, redis, cache, http.out, queue, rpc, graphql or custom. getContext() and getRequestManager() expose the current request's context for advanced use; most applications never need them. See Per-request APIs.

Embed tokens

The embeddable request log (and embedded reports and dashboards) authenticate with a short-lived JWT that locks in the bucket, the filters and the request detail tabs the embed may show. It is minted on your server with an Administrator API token with no access scopes, set as API_TRAFFIC_EMBED_TOKEN (the ingest token the SDK reports with cannot mint embed tokens), and this package mints it:

// start/routes.ts
import router from '@adonisjs/core/services/router'
import { middleware } from '#start/kernel'
import { embedToken } from '@apitraffic/adonisjs'

// An embed token is a read credential: only signed-in users get one.
router
  .get('/embed/token', async ({ auth }) => {
    const user = auth.getUserOrFail()
    const { token, expiresAt } = await embedToken({
      // Structured: the id is escaped, so a value such as `x" OR method == "GET`
      // cannot add a condition of its own.
      filters: { tags: { customerId: String(user.id) } },
      ttl: 900,
    })
    return { token, expiresAt }
  })
  .use(middleware.auth())

The host, bucket and account come from the SDK you already configured and the token from API_TRAFFIC_EMBED_TOKEN, so a call names only what is specific to this embed. Everything is optional, except reportSid or dashboardSid for a report or dashboard embed:

OptionDefaultDetails
bucketSidThe bucket the SDK reports to (request log only)The bucket the embed is scoped to. A report or dashboard keeps its own scope unless you pass one.
view'request-log''request-log', 'report' (with reportSid) or 'dashboard' (with dashboardSid).
filters{}environmentSid, criteria, streamViewSid, tags (request log) or filter, environmentSid, tags (report and dashboard) - signed into the token, so the browser cannot widen them. tags ({ customerId: id }) is written into the criteria (for a report or dashboard, the filter) safely; if you build criteria yourself, put values in double quotes through escapeCriteriaValue().
timeRange / timeRangesThe report's own / allReport and dashboard embeds: the range the embed opens with ({ relative: 'last_7d' }) and the ranges the viewer may pick.
ttl3600Token lifetime in seconds (60–86400).
theme'light''light', 'dark' or 'auto'.
tabsThe API's default setRequest detail tabs this embed may show. Request log only: the API refuses it for a report or dashboard.
features{}copyRequest, includeBodies. Request log only: the API refuses it for a report or dashboard.
accountSidFrom your SDK settingsOnly needed in a process that does not serve requests through ApiTraffic. Also read from API_TRAFFIC_ACCOUNT_SID.
token / apiHostAPI_TRAFFIC_EMBED_TOKEN / the SDK's hostOverride the credential or host for one call.

Data behind a tab you do not grant is never returned to the browser - the tabs are an access boundary, not a display setting.

This is the one call in the package that rejects rather than going quiet. A token has a single useful outcome, and it is produced inside your own await, where a failure is a 500 on that route instead of a broken embed with nothing to explain it.

API_TRAFFIC_EMBED_TOKEN is a read credential. An Administrator API token can mint embed tokens for any bucket in its account, with any filters or none, so whoever holds it can read all of the account's captured traffic. Keep it in a secret store (never in client-side code, images or a repository), set it only on servers that mint embed tokens, and rotate it immediately if it may have been exposed.

Lifecycle: shutdown, flush and stats

When the application terminates - app.terminate(), which the starter kits' bin/server.ts calls on SIGTERM, and on SIGINT when the app runs under pm2 - AdonisJS closes the HTTP server first, and then the provider delivers what was captured and releases the SDK's timers, realtime connection and outbound hooks, once the last application serving HTTP in the process has terminated. Every request the server answered has been reported by then.

The SDK installs no signal handlers of its own, so a process stopped any other way - a plain SIGINT (Ctrl-C) outside pm2 - exits without app.terminate(), and what is still queued is not delivered. Once shut down, the SDK stays down in that process: requests are still served, and not recorded.

@apitraffic/node's lifecycle functions are re-exported for the cases the provider does not cover:

  • flush({ timeoutMs }) delivers what has been captured without stopping (default timeout 2 s). Anything not delivered in time stays queued.
  • shutdown({ flushTimeoutMs }) stops capturing, delivers what is queued and releases everything. You do not need it when the provider is registered.
  • stats() returns delivery counters (pending, queued, delivered, recovered, dropped, breakerOpen, …), for a health endpoint or your own monitoring.

All three never throw.

TypeScript

The package ships its own declarations. defineConfig() takes ApiTrafficOptions, which lists every option the SDK accepts, so in config/apitraffic.ts a value of the wrong type is a compile error, and so is a misspelled key (unlisted keys are still passed through at runtime).

// config/apitraffic.ts
import env from '#start/env'
import { defineConfig } from '@apitraffic/adonisjs'

export default defineConfig({
  token: env.get('API_TRAFFIC_TOKEN'),
  bucket: env.get('API_TRAFFIC_BUCKET'),
  debug: env.get('NODE_ENV') !== 'production',
})

The package is CommonJS and imports into an AdonisJS (ES module) application with named imports. Entry points: @apitraffic/adonisjs (defineConfig, the configure hook Ace runs, and the helpers), @apitraffic/adonisjs/apitraffic_provider, @apitraffic/adonisjs/apitraffic_middleware, @apitraffic/adonisjs/firewall_middleware and @apitraffic/adonisjs/register. Exported types: ApiTrafficOptions, WafConfigureOptions, ApmOptions, RegisterResult, SpanHandle, SpanMeta, EmbedTokenOptions and EmbedTokenResult.

Failure behaviour

The SDK is built so that it can never be the reason your application fails.

  • The provider and both middleware never throw. If capture cannot load, requests are served and the firewall still runs; if neither can, the middleware just calls next().
  • A missing or invalid token disables telemetry and logs a clear message. Your application still boots.
  • If ApiTraffic is unreachable, requests are served normally. A short outage is absorbed by an in-memory retry queue; a longer one sheds data rather than growing without bound. See If ApiTraffic is unreachable.
  • If the realtime connection fails, you lose live configuration updates and nothing else.
  • Nothing is sent before your account's client-side redaction rules have loaded.
  • tag() and trace() never throw into your handlers, and every helper does nothing outside a request.

Want the app to refuse to boot without credentials? Check API_TRAFFIC_TOKEN and API_TRAFFIC_BUCKET yourself before you start the server. strict: true does not do it here: setup runs in the background and its error is caught.

Troubleshooting

Start with debug logging: set API_TRAFFIC_DEBUG=true (or debug: true in config/apitraffic.ts) and restart. A missing token is logged even without debug.

  • No requests are recorded. Check that @apitraffic/adonisjs/apitraffic_middleware is in server.use() in start/kernel.ts (not only in router.use(), which runs only for matched routes), and that API_TRAFFIC_TOKEN and API_TRAFFIC_BUCKET are set in .env or the environment. The setup page in ApiTraffic shows whether the SDK has connected.
  • Tags set in a middleware are missing. The ApiTraffic middleware must come first in server.use(): server middleware registered ahead of it runs before the request it records has started.
  • Body rules do not see the request body. The firewall middleware has to come after @adonisjs/core/bodyparser_middleware in router.use(). node ace configure puts it last.
  • Nothing arrives, and the SDK cannot connect. Allow outbound HTTPS to ingest.apitraffic.io and api.apitraffic.io. Behind a proxy, set HTTPS_PROXY.
  • Bodies are missing or show a placeholder. A body larger than maxBodyBytes (512 KB) is cut, and one larger than maxRedactBodyBytes (1 MB) is replaced by a placeholder; raise the caps if you need more. See Troubleshooting.
  • Several applications in one process (a test suite). The SDK is set up once per process, with the first application's config/apitraffic.ts, and shut down when the last application serving HTTP terminates. After that it stays down, as shutdown() leaves it.
  • Tags set in an Ace command or a queue consumer are missing. Tags, traces and spans belong to an HTTP request; outside one the helpers do nothing.
  • "the installed @apitraffic/waf has no AdonisJS firewall". A live policy is not being enforced because an older @apitraffic/waf is installed alongside. Update it to the version @apitraffic/node depends on, or remove the duplicate.
  • APM shows no spans. Preload with --import, after the OpenTelemetry loader hook, and install @apitraffic/apm. See Set up APM.

More symptoms and fixes are in the troubleshooting guide.

Sample application

The setup guide walks through a complete application. The ApiTraffic source (not the npm package) also includes a working AdonisJS 6 application in examples/basic, set up exactly as node ace add @apitraffic/adonisjs sets it up: the provider in adonisrc.ts, the two middleware in start/kernel.ts, config/apitraffic.ts, and the variables in start/env.ts.

Copy .env.example to .env, set API_TRAFFIC_TOKEN and API_TRAFFIC_BUCKET in it, then:

npm install
node ace generate:key
npm start

It listens on port 4000: try /, /api/v1/users/42 (a route parameter, a tag and a span), /outbound (an outbound call), /admin (a request a route middleware refuses) and /nowhere (a 404), then open the bucket's traffic stream in ApiTraffic.

Contributing, changelog and license

Community

Follow @apitraffic/adonisjs on npm to stay updated on new releases.

Follow us on our Blog and on X.

Chat with the ApiTraffic team and other members on Discord and follow our tutorials and other videos on YouTube.

ApiTraffic Discord

ApiTraffic YouTube

ApiTraffic on X

How to contribute

Here are some ways you can help improve ApiTraffic:

  • Give ApiTraffic a try and share your feedback to help make ApiTraffic even better for you. Feel free to reach out to us on Discord!
  • Join our Discord to connect with other members, share your experiences, and learn from one another.
  • Report bugs or request features by emailing support@apitraffic.io or asking on Discord.

Keywords

adonisjs

FAQs

Package last updated on 03 Nov 2026

Related posts