Back to Journal
DevOps

Complete Guide to Monitoring & Observability with Typescript

A comprehensive guide to implementing Monitoring & Observability using Typescript, covering architecture, code examples, and production-ready patterns.

Muneer Puthiya Purayil 15 min read

TypeScript powers the dashboard, alerting, and integration layer of modern monitoring stacks. While Node.js isn't the choice for building high-throughput metric ingestion, it excels at building monitoring dashboards, webhook integrations, and custom alerting services that connect observability data to team workflows.

Application Instrumentation

Prometheus Metrics with prom-client

typescript
1import { Counter, Histogram, Gauge, Registry, collectDefaultMetrics } from "prom-client";
2 
3const register = new Registry();
4collectDefaultMetrics({ register, prefix: "app_" });
5 
6export const httpRequestDuration = new Histogram({
7 name: "http_request_duration_seconds",
8 help: "Duration of HTTP requests",
9 labelNames: ["method", "route", "status"] as const,
10 buckets: [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5],
11 registers: [register],
12});
13 
14export const httpRequestsTotal = new Counter({
15 name: "http_requests_total",
16 help: "Total HTTP requests",
17 labelNames: ["method", "route", "status"] as const,
18 registers: [register],
19});
20 
21export const activeConnections = new Gauge({
22 name: "http_active_connections",
23 help: "Active HTTP connections",
24 registers: [register],
25});
26 

OpenTelemetry for Node.js

typescript
1import { NodeSDK } from "@opentelemetry/sdk-node";
2import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
3import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-grpc";
4import { PrometheusExporter } from "@opentelemetry/exporter-prometheus";
5 
6const sdk = new NodeSDK({
7 traceExporter: new OTLPTraceExporter({ url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT }),
8 metricReader: new PrometheusExporter({ port: 9090 }),
9 instrumentations: [getNodeAutoInstrumentations({
10 "@opentelemetry/instrumentation-http": { ignoreIncomingPaths: ["/healthz", "/metrics"] },
11 "@opentelemetry/instrumentation-express": { enabled: true },
12 "@opentelemetry/instrumentation-pg": { enabled: true },
13 })],
14});
15 
16sdk.start();
17process.on("SIGTERM", () => sdk.shutdown());
18 

Structured Logging

typescript
1import pino from "pino";
2 
3const logger = pino({
4 level: process.env.LOG_LEVEL || "info",
5 formatters: { level: (label: string) => ({ level: label }) },
6 mixin: () => ({
7 service: process.env.SERVICE_NAME || "unknown",
8 environment: process.env.NODE_ENV || "development",
9 }),
10 redact: ["req.headers.authorization", "password", "secret"],
11});
12 
13export default logger;
14 

Need a second opinion on your DevOps pipelines architecture?

I run free 30-minute strategy calls for engineering teams tackling this exact problem.

Book a Free Call

Custom Alerting Service

typescript
1import Fastify from "fastify";
2 
3interface AlertRule {
4 name: string;
5 query: string;
6 threshold: number;
7 comparison: "gt" | "lt";
8 duration: string;
9 channels: string[];
10}
11 
12interface AlertState {
13 firing: boolean;
14 firedAt?: Date;
15 value?: number;
16}
17 
18class AlertManager {
19 private rules: AlertRule[] = [];
20 private states: Map<string, AlertState> = new Map();
21 private prometheusUrl: string;
22 
23 constructor(prometheusUrl: string) {
24 this.prometheusUrl = prometheusUrl;
25 }
26 
27 addRule(rule: AlertRule): void {
28 this.rules.push(rule);
29 this.states.set(rule.name, { firing: false });
30 }
31 
32 async evaluate(): Promise<void> {
33 for (const rule of this.rules) {
34 const value = await this.queryPrometheus(rule.query);
35 const shouldFire = rule.comparison === "gt" ? value > rule.threshold : value < rule.threshold;
36 const state = this.states.get(rule.name)!;
37 
38 if (shouldFire && !state.firing) {
39 state.firing = true;
40 state.firedAt = new Date();
41 state.value = value;
42 await this.notify(rule, value);
43 } else if (!shouldFire && state.firing) {
44 state.firing = false;
45 await this.notifyResolved(rule);
46 }
47 }
48 }
49 
50 private async queryPrometheus(query: string): Promise<number> {
51 const resp = await fetch(
52 `${this.prometheusUrl}/api/v1/query?query=${encodeURIComponent(query)}`
53 );
54 const data = await resp.json();
55 return parseFloat(data.data?.result?.[0]?.value?.[1] || "0");
56 }
57 
58 private async notify(rule: AlertRule, value: number): Promise<void> {
59 for (const channel of rule.channels) {
60 if (channel.startsWith("slack:")) {
61 await this.sendSlackAlert(channel.replace("slack:", ""), rule, value);
62 } else if (channel.startsWith("pagerduty:")) {
63 await this.sendPagerDutyAlert(channel.replace("pagerduty:", ""), rule, value);
64 }
65 }
66 }
67 
68 private async sendSlackAlert(webhook: string, rule: AlertRule, value: number): Promise<void> {
69 await fetch(webhook, {
70 method: "POST",
71 headers: { "Content-Type": "application/json" },
72 body: JSON.stringify({
73 text: `🚨 Alert: ${rule.name}\nValue: ${value} (threshold: ${rule.threshold})`,
74 }),
75 });
76 }
77 
78 private async sendPagerDutyAlert(routingKey: string, rule: AlertRule, value: number): Promise<void> {
79 await fetch("https://events.pagerduty.com/v2/enqueue", {
80 method: "POST",
81 headers: { "Content-Type": "application/json" },
82 body: JSON.stringify({
83 routing_key: routingKey,
84 event_action: "trigger",
85 payload: {
86 summary: `${rule.name}: ${value} exceeds threshold ${rule.threshold}`,
87 severity: "critical",
88 source: "custom-alert-manager",
89 },
90 }),
91 });
92 }
93 
94 private async notifyResolved(rule: AlertRule): Promise<void> {
95 for (const channel of rule.channels) {
96 if (channel.startsWith("slack:")) {
97 await fetch(channel.replace("slack:", ""), {
98 method: "POST",
99 headers: { "Content-Type": "application/json" },
100 body: JSON.stringify({ text: `✅ Resolved: ${rule.name}` }),
101 });
102 }
103 }
104 }
105}
106 

Conclusion

TypeScript monitoring infrastructure focuses on the human interface layer — dashboards, alerting logic, webhook integrations, and SLA reporting. The prom-client library and OpenTelemetry Node.js SDK handle standard instrumentation, while TypeScript's type safety prevents the configuration errors that plague untyped monitoring scripts. For teams building on Node.js, the monitoring ecosystem is mature enough for production use with proper V8 heap configuration and structured logging.

FAQ

Need expert help?

Building with CI/CD pipelines?

I help teams ship production-grade systems. From architecture review to hands-on builds.

Muneer Puthiya Purayil

SaaS Architect & AI Systems Engineer. 10+ years shipping production infrastructure across fintech, automotive, e-commerce, and healthcare.

Engage

Start a
Conversation.

For teams building at scale: SaaS platforms, agentic AI systems, and enterprise mobile infrastructure. Scope and fit are evaluated before any engagement begins.

Limited availability · Q3 / Q4 2026