SDKs
JavaScript
Express.js

Express Integration

Capture errors and request context from Express.js applications.

Installation

The Express integration is included in the main package:

npm install @statly/observe

Setup

import express from 'express';
import { init } from '@statly/observe';
import { requestHandler, expressErrorHandler } from '@statly/observe/express';
 
// Initialize SDK first
init({
  dsn: 'https://[email protected]/your-org',
  environment: 'production',
});
 
const app = express();
 
// Add request handler FIRST (before routes)
app.use(requestHandler());
 
// Your routes
app.get('/', (req, res) => {
  res.send('Hello World');
});
 
app.get('/error', (req, res) => {
  throw new Error('Test error');
});
 
// Add error handler LAST (after routes)
app.use(expressErrorHandler());
 
app.listen(3000);

Request Handler

The requestHandler() middleware:

  • Records request start time for duration calculation
  • Adds HTTP request breadcrumb
  • Sets transaction name from route
  • Captures user from req.user (if using Passport.js or similar)
app.use(requestHandler());

The handler adds context to req.statlyContext:

{
  transactionName: 'GET /api/users/:id',
  startTime: 1705320000000
}

Error Handler

The expressErrorHandler() captures unhandled errors:

app.use(expressErrorHandler());

Options

app.use(expressErrorHandler({
  shouldHandleError: (error) => {
    // Only capture 5xx errors
    return error.status >= 500;
  },
}));

What's Captured

For each error, the handler captures:

Request Context:

  • HTTP method and URL
  • Query parameters
  • Headers (sanitized)
  • Request body (sanitized)
  • Client IP address

Automatic Sanitization:

Headers filtered:

  • authorization
  • cookie
  • x-api-key
  • x-auth-token

Body fields filtered:

  • password
  • secret
  • token
  • apiKey, api_key
  • credit_card
  • ssn

Manual Capture

You can also capture errors manually:

import { captureException } from '@statly/observe';
 
app.post('/api/users', async (req, res, next) => {
  try {
    const user = await createUser(req.body);
    res.json(user);
  } catch (error) {
    captureException(error, {
      route: '/api/users',
      method: 'POST',
      userId: req.body.email,
    });
    next(error);
  }
});

With Async Handlers

For async route handlers, wrap with try/catch:

app.get('/api/data', async (req, res, next) => {
  try {
    const data = await fetchData();
    res.json(data);
  } catch (error) {
    next(error); // Error handler will capture it
  }
});

Or use a wrapper:

const asyncHandler = (fn) => (req, res, next) =>
  Promise.resolve(fn(req, res, next)).catch(next);
 
app.get('/api/data', asyncHandler(async (req, res) => {
  const data = await fetchData();
  res.json(data);
}));

Full Example

import express from 'express';
import { init, setUser, addBreadcrumb } from '@statly/observe';
import { requestHandler, expressErrorHandler } from '@statly/observe/express';
 
init({
  dsn: process.env.STATLY_DSN,
  environment: process.env.NODE_ENV,
});
 
const app = express();
app.use(express.json());
app.use(requestHandler());
 
// Set user after authentication
app.use((req, res, next) => {
  if (req.user) {
    setUser({
      id: req.user.id,
      email: req.user.email,
    });
  }
  next();
});
 
// Add custom breadcrumbs
app.post('/api/orders', async (req, res, next) => {
  try {
    addBreadcrumb({
      category: 'order',
      message: 'Creating order',
      data: { items: req.body.items.length },
    });
 
    const order = await createOrder(req.body);
    res.json(order);
  } catch (error) {
    next(error);
  }
});
 
app.use(expressErrorHandler({
  shouldHandleError: (error) => error.status >= 500,
}));
 
app.listen(3000);