SDK & Framework Guides

Node.js Error Tracking with Express

Capture unhandled Node.js exceptions and Express route errors with request context and stack traces. A production-ready setup in a few lines.

Node.js applications fail silently. A database query times out in a background job, an async handler throws, or a third-party API goes down mid-request — and unless you're capturing it, your error goes unnoticed until a user complains or revenue drops. Node.js error tracking ensures that every unhandled exception and rejection lands in a dashboard where your team can see it, group it, and fix it before it becomes a production incident.

This guide shows you how to set up production-grade error tracking in an Express app using the Sentry SDK, pointed at LightTrace. By the end, you'll capture synchronous errors, async exceptions, and middleware failures with full stack traces and request context.

Install and initialize the SDK

Start by installing the Sentry SDK for Node.js:

npm install @sentry/node

Initialize it early in your app's startup, before you define routes or start accepting requests:

const Sentry = require("@sentry/node");

Sentry.init({
  dsn: "https://<key>@your-lighttrace-host/1",
  environment: process.env.NODE_ENV,
  release: "api@1.2.3",
  tracesSampleRate: 1.0,
});

const express = require("express");
const app = express();

// Your routes and middleware go here.

That single init call installs global handlers for uncaught exceptions and unhandled promise rejections. But Express needs explicit middleware to catch errors thrown inside route handlers.

Capture Express route errors with middleware

Express doesn't automatically forward thrown errors to a global handler. You need to install Sentry's request-handling middleware and error-catching middleware in the right order.

Request middleware captures request context (URL, method, headers, user):

const Sentry = require("@sentry/node");
const express = require("express");

const app = express();

// Must be early, before all routes
app.use(Sentry.Handlers.requestHandler());

// Your routes
app.get("/api/users/:id", (req, res) => {
  throw new Error("Intentional error for testing");
});

// Error middleware: must be last
app.use(Sentry.Handlers.errorHandler());

app.listen(3000);

The error handler middleware must be registered after all your routes and other error-catching middleware. If you register it too early, it won't catch errors from later routes.

The request handler captures the HTTP method, URL, headers, query parameters, and user info. The error handler catches any exception thrown in a route and sends it to LightTrace with all that context attached.

Handle async errors in Express

Express's default error handler doesn't catch errors thrown in async route handlers or inside Promise chains. You have two options: wrap async routes or create a helper.

Option 1: Wrap with try/catch

app.get("/api/posts/:id", async (req, res) => {
  try {
    const post = await db.posts.findById(req.params.id);
    res.json(post);
  } catch (error) {
    // Sentry will capture this
    throw error;
  }
});

Option 2: Use an async handler wrapper

Create a helper that catches async errors and forwards them to the error middleware:

const asyncHandler = (fn) => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};

app.get("/api/posts/:id", asyncHandler(async (req, res) => {
  const post = await db.posts.findById(req.params.id);
  res.json(post);
}));

The wrapper is cleaner at scale because you don't repeat try/catch in every async route. Errors are automatically passed to the error handler middleware, which sends them to LightTrace.

Add context to speed up debugging

Raw stack traces are a start. The real debugging speed comes from context — who hit the error, which release, what were they doing?

Set the user and add breadcrumbs at meaningful moments:

app.use((req, res, next) => {
  if (req.user) {
    Sentry.setUser({
      id: req.user.id,
      email: req.user.email,
      username: req.user.username,
    });
  }
  next();
});

app.post("/api/checkout", asyncHandler(async (req, res) => {
  Sentry.addBreadcrumb({
    category: "checkout",
    message: "User initiated checkout",
    level: "info",
  });

  const order = await stripe.createCharge(req.body);

  Sentry.addBreadcrumb({
    category: "checkout",
    message: "Charge created",
    level: "info",
  });

  res.json(order);
}));

Now every error arrives with the breadcrumb trail that led to it. If a charge fails, you see the exact sequence of events — initiated checkout, called Stripe API, got an error — instead of a bare stack trace.

Monitor background jobs and queues

Background jobs throw errors just like route handlers, but outside the HTTP request/response cycle. If you're using a queue like Bull or RabbitMQ, you need to tell Sentry about job context:

const Queue = require("bull");
const emailQueue = new Queue("emails");

emailQueue.process(async (job) => {
  Sentry.captureException(new Error("Job ID: " + job.id));
  
  try {
    await sendEmail(job.data);
  } catch (error) {
    Sentry.captureException(error, {
      tags: { queue: "emails", jobId: job.id },
    });
    throw error;
  }
});

By tagging jobs with their queue name and ID, you can later filter errors in LightTrace to see if a particular queue is having trouble.

Performance monitoring for slow endpoints

Beyond errors, distributed tracing shows you which endpoints are slow. Enable transaction tracing to see database queries, HTTP calls, and middleware overhead:

Sentry.init({
  dsn: "https://<key>@your-lighttrace-host/1",
  tracesSampleRate: 0.1, // Trace 10% of transactions (adjust to your volume)
  environment: process.env.NODE_ENV,
  release: "api@1.2.3",
});

// Enable automatic instrumentation for common libraries
require("@sentry/integrations").Http;
require("@sentry/integrations").Postgres;

This captures the time spent in database queries, HTTP requests to external services, and other operations. With tracing enabled, you can see slow database queries that are dragging down your API responses — and reduce API latency by fixing the bottleneck.

Test and iterate

To verify your setup is working, throw an intentional error:

app.get("/test-error", (req, res) => {
  throw new Error("Test error to verify Sentry is working");
});

Visit http://localhost:3000/test-error, and within seconds you should see the error grouped in LightTrace. If you don't, check:

  • That your DSN is correct (copy it from your LightTrace project settings)
  • That your environment variable or NODE_ENV is set
  • That the error middleware is registered after all your routes

Use different releases for staging and production (release: "api@1.2.3-staging" vs release: "api@1.2.3"). Then you can see which release introduced a regression and roll back faster.

What's next

Error tracking catches the exceptions; error tracking best practices show you how to use them. Set up alert rules to page your team on new errors or spikes. If you're running multiple services, cross-project tracing stitches errors and spans across your whole system.

Start tracking errors in minutes

Point the Sentry SDK at LightTrace and see your first Node.js errors grouped and traced in minutes — free up to 5,000 events a month.

Error tracking isn't optional for production Node.js. Silent failures are worse than crashes because you don't know they're happening. With the SDK initialized and middleware in place, every unhandled exception becomes a line in your dashboard, and every developer on your team can see it.

Fix your next production error faster

Point any Sentry SDK at LightTrace — free up to 5,000 events/month.