Framework Guide

TypeScript API Monitoring

The best way to monitor a TypeScript API is to add type-safe health check endpoints and connect them to an external monitoring service. This guide covers Express, Fastify, and NestJS with fully typed response interfaces.

Why TypeScript APIs Need External Monitoring

TypeScript catches type errors at compile time, but runtime failures are a different story. Your types won't protect you from network timeouts, database connection drops, or memory leaks in production. External monitoring fills this gap.

  • Runtime type mismatches -- External API responses that don't match your interfaces can cause silent failures
  • Dependency outages -- Database, cache, and third-party service failures cascade through your app
  • Memory leaks -- TypeScript doesn't prevent closures and event listeners from leaking memory
  • Deployment regressions -- A passing build doesn't guarantee a working production deployment

Type-Safe Health Check Interfaces

Use these TypeScript interfaces as the foundation for all your health check endpoints. They enforce consistent response shapes across every framework.

interface DependencyCheck {
  status: 'ok' | 'error';
  responseTime: number;
  message?: string;
}

interface HealthCheckResponse {
  status: 'healthy' | 'unhealthy';
  timestamp: string;
  uptime: number;
  version: string;
  checks: Record<string, DependencyCheck>;
}

// Use this for simple liveness probes
interface LivenessResponse {
  status: 'healthy';
  timestamp: string;
}

Express + TypeScript Health Endpoint

The most common TypeScript API setup. Use typed request and response objects from @types/express for full type safety.

import express, { Request, Response } from 'express';

const app = express();

// Simple liveness check
app.get('/health', (_req: Request, res: Response<LivenessResponse>) => {
  res.json({
    status: 'healthy',
    timestamp: new Date().toISOString(),
  });
});

// Comprehensive readiness check
app.get('/ready', async (_req: Request, res: Response<HealthCheckResponse>) => {
  const health = await buildHealthCheck();
  const statusCode = health.status === 'healthy' ? 200 : 503;
  res.status(statusCode).json(health);
});

Fastify + TypeScript Health Endpoint

Fastify has first-class TypeScript support with built-in schema validation. Use generics on route handlers for end-to-end type safety.

import Fastify from 'fastify';

const app = Fastify({ logger: true });

app.get<{ Reply: HealthCheckResponse }>('/health', async (_request, reply) => {
  const health = await buildHealthCheck();
  const statusCode = health.status === 'healthy' ? 200 : 503;
  reply.status(statusCode).send(health);
});

// With Fastify JSON Schema for runtime validation
app.get('/health', {
  schema: {
    response: {
      200: {
        type: 'object',
        properties: {
          status: { type: 'string', enum: ['healthy'] },
          timestamp: { type: 'string' },
          uptime: { type: 'number' },
          version: { type: 'string' },
          checks: { type: 'object' },
        },
        required: ['status', 'timestamp'],
      },
    },
  },
}, async (_request, reply) => {
  const health = await buildHealthCheck();
  reply.send(health);
});

NestJS Health Endpoint

NestJS provides the @nestjs/terminus module specifically for health checks. This is the recommended approach for NestJS applications.

import { Controller, Get } from '@nestjs/common';
import {
  HealthCheck,
  HealthCheckService,
  TypeOrmHealthIndicator,
  HttpHealthIndicator,
} from '@nestjs/terminus';

@Controller('health')
export class HealthController {
  constructor(
    private health: HealthCheckService,
    private db: TypeOrmHealthIndicator,
    private http: HttpHealthIndicator,
  ) {}

  @Get()
  @HealthCheck()
  check() {
    return this.health.check([
      () => this.db.pingCheck('database'),
      () => this.http.pingCheck('api', 'https://api.example.com'),
    ]);
  }
}

Install with: npm install @nestjs/terminus @nestjs/axios

Database Health Checks with Typed Responses

Use this reusable function to check any database connection with proper TypeScript types, timeouts, and error handling.

async function checkDependency(
  name: string,
  checkFn: () => Promise<void>,
  timeoutMs = 5000
): Promise<DependencyCheck> {
  const start = Date.now();
  try {
    await Promise.race([
      checkFn(),
      new Promise<never>((_, reject) =>
        setTimeout(() => reject(new Error('timeout')), timeoutMs)
      ),
    ]);
    return {
      status: 'ok',
      responseTime: Date.now() - start,
    };
  } catch (error) {
    return {
      status: 'error',
      responseTime: Date.now() - start,
      message: error instanceof Error ? error.message : 'Unknown error',
    };
  }
}

// Usage with any database client
async function buildHealthCheck(): Promise<HealthCheckResponse> {
  const dbCheck = await checkDependency('database', async () => {
    await db.query('SELECT 1');
  });

  const redisCheck = await checkDependency('redis', async () => {
    await redis.ping();
  });

  const checks = { database: dbCheck, redis: redisCheck };
  const isHealthy = Object.values(checks).every(c => c.status === 'ok');

  return {
    status: isHealthy ? 'healthy' : 'unhealthy',
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
    version: process.env.npm_package_version || '1.0.0',
    checks,
  };
}

Comparing Health Check Approaches

Approach Type Safety Runtime Validation Best For
Interfaces only Compile-time None Simple APIs, internal services
Zod schemas Compile + runtime Full Public APIs, strict contracts
Fastify schemas Compile + runtime JSON Schema Fastify projects
NestJS Terminus Full (decorators) Built-in NestJS projects

Set Up Monitoring with UptimeSignal

Once your health endpoint is deployed, connect it to UptimeSignal for continuous external monitoring. UptimeSignal pings your endpoint on a schedule and alerts you immediately when it returns a non-200 status or times out.

  1. Sign up at app.uptimesignal.io -- free tier includes 25 monitors
  2. Add your health endpoint URL -- e.g., https://api.example.com/health
  3. Set your check interval -- 5 minutes on free, 1 minute on Pro ($15/mo)
  4. Configure alerts -- get email notifications when your API goes down or recovers

UptimeSignal verifies both the HTTP status code (expects 200) and response time. If your buildHealthCheck() function detects an unhealthy dependency and returns 503, UptimeSignal will immediately alert you.

Best Practices

  • Separate liveness from readiness -- Use /health for simple "is the process alive" checks and /ready for dependency checks
  • Add timeouts to every dependency check -- Use Promise.race with a 5-second timeout to prevent hanging
  • Return 503 for unhealthy states -- Monitoring services like UptimeSignal key on status codes
  • Keep health endpoints unauthenticated -- Monitoring services need direct access without credentials
  • Use strict TypeScript config -- Enable strict: true in tsconfig.json to catch potential null/undefined issues in health check logic
  • Log health check traffic separately -- Exclude /health from your request logger to avoid noise

Common TypeScript API Production Issues

Runtime Type Mismatches

TypeScript types are erased at runtime. External API responses, database results, and environment variables can all have unexpected shapes. Use Zod or runtime validation in your health checks to catch these before they cascade.

Compilation Succeeds, Deployment Fails

A successful tsc build doesn't guarantee your app starts correctly. Missing environment variables, wrong Node.js version, or missing native dependencies only surface at runtime. External monitoring catches post-deployment failures immediately.

Decorator Metadata Issues (NestJS)

NestJS relies on reflect-metadata and emitDecoratorMetadata. Misconfigured tsconfig or missing polyfills can cause dependency injection to fail silently, leaving health endpoints unregistered.

Connection Pool Exhaustion

ORMs like TypeORM and Prisma manage connection pools that can fill up under load. Include a database ping in your readiness check with a timeout to detect pool exhaustion before users see errors.

Frequently Asked Questions

What TypeScript interfaces should I use for health check responses?
Define a HealthCheckResponse interface with status: 'healthy' | 'unhealthy', timestamp, and an optional checks record mapping dependency names to their status and response time. Use a discriminated union for the status field so consumers can narrow the type. Return HTTP 200 for healthy and 503 for unhealthy.
How do I add health checks to a NestJS application?
Use the @nestjs/terminus module. Install it with npm install @nestjs/terminus, create a HealthController with a @Get('/health') endpoint, and inject HealthCheckService along with indicators like TypeOrmHealthIndicator or HttpHealthIndicator. Terminus handles response formatting and HTTP status codes automatically.
Should I use Zod or TypeScript interfaces for health check validation?
Use both. Define TypeScript interfaces for compile-time safety and Zod schemas for runtime validation. Zod can infer TypeScript types with z.infer, giving you a single source of truth. This is especially useful in Fastify where you can use Zod schemas for both route validation and type inference.
How do I type database health checks in TypeScript?
Create a DependencyCheck interface with status: 'ok' | 'error', responseTime: number, and an optional message: string. Write an async function that returns this type, wrapping the database query in a try-catch with a timeout using Promise.race. This gives you type-safe error handling and consistent response shapes across all dependency checks.
How often should I monitor my TypeScript API?
For production TypeScript APIs, check every 1-5 minutes. UptimeSignal's free tier checks every 5 minutes, which catches most outages within minutes. The Pro tier supports 1-minute intervals for mission-critical services. Avoid intervals shorter than 30 seconds to prevent unnecessary load on your health check dependencies.

Start monitoring your TypeScript API

Get alerted when your TypeScript endpoints fail. Setup takes 30 seconds.

25 monitors free. No credit card required.

Related Guides