JustAnalytics for Express.js

Step-by-step guide to add analytics, error tracking, and APM to your Express.js app

JustAnalytics for Express.js

Add distributed tracing, error tracking, structured logging, and infrastructure metrics to your Express application.

Time to data: 3 minutes

Prerequisites#


Install the SDK

npm install @justanalyticsapp/node

Initialize the SDK

Initialize JustAnalytics before creating your Express app. The SDK automatically instruments all incoming HTTP requests and outgoing HTTP calls.

// src/index.ts
import JA from '@justanalyticsapp/node';
import express from 'express';

// Initialize FIRST — before any other imports that make HTTP calls
JA.init({
  siteId: process.env.JA_SITE_ID!,
  apiKey: process.env.JA_API_KEY!,
  serviceName: 'api-server',
  environment: process.env.NODE_ENV || 'development',
});

const app = express();
app.use(express.json());

// Register Express middleware for route-pattern span names
app.use(JA.expressMiddleware());

Add your routes

Every incoming request is automatically traced. The Express middleware updates span names with route patterns (e.g., GET /api/users/:id instead of GET /api/users/42).

app.get('/api/users/:id', async (req, res) => {
  // This request is automatically traced
  const user = await db.users.findUnique({ where: { id: req.params.id } });
  res.json(user);
});

app.post('/api/orders', async (req, res) => {
  // Nested spans for granular tracing
  const order = await JA.startSpan('create-order', async (span) => {
    span.setAttribute('order.items', req.body.items.length);
    return await db.orders.create({ data: req.body });
  });
  res.status(201).json(order);
});

Add error handling

Add an Express error handler to capture unhandled errors:

// Error-handling middleware (must be registered last)
app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
  JA.captureException(err, {
    tags: { route: req.route?.path || req.path },
    extra: { method: req.method, query: req.query },
  });

  res.status(500).json({ error: 'Internal server error' });
});

Add graceful shutdown

Flush pending data before your server exits:

const server = app.listen(3000, () => {
  console.log('Server running on port 3000');
});

process.on('SIGTERM', async () => {
  await JA.close();
  server.close();
});

Verify Your Setup

Send a request to any endpoint (e.g., curl http://localhost:3000/api/users/1), then check the Traces tab. You should see spans with Express route patterns.

Open Dashboard

Advanced Features#

Custom spans#

app.get('/api/reports/:id', async (req, res) => {
  const report = await JA.startSpan('generate-report', async (span) => {
    span.setAttribute('report.id', req.params.id);

    const data = await JA.startSpan('fetch-data', async () => {
      return await db.reports.findUnique({ where: { id: req.params.id } });
    });

    const pdf = await JA.startSpan('render-pdf', async () => {
      return await renderPdf(data);
    });

    return pdf;
  });

  res.contentType('application/pdf').send(report);
});

User identification#

// After authentication middleware
app.use((req, res, next) => {
  if (req.user) {
    JA.setUser({ id: req.user.id, email: req.user.email });
  }
  next();
});

Structured logging#

JA.logger.info('Order created', { orderId: order.id, total: order.total });
JA.logger.warn('Rate limit approaching', { userId: user.id, remaining: 5 });
JA.logger.error('Payment declined', { orderId: order.id, reason: 'card_declined' });

Winston integration#

import JA from '@justanalyticsapp/node';
import { JustAnalyticsTransport } from '@justanalyticsapp/node/winston';
import winston from 'winston';

const logger = winston.createLogger({
  transports: [
    new winston.transports.Console(),
    new JustAnalyticsTransport({ level: 'warn' }),
  ],
});

Custom metrics#

JA.recordMetric('custom.active_connections', server.connections);
JA.recordMetric('custom.request_queue_size', queue.length, { queue: 'api' });

Environment variables#

| Variable | Description | Required | |----------|-------------|----------| | JA_SITE_ID | Your site ID from the dashboard | Yes | | JA_API_KEY | API key (starts with ja_sk_) | Yes | | NODE_ENV | Used as the default environment tag | No |

Complete example#

import JA from '@justanalyticsapp/node';
import express from 'express';

JA.init({
  siteId: process.env.JA_SITE_ID!,
  apiKey: process.env.JA_API_KEY!,
  serviceName: 'api-server',
  environment: process.env.NODE_ENV,
});

const app = express();
app.use(express.json());
app.use(JA.expressMiddleware());

app.get('/api/health', (req, res) => {
  res.json({ status: 'ok' });
});

app.get('/api/users/:id', async (req, res) => {
  const user = await db.users.findUnique({ where: { id: req.params.id } });
  if (!user) return res.status(404).json({ error: 'Not found' });
  res.json(user);
});

app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
  JA.captureException(err);
  res.status(500).json({ error: 'Internal server error' });
});

const server = app.listen(3000);
process.on('SIGTERM', async () => { await JA.close(); server.close(); });