Node.js SDK
Install @errtap/node to capture uncaught exceptions and rejections, report handled errors from Express or any handler, and send logs and user feedback from Node.js.
Install
npm i @errtap/nodeInitialise
Call init as early as possible — before any other imports that might throw:
import { init } from '@errtap/node';
init({
dsn: 'https://et_<key>@<host>/<project>',
environment: 'production',
release: process.env.APP_VERSION,
});init registers uncaughtException and unhandledRejection handlers. Calling it again
replaces them instead of stacking duplicates. On runtimes without process.on (Edge, some
workers) the handlers are skipped and manual capture still works.
Options
| Option | Type | Default | Description |
|---|---|---|---|
dsn | string | required | URL DSN, or a bare et_… key with endpoint |
endpoint | string | derived from the DSN | Full ingest URL override |
environment | string | 'production' | Attached to every event and log |
release | string | none | Used for release health and regression detection |
tags | Record<string, unknown> | none | Attached to every event and log |
exitOnFatal | boolean | true | Exit with code 1 after reporting an uncaught exception |
Fatal errors
Listening for uncaughtException normally stops Node from crashing. With exitOnFatal: true
the SDK keeps Node's behaviour: it prints the error, waits up to 2 seconds for the report, then
exits with code 1. Unhandled rejections are treated the same way unless the process runs with
--unhandled-rejections=warn or none. Set exitOnFatal: false when a framework or
supervisor owns the process; @errtap/next/server does this for you.
Express integration
The package does not install Express middleware. Capture in your existing error middleware and pass request context on that call:
import express from 'express';
import { init, captureException } from '@errtap/node';
init({ dsn: 'https://et_<key>@<host>/<project>' });
const app = express();
// ... your routes here ...
app.use((err, req, res, next) => {
void captureException(err, {
url: req.originalUrl,
tags: { method: req.method },
});
next(err);
});
app.listen(3000);Capture helpers
import { captureException, captureMessage } from '@errtap/node';
try {
await doSomethingRisky();
} catch (err) {
captureException(err, {
tags: { job: 'invoice-sync' },
context: { invoiceId: inv.id },
});
}
captureMessage('stripe webhook skipped', { level: 'warning' });API
captureException(error, options?)
| Field | Type | Description |
|---|---|---|
tags | Record<string, unknown> | Merged with init-level tags |
context | Record<string, unknown> | Arbitrary extra data |
level | string | error (default), warning, info, fatal |
fingerprint | string | Overrides automatic grouping |
release | string | Overrides init-level release |
user | { id?, email?, name? } | Attaches user to this event |
Once init has run with a valid DSN, both functions return a promise that resolves when the
send finishes and never rejects. Before init, or with a DSN that couldn't be parsed, they
return undefined, so use await rather than .then(). await them in short-lived scripts
so the event leaves before the process exits.
captureMessage(message, options?)
Same options shape as captureException.
captureFeedback(feedback)
Sends a user's note, linked to eventId or, by default, lastEventId() — the last error this
process sent. See User feedback.
Use logger.debug, logger.info, logger.warn/warning, and logger.error
for structured logs. Dynamic request/user context belongs on each call; the Node
SDK deliberately does not keep global breadcrumbs or mutable request context.
Release & environment
init({
dsn: 'https://et_<key>@<host>/<project>',
release: process.env.APP_VERSION ?? 'dev',
environment: process.env.NODE_ENV ?? 'development',
});ErrTap uses release to detect regressions: if an issue was resolved while v1.4.2 was the latest registered release and reappears in v1.4.3, it is reopened, provided v1.4.3 is registered as a later deploy with POST /ingest/release or on the Releases page. See Regression behaviour.