SDKs
JavaScript
Fastify

Fastify Integration

Capture errors and request context from Fastify applications.

Installation

npm install @statly/observe

Setup

Register the Statly plugin:

import Fastify from 'fastify';
import { init } from '@statly/observe';
import { statlyFastifyPlugin } from '@statly/observe/fastify';
 
// Initialize SDK first
init({
  dsn: 'https://[email protected]/your-org',
  environment: 'production',
});
 
const fastify = Fastify();
 
// Register plugin
await fastify.register(statlyFastifyPlugin, {
  // Options
});
 
// Your routes
fastify.get('/', async (request, reply) => {
  return { hello: 'world' };
});
 
await fastify.listen({ port: 3000 });

Plugin Options

interface StatlyFastifyPluginOptions {
  // Capture Fastify validation errors
  captureValidationErrors?: boolean; // Default: true
 
  // Custom filter for which errors to capture
  shouldCapture?: (error: Error, request: FastifyRequest) => boolean;
 
  // Status codes to skip (not capture)
  skipStatusCodes?: number[]; // Default: [400, 401, 403, 404]
}

Examples

// Capture everything including 4xx
await fastify.register(statlyFastifyPlugin, {
  skipStatusCodes: [],
});
 
// Only capture 5xx errors
await fastify.register(statlyFastifyPlugin, {
  skipStatusCodes: [400, 401, 403, 404, 422],
});
 
// Custom filter
await fastify.register(statlyFastifyPlugin, {
  shouldCapture: (error, request) => {
    // Skip health check errors
    if (request.url === '/health') return false;
    // Skip rate limiting
    if (error.statusCode === 429) return false;
    return true;
  },
});

What's Captured

Request Context

The plugin automatically captures:

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

Hooks

The plugin registers these Fastify hooks:

onRequest: Records start time, adds request breadcrumb

onResponse: Adds response breadcrumb with status and duration

setErrorHandler: Captures unhandled errors

Request-Scoped Capture

Use createRequestCapture for request-specific error capture:

import { createRequestCapture } from '@statly/observe/fastify';
 
fastify.get('/api/orders/:id', async (request, reply) => {
  const capture = createRequestCapture(request);
 
  try {
    const order = await getOrder(request.params.id);
    return order;
  } catch (error) {
    // Capture with request context automatically
    capture(error, {
      orderId: request.params.id,
    });
    throw error;
  }
});

With TypeScript

Full TypeScript support:

import Fastify, { FastifyRequest, FastifyReply } from 'fastify';
import { init } from '@statly/observe';
import {
  statlyFastifyPlugin,
  StatlyFastifyPluginOptions,
} from '@statly/observe/fastify';
 
init({
  dsn: process.env.STATLY_DSN!,
  environment: process.env.NODE_ENV,
});
 
const fastify = Fastify({
  logger: true,
});
 
const options: StatlyFastifyPluginOptions = {
  captureValidationErrors: true,
  skipStatusCodes: [400, 401, 403, 404],
};
 
await fastify.register(statlyFastifyPlugin, options);
 
fastify.get<{
  Params: { id: string };
}>('/users/:id', async (request, reply) => {
  const user = await getUser(request.params.id);
  return user;
});

Manual Capture

Capture errors manually with context:

import { captureException, addBreadcrumb } from '@statly/observe';
 
fastify.post('/api/payments', async (request, reply) => {
  addBreadcrumb({
    category: 'payment',
    message: 'Processing payment',
    data: { amount: request.body.amount },
  });
 
  try {
    const result = await processPayment(request.body);
    return result;
  } catch (error) {
    captureException(error, {
      paymentMethod: request.body.method,
      amount: request.body.amount,
    });
 
    reply.status(500);
    return { error: 'Payment failed' };
  }
});

Full Example

import Fastify from 'fastify';
import { init, setUser, addBreadcrumb } from '@statly/observe';
import { statlyFastifyPlugin } from '@statly/observe/fastify';
 
init({
  dsn: process.env.STATLY_DSN,
  environment: process.env.NODE_ENV,
});
 
const fastify = Fastify();
 
await fastify.register(statlyFastifyPlugin, {
  captureValidationErrors: true,
  skipStatusCodes: [400, 401, 403, 404],
});
 
// Authentication hook
fastify.addHook('preHandler', async (request) => {
  if (request.user) {
    setUser({
      id: request.user.id,
      email: request.user.email,
    });
  }
});
 
// Routes
fastify.get('/api/users', async () => {
  return await db.users.findMany();
});
 
fastify.post('/api/users', async (request) => {
  addBreadcrumb({
    category: 'user',
    message: 'Creating user',
  });
 
  return await db.users.create(request.body);
});
 
await fastify.listen({ port: 3000 });