> ## Documentation Index
> Fetch the complete documentation index at: https://docs.squasher.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Node.js SDK

> Capture errors from Node.js services, workers, and HTTP frameworks.

Use `@squasher-ai/node` for Express, Fastify, Hono, Bun, workers, and plain scripts.

## Install

```bash theme={null}
bun add @squasher-ai/node
```

## Initialize early

Call `init()` before you import code that might throw.

```typescript src/index.ts theme={null}
import { init } from "@squasher-ai/node";

init({
  apiKey: process.env.SQUASHER_API_KEY!,
  projectId: process.env.SQUASHER_PROJECT_ID!,
  environment: process.env.NODE_ENV,
});
```

For long-running services, install process-level handlers and close the client during shutdown:

```typescript theme={null}
import { close, installGlobalHandlers } from "@squasher-ai/node";

installGlobalHandlers();

process.on("SIGTERM", () => {
  void close().finally(() => process.exit(0));
});
```

The SDK batches buffered events and automatically splits multi-event request bodies at 1 MB. The
configured event-count and flush-interval thresholds still determine when a flush starts; the byte
limit only keeps a single outbound request bounded.

## Wrap request handlers

Use `withHttpRequest` when you want request metadata attached automatically if the handler throws:

```typescript theme={null}
import { getClient, withHttpRequest } from "@squasher-ai/node";

app.get("/checkout", async (req, res) => {
  const result = await withHttpRequest(
    getClient(),
    { request: req, response: res, attributes: { "http.route": "/checkout" } },
    async () => runCheckout(req),
  );

  res.json(result);
});
```

## Capture framework errors

<Tabs>
  <Tab title="Express">
    ```typescript theme={null}
    import express from "express";
    import { captureError } from "@squasher-ai/node";

    const app = express();

    app.use(async (error: unknown, req, res, _next) => {
      if (error instanceof Error) {
        await captureError(error, { url: req.originalUrl, method: req.method });
      }
      res.status(500).json({ error: "Internal server error" });
    });
    ```
  </Tab>

  <Tab title="Fastify">
    ```typescript theme={null}
    import Fastify from "fastify";
    import { captureError } from "@squasher-ai/node";

    const app = Fastify();

    app.setErrorHandler(async (error, request, reply) => {
      await captureError(error, { url: request.url, method: request.method });
      reply.status(500).send({ error: "Internal server error" });
    });
    ```
  </Tab>

  <Tab title="Hono">
    ```typescript theme={null}
    import { Hono } from "hono";
    import { captureError } from "@squasher-ai/node";

    const app = new Hono();

    app.onError(async (error, c) => {
      await captureError(error, { url: c.req.url, method: c.req.method });
      return c.json({ error: "Internal server error" }, 500);
    });
    ```
  </Tab>
</Tabs>

## Manual capture

```typescript theme={null}
import { captureError, captureGeneration, captureMessage, captureTelemetry } from "@squasher-ai/node";

await captureMessage("Worker started", "info");
await captureError(new Error("Verification error"));
await captureGeneration("Support reply drafted", {
  llm: {
    provider: "openai",
    model: "gpt-4o-mini",
    prompt_tokens: 820,
    completion_tokens: 140,
    total_tokens: 960,
    cached_input_tokens: 320,
    cost_usd: 0.0021,
  },
});

await captureTelemetry({
  message: "Payment authorization failed",
  level: "error",
  kind: "error",
  attributes: {
    "error.why": "The payment provider declined the authorization.",
    "error.fix": "Ask the customer to use another payment method.",
    "error.link": "https://docs.example.com/payments/declines",
    "error.status": "actionable",
  },
});
```

## Actionable error context

Squasher AI triage works best when errors include a short explanation and a next step. For Node services, send that context as structured attributes on telemetry events:

| Attribute      | Use for                                                                         |
| -------------- | ------------------------------------------------------------------------------- |
| `error.why`    | What likely happened, in one sentence                                           |
| `error.fix`    | The safest next action for an engineer or agent                                 |
| `error.link`   | A public runbook or product doc URL                                             |
| `error.status` | Current state or status code, for example `actionable`, `investigate`, or `429` |

Keep these values safe to store with remote project telemetry. Put private implementation notes in your own development workflow rather than public-facing docs or customer-visible messages.

For reusable application errors, create the context once and let `captureError` merge it into the event:

```typescript theme={null}
import { captureError, createActionableError } from "@squasher-ai/node";

await captureError(
  createActionableError("Payment authorization failed", {
    why: "The payment provider declined the authorization.",
    fix: "Ask the customer to use another payment method.",
    link: "https://docs.example.com/payments/declines",
    status: "actionable",
    internal: { provider_status: "card_declined" },
  }),
);
```

`internal` is hidden from the error's normal JSON representation, but it is still sent to Squasher as event extra. Only include values that are safe for retained project telemetry.

## Local debug artifacts

Enable `localEventSink` when you want a coding agent or local debug script to inspect the exact events the SDK prepared after `beforeSend`.

```typescript theme={null}
import { init } from "@squasher-ai/node";

init({
  apiKey: process.env.SQUASHER_API_KEY!,
  projectId: process.env.SQUASHER_PROJECT_ID!,
  localEventSink: {
    enabled: process.env.NODE_ENV === "development",
    directory: ".squasher/events",
    redactKeys: ["x-internal-admin-token"],
  },
});
```

The sink writes redacted NDJSON files named `YYYY-MM-DD.jsonl`. Treat them as temporary debugging output for agents and humans. They complement Squasher's remote observability, but they are not the production source of truth and should not be used for alerting, retention, or incident history.

## Agent handoff

```text theme={null}
Install @squasher-ai/node. Initialize it before application code runs, use withHttpRequest around HTTP handlers where possible, install global handlers for long-running services, add safe actionable attributes for known failure modes, and call close() during shutdown.
```

## Related guides

* Use [Pino transport](/sdks/pino) or [Winston transport](/sdks/winston) for structured logs.
* Use [source maps](/sdks/source-maps) to improve stack trace quality.
* Use [AI triage](/features/ai-triage) to turn rich error context into root-cause summaries.
