Automatic error tracking middleware for Express.js applications.
Set up Express middleware to automatically capture uncaught errors in your application.
/**
* Set up Express error handler middleware
* @param posthog - PostHog client instance
* @param app - Express application or router with use() method
*/
function setupExpressErrorHandler(
posthog: PostHog,
app: { use: (middleware: ExpressMiddleware | ExpressErrorMiddleware) => unknown }
): void;Usage Examples:
import express from 'express';
import { PostHog, setupExpressErrorHandler } from 'posthog-node';
const app = express();
const client = new PostHog('phc_your_api_key');
// Your routes
app.get('/api/users', async (req, res) => {
const users = await getUsers();
res.json(users);
});
// Setup error handler (must be after all routes)
setupExpressErrorHandler(client, app);
// Start server
app.listen(3000);The Express error handler middleware:
import express from 'express';
import { PostHog, setupExpressErrorHandler } from 'posthog-node';
const app = express();
const client = new PostHog('phc_your_api_key', {
host: 'https://app.posthog.com'
});
// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Routes
app.get('/', (req, res) => {
res.json({ status: 'ok' });
});
app.get('/api/users/:id', async (req, res) => {
const user = await getUser(req.params.id);
if (!user) {
throw new Error('User not found');
}
res.json(user);
});
app.post('/api/orders', async (req, res) => {
const order = await createOrder(req.body);
res.json(order);
});
// Setup PostHog error handler (after routes, before other error handlers)
setupExpressErrorHandler(client, app);
// Custom error handler (optional, after PostHog handler)
app.use((error, req, res, next) => {
console.error('Error:', error);
res.status(error.status || 500).json({
error: error.message || 'Internal server error'
});
});
// Start server
const server = app.listen(3000, () => {
console.log('Server listening on port 3000');
});
// Graceful shutdown
process.on('SIGTERM', async () => {
server.close();
await client.shutdown();
process.exit(0);
});The order of middleware is important in Express. PostHog error handler should be:
// 1. Basic middleware
app.use(express.json());
app.use(cors());
// 2. Routes
app.get('/api/users', getUsers);
app.post('/api/orders', createOrder);
// 3. PostHog error handler
setupExpressErrorHandler(client, app);
// 4. Custom error handlers
app.use(customErrorHandler);The middleware automatically extracts status codes from various error formats:
// Standard Express error
const error = new Error('Not found');
error.status = 404; // Captured
// HTTP errors (http-errors package)
const error = createError(404, 'Not found');
error.statusCode = 404; // Captured
// Custom error formats
const error = new Error('Bad request');
error.status_code = 400; // Captured
// Boom errors
const error = Boom.badRequest('Invalid input');
error.output.statusCode = 400; // CapturedThe setupExpressErrorHandler uses personless processing by default. For user-specific error tracking, use manual error capture:
import { PostHog, setupExpressErrorHandler } from 'posthog-node';
const client = new PostHog('phc_your_api_key');
// Manual error handling with user context
app.get('/api/protected', async (req, res) => {
try {
const data = await getProtectedData(req.user.id);
res.json(data);
} catch (error) {
// Capture with user ID
client.captureException(error, req.user.id, {
endpoint: '/api/protected',
method: req.method
});
throw error; // Will also be caught by setupExpressErrorHandler
}
});
// Automatic error handling (personless)
setupExpressErrorHandler(client, app);For async route handlers, ensure errors are properly caught:
// Using express-async-errors
import 'express-async-errors';
app.get('/api/users', async (req, res) => {
// Errors automatically caught
const users = await getUsers();
res.json(users);
});
setupExpressErrorHandler(client, app);
// Or wrap in try-catch
app.get('/api/users', async (req, res, next) => {
try {
const users = await getUsers();
res.json(users);
} catch (error) {
next(error); // Pass to error handler
}
});// Main app
const app = express();
const client = new PostHog('phc_your_api_key');
// API router
const apiRouter = express.Router();
apiRouter.get('/users', getUsers);
apiRouter.post('/orders', createOrders);
// Setup error handler on router
setupExpressErrorHandler(client, apiRouter);
// Mount router
app.use('/api', apiRouter);
// Main app error handler
setupExpressErrorHandler(client, app);You can use PostHog alongside other error tracking services:
import { PostHog, setupExpressErrorHandler } from 'posthog-node';
import * as Sentry from '@sentry/node';
const client = new PostHog('phc_your_api_key');
// Sentry error handler
app.use(Sentry.Handlers.errorHandler());
// PostHog error handler
setupExpressErrorHandler(client, app);
// Custom error handler
app.use((error, req, res, next) => {
res.status(500).json({ error: 'Internal error' });
});To filter which errors are sent to PostHog, use manual error capture instead:
// Don't use setupExpressErrorHandler, use custom middleware
app.use((error, req, res, next) => {
// Only capture 500 errors
if (error.status >= 500) {
client.captureException(error, req.user?.id, {
endpoint: req.path,
method: req.method,
status: error.status
});
}
next(error);
});const client = new PostHog('phc_your_api_key', {
disabled: process.env.NODE_ENV === 'development'
});
// Only setup in production
if (process.env.NODE_ENV === 'production') {
setupExpressErrorHandler(client, app);
}
// Or use both with different configs
if (process.env.NODE_ENV === 'development') {
app.use((error, req, res, next) => {
console.error('Development error:', error);
next(error);
});
} else {
setupExpressErrorHandler(client, app);
}import request from 'supertest';
import express from 'express';
import { PostHog, setupExpressErrorHandler } from 'posthog-node';
describe('Express error handling', () => {
let app;
let client;
beforeEach(() => {
app = express();
client = new PostHog('test_key', { disabled: true });
app.get('/error', () => {
throw new Error('Test error');
});
setupExpressErrorHandler(client, app);
});
it('should handle errors', async () => {
const response = await request(app).get('/error');
expect(response.status).toBe(500);
});
});// Good
app.get('/api/users', getUsers);
app.post('/api/orders', createOrders);
setupExpressErrorHandler(client, app);
// Bad
setupExpressErrorHandler(client, app);
app.get('/api/users', getUsers); // Won't be caught// Combine automatic and manual tracking
app.post('/api/checkout', async (req, res) => {
try {
await processCheckout(req.user.id, req.body);
res.json({ success: true });
} catch (error) {
// Manual: with user context
client.captureException(error, req.user.id, {
step: 'checkout',
cart_total: req.body.total
});
throw error; // Automatic: will also be caught
}
});
setupExpressErrorHandler(client, app);const server = app.listen(3000);
process.on('SIGTERM', async () => {
server.close();
await client.shutdown(5000);
process.exit(0);
});Use manual captureException() for errors that need user context or additional properties:
app.post('/api/payment', async (req, res) => {
try {
await processPayment(req.body);
res.json({ success: true });
} catch (error) {
// Capture with rich context
client.captureException(error, req.user.id, {
payment_method: req.body.method,
amount: req.body.amount,
order_id: req.body.orderId
});
res.status(500).json({ error: 'Payment failed' });
}
});captureException()