npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@fixback/node

v0.7.0

Published

The Fixback backend error SDK for Node servers — one-call init, batched secret-key capture, process handlers, and Express + NestJS adapters.

Readme

@fixback/node

The Fixback backend error SDK for Node servers. Capture unhandled crashes and manually-reported errors and file them into the same Fixback pipeline your frontend uses — one queue for your whole product (ADR-0061/0062/0028, spec #224).

Backend errors authenticate with a secret key, so they arrive Internal tier, tagged platform: node, and filed under the environment that key belongs to. Fleet-wide crash-loops collapse into a single Issue with a rising occurrence count instead of flooding your queue.

📖 Full documentation: docs.fixback.dev/sdk/node — guides, concepts, and the complete API reference.

Install

npm install @fixback/node

The Express and NestJS adapters are optional — install the framework you use.

Quick start

Call init as early as possible in your process (before your app boots), so the process handlers are installed before anything can throw:

// instrument.ts — imported first in your entry file
import { init } from "@fixback/node";

init({
  secretKey: process.env.FIXBACK_SECRET_KEY!, // sk_… (never your publishable key)
  release: process.env.GIT_SHA,               // symbolicated against your uploaded sourcemaps
  serviceName: "checkout-api",                // or FIXBACK_SERVICE_NAME — which service this is
  enabled: process.env.NODE_ENV === "production", // gate auto-capture off in local dev
});

release falls back to the FIXBACK_RELEASE environment variable when you pass none, so a deploy that sets it needs no release option. Use the release your sourcemaps were uploaded under: frames then map back to your source, from CommonJS or ES-module builds, on POSIX or Windows.

The secret key selects the environment. Each environment in the Fixback dashboard — Production, Staging, … — has its own keys (and its own Gate), and every error is filed under the environment of the secret key that sent it. So each deploy sets FIXBACK_SECRET_KEY to its own environment's secret key, and there is no environment option to keep in step with it — one passed to init is ignored, with a one-time warning. A new deploy target starts in the dashboard: create its environment and deploy that environment's key.

That single call wires:

  • a batched secret-key transport that keeps an error until Fixback has filed it — retrying, honouring Retry-After, and flushing on shutdown (see Delivery, below) — and sends a flush too large for one request in several. Each error carries the instant it was captured (occurredAt), so a batch flushed later still records when every error happened;
  • process-level uncaughtException / unhandledRejection capture that leaves your process to do what Node would (below) — autoCapture: false turns both off (or captureUncaughtException / captureUnhandledRejection individually);
  • where it ran, on every batch: the SDK's name and version (the Issue's SDK row), your serviceName, and the host name, Node.js version and OS, read once at init — so several services or instances filing into one environment can be told apart;
  • breadcrumbs — each request's console lines and outgoing HTTP calls, which its errors carry as their Trace (below);
  • and the manual capture API below.

apiUrl (or FIXBACK_API_URL) defaults to https://api.fixback.dev — the hosted Fixback API. Set it only when your secret key was minted elsewhere: your own origin for a self-hosted or local deployment. An explicit apiUrl or release option wins over its environment variable.

Crashes and unhandled rejections

Listening for uncaughtException or unhandledRejection switches off what Node itself does with one, so the SDK does it instead, after capturing (handled: false):

  • An uncaught exception is flushed, printed, and exits the process with code 1, as Node would.
  • An unhandled rejection follows your --unhandled-rejections mode, read from NODE_OPTIONS and the command line. In Node's default throw mode the capture is flushed (within a short timeout) and the rejection is raised as an uncaught exception: the process prints it and exits 1, or your own uncaughtException handler receives it. warn-with-error-code prints Node's warning and sets exit code 1; warn and none change nothing.

When your app has its own listener for the event, the SDK only captures and your listener decides what happens. Before 0.7 the SDK kept a process running after an unhandled rejection that Node would have crashed on; to keep that behaviour, handle the rejection, add your own unhandledRejection listener, or run Node with --unhandled-rejections=warn.

Delivery

Errors buffer in memory and go to Fixback in batches — when 100 are buffered, every 5 seconds, on flush(), and on close(). An error leaves the buffer only once Fixback has filed it:

  • Retried. A batch that fails — a 5xx, a timeout, Fixback unreachable — is kept and sent again after 10 seconds, then 20, 40, … up to 5 minutes between tries. An error is given up after 6 failed sends, about five minutes of an unreachable Fixback.
  • Rate limits honoured. When your environment's error budget is spent, Fixback files what it can and answers the rest rate_limited, or the whole batch 429, with a Retry-After. The SDK sends nothing until that wait is over, then sends those errors again; each wait counts as one of the 6 tries.
  • Nothing lost to a pause. Errors captured while the SDK waits keep buffering, and a repeat of one already buffered folds into it — so occurrence counts still add up. The buffer holds maxQueueSize distinct errors (1024): past that the oldest is dropped.
  • Never resent when it can't pass. An error Fixback could not read (invalid_error_item), or a batch refused outright (a 4xx other than 429), is dropped rather than retried.
  • Flushed on shutdown. When your process's event loop runs dry (beforeExit), what was captured since the last flush is sent. On SIGTERM or SIGINT, if your app has no handler of its own for the signal, the SDK sends what is buffered (waiting at most 2 seconds), then re-raises the signal — so the process still ends exactly as it would have. If your app handles the signal itself, the SDK registers nothing for it: call await close() in your handler before you exit. A serverless handler should await flush() before it returns.

A secret key Fixback refuses (401: revoked, its environment deleted, or a publishable key) or a suspended workspace (403) is said once on standard error, so a misconfigured deploy is never silent. debug: true says more: why the SDK is inert, what every send answered, and what is retried or given up.

Manual capture

import { captureException, captureMessage } from "@fixback/node";

try {
  await risky();
} catch (err) {
  captureException(err);           // handled: true
}

captureMessage("cache miss storm", "warning");

A message's level — fatal, error, warning, info (the default) or debug — is sent with it and shows on the Issue.

Chained errors

An error you capture is often a wrapper: fetch failed around the AggregateError of every address the connect tried, or your own error thrown around the database error you caught. The SDK sends the errors behind it too — its cause, followed five deep, and an AggregateError's errors, ten of one aggregate — at most ten in all, each with its own name, message and stack frames, scrubbed like the error itself:

try {
  await db.insert(order);
} catch (err) {
  throw new Error("could not save the order", { cause: err });
}

The Issue shows each one under the error as a Caused by section, and the Brief and the hand-off to your coding agent include them. The outer error leads, and the types behind it join its fingerprint. beforeSend sees them as event.errorCauses, and can redact or drop them. When one error alone outgrows a request, the causes' frames are dropped before the error's own.

Grouping

Repeats of one error fold into one Issue with a rising count, across every instance and deploy. An error's fingerprint is its type with the types behind it, and your own top frames — never node_modules or node: internals — by function and file, never by line. An anonymous callback is known by the named function it is written in. The message counts only when the frames cannot name the throw site, with every number collapsed: User 42 not found and User 43 not found from one throw site are one Issue.

Group your own way with fingerprint, on a capture or in beforeSend. "{{ default }}" stands for the fingerprint the SDK computed:

captureException(err, { fingerprint: ["{{ default }}", tenantId] }); // split by tenant
captureMessage("Payment provider down", "error", { fingerprint: ["payment-provider-down"] });

init({
  secretKey,
  beforeSend(event) {
    if (event.type === "AbortError") event.fingerprint = ["aborted-request"];
    return event;
  },
});

event.errorSignature is the computed fingerprint; fingerprint wins over it. Fingerprints changed in version 2 of the rules. An Issue filed before you upgrade keeps folding: the SDK also sends the old key, and Fixback moves the Issue onto the new one the first time it sees both.

Express

import express from "express";
import { fixbackRequestContext, fixbackErrorHandler } from "@fixback/node/express";

const app = express();
app.use(fixbackRequestContext()); // opens per-request context (before your routes)
// … your routes …
app.use(fixbackErrorHandler());   // captures errors that reach next(err) (after your routes)

A route in a mounted router is recorded by its full pattern: a router at app.use("/orgs/:org", posts) with posts.get("/posts/:postId") records /orgs/:org/posts/:postId, from the error handler and from a captureException inside the route alike, on Express 4 and 5. If a mount's pattern can't be worked out for certain, the route is left out instead. That happens with a parameter that shares its segment with text (/report-:year), a wildcard, a RegExp mount, or an error from a mounted sub-app that reaches the parent app's error handler.

NestJS

import { Module } from "@nestjs/common";
import { FixbackModule } from "@fixback/node/nestjs";

@Module({ imports: [FixbackModule.forRoot()] })
export class AppModule {}

FixbackModule.forRoot() registers the request-context middleware and a global exception filter. The filter captures what a request throws, then lets Nest's own exception filter produce the response, so your error handling is unchanged. An exception from a microservice (RPC) or GraphQL call is captured too, then left to Nest's default handling for that context. Global filters don't reach WebSocket gateways. A gateway that applies the filter with @UseFilters(FixbackExceptionFilter) has its exceptions captured, and the client still gets Nest's usual exception event.

Attaching a user and tags

The SDK never scrapes user identity. Attach an app-supplied reference and it rides along with the errors captured in its scope, and shows on the Issue:

import { setTag, setUser } from "@fixback/node";

setUser(currentUser.id); // an opaque ref you choose — not a name/email unless you send one
setTag("tenant", currentTenant.slug); // a key/value pair, shown on the Issue as a chip

Inside a request, both belong to that request. Outside one — a queue consumer, a cron job, startup — they set process-wide values that every error carries, and that a request without its own value carries too (a request's value wins for a key both set). setUser(null) clears the scope's user; setTag(key, null) removes a tag.

Tags are bounded as ingest keeps them: a key is up to 32 letters, digits, _, ., : or -; a value is a string, number or boolean of up to 200 characters on one line; an error carries at most 50. A user ref is up to 256 characters. Anything outside those bounds is ignored.

Breadcrumbs

An error carries the lead-up of its own request — what it logged and the calls it made out — and the Issue's Trace tab lists them in order above the failing request:

  • every console call (log, info, warn, error, debug, a failing assert): its level and its message, formatted as the console prints it and cut to 300 characters;
  • every outgoing request through http / https or fetch: its method, its URL without the query string, its status, how long it took, and how it ended (ok, http-5xx, network-error, …). Never a header and never a body. The SDK's own batches to Fixback are not recorded.

Each request records into its own ring, so an error never carries another request's breadcrumbs; work outside a request (a job, a timer) records into one process-wide ring, which only an error outside a request carries. A ring holds the newest maxBreadcrumbs (50 by default, at most 100; 0 records none) for at most five minutes. Requests are read from Node's diagnostics_channel — nothing is patched — and the console wrapper always runs your original method.

init({
  secretKey: process.env.FIXBACK_SECRET_KEY!,
  maxBreadcrumbs: 30,
  capture: { console: true, network: true }, // turn either off with false
  // See each breadcrumb as it is recorded: edit it, or drop it with null.
  beforeBreadcrumb: (crumb) => (crumb.url?.includes("/health") ? null : crumb),
});

Breadcrumbs pass the default scrub like every Trace: URLs lose their query strings and PII in the path, and messages their emails, bearer tokens and long digit runs. Your beforeSend sees them as event.breadcrumbs — edit or empty the list to change what is sent.

The Correlation id

The request-context middleware gives every request one id, from the first source that has it: the trace id of an inbound W3C traceparent header, then x-request-id (or your requestIdHeader), else a freshly minted one. A malformed traceparent is ignored, never partly used. Errors captured during the request carry the id, so when the page runs the Fixback browser SDK — which sends traceparent on its same-origin requests and to the origins you list in its propagateTo — the browser Issue for a failed request and this server's Issue show the same id. The browser SDK sends the sampled flag (01) by default, so a parent-based OpenTelemetry sampler on this server records those requests rather than dropping them; its traceparentSampled: false sends 00 instead. This SDK reads the id, never the flag, so the link holds either way.

Privacy

Private-by-default. The SDK captures the route pattern (never the concrete path with values: a route whose pattern it can't work out is left out), method, status, a correlation id, the app-supplied user ref and tags, where the process runs (service, host name, Node.js version, OS), and the request's breadcrumbs — console messages and outgoing requests' metadata, scrubbed. It never sends Authorization/Cookie headers, environment variables, or query-string values. Request/response bodies are an explicit opt-in, off by default: with captureBodies: true the failing request's bodies ride on its server context — serialized, PII-redacted at the scrub choke point, and dropped (never truncated) when larger than maxBodyBytes (16384 by default, at most 65536) or when they would make their error too large to send in one request. A beforeSend(event) hook lets you redact further or drop an event entirely (return null).

Fails quiet

If Fixback is unreachable or misconfigured, your server keeps running normally — observability never becomes an outage. A capture never throws, the buffer never grows past maxQueueSize, and the SDK's timers never keep your process alive. Quiet is not silent: a refused key is said once on standard error, and debug: true says the rest.