DocumentationNode.js

Node.js

Track exceptions raised on your Node backend. Server errors land in the same Nohmo project as your frontend errors, so a 500 in your API sits next to the JS error it caused in the browser — same Errors page, same grouping, same daily digest.

Ships inside the nohmo package you already install, with no runtime dependencies — Node built-ins only. It never imports React or anything browser-side, because an error reporter should not be the thing that breaks the app it is watching.

Install

bash
npm install nohmo

The server code lives on its own entry point, nohmo/server, so nothing server-side can end up in a browser bundle.

Express

Register the error handler last, after every route and router. Express only shows an error handler what was registered before it — this is the opposite of the Django middleware, which goes first.

javascript
const express = require('express')
const { init, expressErrorHandler } = require('nohmo/server')
init({
projectId: process.env.NOHMO_PROJECT_ID,
apiKey: process.env.NOHMO_API_KEY,
environment: process.env.NODE_ENV, // optional
release: process.env.GIT_SHA, // optional - ties errors to a deploy
})
const app = express()
app.get('/orders/:id', getOrder)
// ... all your routes ...
app.use(expressErrorHandler()) // LAST

It reports the error and then passes it straight on, so your own error page or JSON response is unchanged. To skip errors that are expected traffic rather than defects:

javascript
app.use(expressErrorHandler({
shouldReport: (err) => err.status !== 404,
}))

Any other framework

Wrap a node:httphandler. This covers Connect, Koa's raw layer, a Next.js custom server, or a hand-rolled server.

javascript
const http = require('node:http')
const { init, wrapHandler } = require('nohmo/server')
init({ projectId: '...', apiKey: process.env.NOHMO_API_KEY })
http.createServer(wrapHandler(async (req, res) => {
// anything thrown here is reported, then rethrown
})).listen(3000)

Reporting by hand

javascript
const { captureException, captureMessage } = require('nohmo/server')
try {
await charge(order)
} catch (err) {
captureException(err, {
request: { path: '/checkout' },
extra: { orderId: order.id },
})
}
captureMessage('nightly reconciliation finished with 3 mismatches')

Short-lived processes

Events ship on a timer that is deliberately unref'd, so it can never hold your process open — which also means it may not fire on the way out. A cron job, a Lambda or a one-off script should flush explicitly.

javascript
const { flush } = require('nohmo/server')
const ok = await flush(5000)
// false means events were dropped - worth checking in a job you care about

Options

OptionDefaultWhat it does
environment'production'Tags every event
release''Ties errors to a deploy
serverNameos.hostname()Groups errors per instance
sampleRate1Fraction of errors sent
dedupWindow5Seconds before an identical error is sent again
queueSize1000Bounded - drops rather than growing without limit
batchSize50Events per request
flushInterval5Seconds before a partial batch ships
sendDefaultPiifalseInclude headers, query strings and user email
endpointhttps://www.nohmo.in/...Override the ingest URL — only for self-hosted or tests
debugfalseVerbose logging

Privacy

sendDefaultPii is off by default: no headers, no query strings and no user email leave your server. Turn it on and everything is still run through a credential scrubber that redacts Authorization, Cookie, Set-Cookie, X-API-Key and any key containing secret, password, token, api_key, private_key or credential — at any nesting depth.

userIdis always sent, since an id is not PII on its own. The user's email is sent only with sendDefaultPii: true.

Behaviour under load

The queue is bounded and drops the oldest events when full. A crash loop generates errors faster than any network can ship them, and an unbounded queue there is a memory leak that ends in an OOM kill — the SDK becoming the outage. Identical errors are deduplicated within dedupWindow, failed sends retry with exponential backoff and jitter, and a Retry-After from the server is honoured.

If the ingest host cannot be reached at all — blocked egress, a TLS failure, bad DNS — the SDK warns once, and once again on recovery. Silent failure is the one thing an error reporter must never do.

bash
nohmo: cannot reach https://www.nohmo.in/api/tracker/track/ (...) - events are being dropped.

Note: Nothing the SDK does can throw into your request path. captureException swallows its own failures, and flush() resolves false rather than rejecting.

Grouping

Errors are grouped by type, message and the first stack frame that names your code. V8 puts built-ins at the top of a Node stack — at JSON.parse (<anonymous>) — so Nohmo skips those and groups on the real call site instead. Two endpoints failing inside the same built-in stay separate issues.