# APM for Node, Bun and Deno

> Instrument Node.js, Next.js, Nuxt, Bun and Deno apps with @falak/apm-node — auto-instrumentation, request wrappers, exceptions, config and redaction.

Source: https://falak.sh/docs/observability/apm-node/

`@falak/apm-node` is a thin preset over the official OpenTelemetry JS SDK. It exports traces, logs and metrics as OTLP/HTTP JSON to the local agent (`http://127.0.0.1:4318`) and normalizes spans to Falak's event types (`request`, `outgoing_request`, `query`, `cache`) so they show up in Insights.

## Install

```bash
npm i @falak/apm-node      # or: bun add @falak/apm-node / pnpm add @falak/apm-node
```

The package source lives in the Falak repository under `packages/apm-node` (version 0.1.0). *Registry availability is not verified by these docs.*

## Set up per framework

  
    Load it before your app so auto-instrumentation can patch modules:

    ```json title="package.json"
    { "scripts": { "start": "node --import @falak/apm-node/register dist/server.js" } }
    ```

    Or put `import '@falak/apm-node/register';` on the first line of your entry file (covers CommonJS and built-ins; use `--import` when you load `pg`/`express` through ESM).

    Auto-instrumented: `http`/`https` (incoming and outgoing), `undici`/global `fetch`, `pg`, `mysql2`, `ioredis`, `express`, `fastify`.
  
  
    ```ts title="instrumentation.ts"
    export async function register() {
      const { registerFalak } = await import('@falak/apm-node/next');
      await registerFalak();
    }
    ```

    Only the Node.js runtime is instrumented. Next's own spans are mapped, with the route from `next.route`.
  
  
    ```ts title="server/plugins/falak.ts"
    import falak from '@falak/apm-node/nitro';
    export default defineNitroPlugin(falak);
    ```

    Records errors from Nitro's `error` hook as unhandled and sets `http.route` from the matched route.
  
  
    `Bun.serve` and `Deno.serve` are not auto-instrumented. Wrap your handler:

    ```ts
    import { start, withFalakRequest } from '@falak/apm-node';

    start();

    Bun.serve({
      fetch: withFalakRequest(handler, {
        route: (req) => '/users/:id',          // low-cardinality route template
        user: (req) => session(req)?.userId,   // enduser.id
        captureHeaders: ['x-request-id'],
      }),
    });
    ```

    The same wrapper works for Hono (`withFalakRequest(app.fetch)`), Deno and Next route handlers. It continues an incoming `traceparent`, records thrown errors as unhandled, and marks 5xx responses as errors.
  

## Exceptions

```ts
import { recordException } from '@falak/apm-node';

try { await charge(); } catch (e) { recordException(e); }   // handled=true
recordException(err, { handled: false });                    // span → ERROR
```

Uncaught exceptions and unhandled rejections are captured as `handled=false`; the process still crashes as it would by default. Opt out with `captureProcessErrors: false`.

## Configuration

Pass options to `start({ … })` or use environment variables:

| Option | Env | Default |
|---|---|---|
| `enabled` | `FALAK_APM_ENABLED` | `true` |
| `endpoint` | `FALAK_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_ENDPOINT` | `http://127.0.0.1:4318` |
| `serviceName` | `FALAK_SERVICE_NAME`, `OTEL_SERVICE_NAME` | `npm_package_name` (the agent overrides it with the site slug) |
| `sampleRate` | `FALAK_SAMPLE_RATE` | `1` (root, parent-based) |
| `autoInstrument` | `FALAK_APM_AUTO_INSTRUMENT` | `true` (Node only) |
| `logs` | `FALAK_APM_LOGS` | `true` |
| `metrics` | `FALAK_APM_METRICS` | `true` (Node only, 60 s interval) |
| `redactKeys` | | `password, token, secret, authorization, cookie, api_key` |
| `redactQueryLiterals` | | `true` |
| `redact(attributes, eventType)` | | none |
| `captureRequestHeaders` | | `['user-agent']` |
| `captureProcessErrors` | | `true` |

Resource attributes come from `FALAK_SITE_ID`, `FALAK_SERVER_ID`, `FALAK_DEPLOYMENT_ID`, `FALAK_RELEASE_ID`, `FALAK_ORG_ID` and `FALAK_ENVIRONMENT` (falling back to `NODE_ENV`).

## Redaction

Before export:

- attribute keys containing a denylisted word (including captured headers) become `[redacted]`;
- denylisted query parameters in URLs and passwords in URL userinfo are redacted;
- quoted SQL literals become `?`;
- cache keys containing a denylisted word are redacted;
- your `redact` callback runs last (a throwing callback is ignored).

## Graceful shutdown

```ts
import { start, shutdown } from '@falak/apm-node';

start({ serviceName: 'shop', sampleRate: 0.5 });
process.on('SIGTERM', () => shutdown().finally(() => process.exit(0)));
```

## Next steps
