Skip to main content

Overview

Monitoring is critical for maintaining a healthy production API. This guide covers health checks, logging, error tracking, and performance monitoring.

Health Check Endpoint

Overview

The health check endpoint provides real-time status of the API (src/server.ts:112):
app.get('/health', (req, res) => {
  res.status(200).json({
    status: 'ok',
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
    environment: process.env.NODE_ENV || 'development',
  });
});

Usage

curl https://api.tresacontafy.com/health

Response Format

{
  "status": "ok",
  "timestamp": "2026-03-07T12:00:00.000Z",
  "uptime": 3600.5,
  "environment": "production"
}
status
string
Health status: ok if server is running
timestamp
string
Current server timestamp (ISO 8601)
uptime
number
Server uptime in seconds since last restart
environment
string
Current environment: development, production, or test

Integration

Use services like UptimeRobot, Pingdom, or Better Uptime:
  1. Add health check URL: https://api.yourdomain.com/health
  2. Set interval: 5 minutes (recommended)
  3. Alert on: Status code ≠ 200
  4. Alert channels: Email, Slack, SMS

Logging

Pino Logger

Tresa Contafy uses Pino for high-performance logging (src/utils/logger.util.ts):
import pino from 'pino';

const isProduction = process.env.NODE_ENV === 'production';

const logger = pino({
  level: process.env.LOG_LEVEL || (isProduction ? 'info' : 'debug'),
  ...(isProduction
    ? {
        formatters: {
          level: (label) => ({ level: label }),
        },
        timestamp: pino.stdTimeFunctions.isoTime,
        serializers: {
          err: pino.stdSerializers.err,
          req: pino.stdSerializers.req,
          res: pino.stdSerializers.res,
        },
      }
    : {
        transport: {
          target: 'pino-pretty',
          options: {
            colorize: true,
            translateTime: 'HH:MM:ss Z',
            ignore: 'pid,hostname',
          },
        },
      }),
});

Log Levels

When: Application crash, unrecoverable errorsExample:
logger.fatal({ err: error }, 'Database connection failed');
process.exit(1);
When: Error conditions that need attentionExample:
logger.error({ err, userId }, 'Failed to process invoice');
When: Warning conditions, potential issuesExample:
logger.warn('FRONTEND_URL not configured in production');
When: Informational messages (default in production)Example:
logger.info({ port, environment }, 'Server started');
When: Debug information (default in development)Example:
logger.debug({ profileId }, 'Fetching profile data');
When: Very detailed trace informationExample:
logger.trace({ query }, 'Database query executed');

Log Format

Structured JSON for log aggregation tools:
{
  "level": "info",
  "time": "2026-03-07T12:00:00.000Z",
  "msg": "Server started",
  "port": 3001,
  "environment": "production"
}

Setting Log Level

Control verbosity via environment variable:
# Production: Only info and above
LOG_LEVEL=info

# Development: Debug and above
LOG_LEVEL=debug

# Troubleshooting: Everything
LOG_LEVEL=trace

HTTP Request Logging

Morgan middleware logs all HTTP requests (src/server.ts:58):
app.use(morgan(process.env.NODE_ENV === 'production' ? 'combined' : 'dev'));
Production format (combined):
::1 - - [07/Mar/2026:12:00:00 +0000] "GET /api/profiles HTTP/1.1" 200 1234 "-" "Mozilla/5.0..."
Development format (dev):
GET /api/profiles 200 123.456 ms - 1234

Error Logging

Centralized error handler logs all errors (src/server.ts:144):
app.use((err, req, res, next) => {
  logger.error(
    {
      err,
      method: req.method,
      path: req.path,
      ip: req.ip,
      userAgent: req.get('user-agent'),
    },
    'Unhandled error'
  );
  
  // Send response
});
Error log example:
{
  "level": "error",
  "time": "2026-03-07T12:00:00.000Z",
  "msg": "Unhandled error",
  "err": {
    "type": "Error",
    "message": "Database connection failed",
    "stack": "Error: Database connection failed\n    at ..."
  },
  "method": "GET",
  "path": "/api/profiles",
  "ip": "192.168.1.1"
}

Error Tracking

Process Error Handlers

Global error handlers catch unhandled errors (src/index.ts):
process.on('unhandledRejection', (reason, promise) => {
  logger.error({ reason, promise }, 'Unhandled Rejection');
  
  if (process.env.NODE_ENV === 'production') {
    // Optional: Exit and let process manager restart
    // process.exit(1);
  }
});

process.on('uncaughtException', (error) => {
  logger.fatal({ err: error }, 'Uncaught Exception');
  
  if (process.env.NODE_ENV === 'production') {
    process.exit(1);
  }
});

Graceful Shutdown

Handle shutdown signals gracefully (src/index.ts:28):
const gracefulShutdown = (signal) => {
  logger.info({ signal }, 'Graceful shutdown initiated');
  process.exit(0);
};

process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));

External Error Tracking

Integrate with error tracking services:
pnpm add @sentry/node
import * as Sentry from '@sentry/node';

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  environment: process.env.NODE_ENV,
  tracesSampleRate: 1.0,
});

app.use(Sentry.Handlers.requestHandler());
app.use(Sentry.Handlers.errorHandler());

Log Aggregation

Centralized Logging

Forward logs to centralized systems:
Railway automatically collects logs:
  1. Go to your service
  2. Click “Deployments” → “View Logs”
  3. Filter by level, time, or search text

Performance Monitoring

Response Time Monitoring

Morgan logs include response time:
GET /api/profiles 200 123.456 ms - 1234
Watch for slow requests (>1000ms) and optimize.

Database Query Performance

Enable Sequelize logging in development:
const sequelize = new Sequelize(databaseUrl, {
  logging: (sql, timing) => {
    logger.debug({ sql, timing }, 'Database query');
  },
});

APM (Application Performance Monitoring)

pnpm add newrelic
Create newrelic.js:
exports.config = {
  app_name: ['Tresa Contafy API'],
  license_key: process.env.NEW_RELIC_LICENSE_KEY,
  logging: {
    level: 'info',
  },
};
Require at app start:
import 'newrelic';
import app from './server';

Metrics

Custom Metrics Endpoint

Create a metrics endpoint for monitoring:
app.get('/metrics', (req, res) => {
  res.json({
    uptime: process.uptime(),
    memory: process.memoryUsage(),
    cpu: process.cpuUsage(),
    timestamp: new Date().toISOString(),
  });
});
Response:
{
  "uptime": 3600.5,
  "memory": {
    "rss": 123456789,
    "heapTotal": 98765432,
    "heapUsed": 87654321,
    "external": 1234567
  },
  "cpu": {
    "user": 123456,
    "system": 78901
  },
  "timestamp": "2026-03-07T12:00:00.000Z"
}

Prometheus Integration

For Prometheus metrics:
pnpm add prom-client
import client from 'prom-client';

const register = new client.Registry();
client.collectDefaultMetrics({ register });

app.get('/metrics', async (req, res) => {
  res.set('Content-Type', register.contentType);
  res.end(await register.metrics());
});

Alerting

Alert Conditions

Set up alerts for:
Health check failures - 3+ consecutive failures
High error rate - >1% of requests fail
Slow response time - Average >1000ms
High memory usage - >80% of available memory
Database connection errors - Any connection failures
Rate limit exceeded - Unusual spike in rate limiting

Alert Channels

Email

Critical alerts to on-call team

Slack

Real-time notifications to dev channel

SMS

High-priority alerts for immediate response

PagerDuty

Incident management and escalation

Monitoring Checklist

✓ Health check endpoint configured
✓ Uptime monitoring active (UptimeRobot, Pingdom)
✓ Structured logging with Pino
✓ Log level set appropriately (info in production)
✓ Error tracking service integrated (Sentry, Rollbar)
✓ Centralized log aggregation (Datadog, CloudWatch)
✓ Performance monitoring (APM)
✓ Alerts configured for critical conditions
✓ Graceful shutdown handlers implemented
✓ Database query performance monitored

Next Steps

Security

Review security configuration and best practices

Production Deployment

Complete production deployment guide

Build docs developers (and LLMs) love