How to Monitor Your tRPC API with Vigilmon
tRPC gives you end-to-end type safety between your TypeScript backend and frontend-but it doesn't give you uptime monitoring. Your tRPC server needs external health checks the same as any other API. This guide shows how to add a health endpoint to a tRPC server and monitor it with Vigilmon.
tRPC Architecture Quick Overview
tRPC APIs typically run on:
- Express or Fastify adapter
- Next.js API routes
- Standalone HTTP server
Each deployment target has a slightly different health endpoint approach.
Health Endpoint with Express Adapter
` ypescript
// server.ts
import express from "express";
import * as trpcExpress from "@trpc/server/adapters/express";
import { appRouter } from "./router";
import { createContext } from "./context";
const app = express();
// Health endpoint BEFORE tRPC middleware
app.get("/health", (req, res) => {
res.json({
status: "ok",
uptime: process.uptime(),
timestamp: new Date().toISOString(),
});
});
app.use(
"/trpc",
trpcExpress.createExpressMiddleware({
router: appRouter,
createContext,
})
);
app.listen(3000, () => {
console.log("Server running on port 3000");
});
`
The health endpoint at /health lives outside tRPC routing, so it works even if tRPC fails to initialize.
Health Endpoint with Fastify Adapter
` ypescript
// server.ts
import Fastify from "fastify";
import { fastifyTRPCPlugin } from "@trpc/server/adapters/fastify";
import { appRouter } from "./router";
import { createContext } from "./context";
const server = Fastify({ logger: true });
// Health check
server.get("/health", async () => ({
status: "ok",
uptime: process.uptime(),
timestamp: new Date().toISOString(),
}));
server.register(fastifyTRPCPlugin, {
prefix: "/trpc",
trpcOptions: { router: appRouter, createContext },
});
const start = async () => {
await server.listen({ port: 3000, host: "0.0.0.0" });
};
start();
`
Health Route in Next.js (App Router)
` ypescript
// app/api/health/route.ts
import { NextResponse } from "next/server";
export async function GET() {
return NextResponse.json({
status: "ok",
timestamp: new Date().toISOString(),
uptime: process.uptime(),
});
}
`
Vigilmon monitors https://yourdomain.com/api/health - outside tRPC's /api/trpc namespace.
Health Route in Next.js (Pages Router)
` ypescript
// pages/api/health.ts
import type { NextApiRequest, NextApiResponse } from "next";
export default function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== "GET") {
return res.status(405).json({ error: "Method not allowed" });
}
res.status(200).json({
status: "ok",
timestamp: new Date().toISOString(),
});
}
`
Deep Health Check with Database
` ypescript
// server/health.ts
import { db } from "./db"; // Your Prisma/Drizzle/etc. client
export interface HealthResult {
status: "ok" | "error";
timestamp: string;
uptime: number;
components: {
database: { status: "ok" | "error"; latencyMs?: number; error?: string };
};
}
export async function getHealth(): Promise<[HealthResult, number]> {
const start = Date.now();
try {
await db.SELECT 1;
const dbLatency = Date.now() - start;
return [
{
status: "ok",
timestamp: new Date().toISOString(),
uptime: process.uptime(),
components: { database: { status: "ok", latencyMs: dbLatency } },
},
200,
];
} catch (error) {
return [
{
status: "error",
timestamp: new Date().toISOString(),
uptime: process.uptime(),
components: {
database: {
status: "error",
error: error instanceof Error ? error.message : "Unknown",
},
},
},
503,
];
}
}
// Use in Express:
app.get("/health", async (req, res) => {
const [result, statusCode] = await getHealth();
res.status(statusCode).json(result);
});
`
tRPC Health Procedure (Optional)
Some teams add a health procedure inside tRPC for client-side checks:
` ypescript
// router/health.ts
import { publicProcedure, router } from "../trpc";
export const healthRouter = router({
ping: publicProcedure.query(() => ({
status: "ok" as const,
timestamp: new Date().toISOString(),
})),
});
// app/router.ts
import { router } from "../trpc";
import { healthRouter } from "./health";
import { userRouter } from "./user";
export const appRouter = router({
health: healthRouter,
user: userRouter,
});
`
This lets your frontend check health via rpc.health.ping.query(), but don't use this for Vigilmon-Vigilmon needs a plain HTTP endpoint, not a tRPC procedure call.
Setting Up Vigilmon
- Go to vigilmon.online
- Click Add Monitor
-
Configure:
- URL: https://yourdomain.com/health
- Method: GET
- Expected status: 200
- Interval: 1 minute
Add SSL monitoring for your domain
Validate Your Health Endpoint First
`ash
Test locally
curl http://localhost:3000/health
Test in production
curl https://yourdomain.com/health
`
Expected response:
json
{
"status": "ok",
"uptime": 1234,
"timestamp": "2026-08-03T12:00:00.000Z"
}
Type Safety for Health Responses
Keep your health response type-safe:
` ypescript
// shared/types.ts
export type HealthStatus = "ok" | "degraded" | "error";
export interface HealthResponse {
status: HealthStatus;
timestamp: string;
uptime?: number;
components?: Record;
}
`
Use this type in both your Express handler and your Next.js route to keep them consistent.
Summary
- Add a /health endpoint outside tRPC's namespace
- Use Express/Fastify route or Next.js API route-not a tRPC procedure
- Include database connectivity check for complete health visibility
- Return 200 for healthy, 503 for degraded
- Monitor externally with Vigilmon
Set up uptime monitoring for your tRPC API at vigilmon.online - free for 3 monitors.
Top comments (0)