Skip to main content

Monitoring & Observability

Deploying is just the beginning. You need to know when things break, how fast your app is, and why users are leaving.

The Three Pillars​

PillarWhatTools
LogsStructured records of eventsRailway logs, Logtail, Papertrail
MetricsNumeric measurements over timeRailway metrics, Prometheus
TracesRequest lifecycle across servicesOpenTelemetry, Sentry

Structured Logging with Pino​

Plain console.log is hard to search. Structured JSON logs are queryable:

npm install pino pino-pretty
npm install -D @types/pino
// src/lib/logger.ts
import pino from 'pino';

export const logger = pino({
level: process.env.LOG_LEVEL ?? 'info',
...(process.env.NODE_ENV !== 'production' && {
transport: {
target: 'pino-pretty',
options: { colorize: true },
},
}),
});

// NestJS integration
import { Logger } from '@nestjs/common';

@Injectable()
export class PostsService {
private readonly logger = new Logger(PostsService.name);

async create(dto: CreatePostDto) {
this.logger.log(`Creating post: ${dto.title}`);
try {
const post = await this.prisma.post.create({ data: dto });
this.logger.log({ msg: 'Post created', postId: post.id });
return post;
} catch (error) {
this.logger.error({ msg: 'Failed to create post', error });
throw error;
}
}
}

Error Tracking with Sentry​

npm install @sentry/nestjs @sentry/node
# or for Next.js:
npm install @sentry/nextjs
// NestJS
import * as Sentry from '@sentry/nestjs';

Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,
});

// In main.ts — capture unhandled errors
app.useGlobalFilters(new SentryFilter());
// Next.js — sentry.server.config.ts
import * as Sentry from '@sentry/nextjs';

Sentry.init({
dsn: process.env.SENTRY_DSN,
tracesSampleRate: 0.1,
replaysSessionSampleRate: 0.1, // record 10% of sessions
});

Sentry captures:

  • Unhandled exceptions
  • Performance traces
  • User sessions (Session Replay)
  • Custom events: Sentry.captureMessage('Payment failed', { extra: { orderId } })

Health Check Endpoint​

// NestJS with @nestjs/terminus
npm install @nestjs/terminus @nestjs/axios

// src/health/health.module.ts
@Module({
imports: [TerminusModule],
controllers: [HealthController],
})
export class HealthModule {}

// src/health/health.controller.ts
@Controller('health')
export class HealthController {
constructor(
private health: HealthCheckService,
private db: PrismaHealthIndicator,
) {}

@Get()
@HealthCheck()
check() {
return this.health.check([
() => this.db.pingCheck('database'),
]);
}
}

Railway uses GET /health to determine if your service is up.

Uptime Monitoring​

BetterStack (free tier):

  1. Create a monitor pointing to https://your-api.up.railway.app/health
  2. Check interval: 1 minute
  3. Alert via email/Slack if down

Alternatives: Upptime (GitHub-based, free), UptimeRobot, Freshping.

Performance Monitoring​

Next.js — Vercel Analytics​

// app/layout.tsx
import { Analytics } from '@vercel/analytics/react';
import { SpeedInsights } from '@vercel/speed-insights/next';

export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<Analytics />
<SpeedInsights />
</body>
</html>
);
}

Vercel Analytics shows Core Web Vitals per page, per country, per device.

API Response Times​

// Custom middleware to log response times
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
logger.info({
method: req.method,
path: req.path,
status: res.statusCode,
duration,
});
if (duration > 1000) {
logger.warn({ msg: 'Slow request', path: req.path, duration });
}
});
next();
});

Alerting​

Set up alerts so you know before users complain:

  1. Error rate spike → Sentry alert rule
  2. Response time > 2s → Sentry performance alert
  3. Downtime → BetterStack / UptimeRobot
  4. Database connection exhaustion → Supabase dashboard alert

The Production Checklist​

Before going live:

  • NODE_ENV=production set
  • All secrets in platform secrets (not code)
  • Health check endpoint returning 200
  • Sentry DSN configured
  • Uptime monitor pointing to health endpoint
  • Database connection string using pooler (for serverless)
  • Migrations run against production DB
  • Error pages (404, 500) customized
  • Rate limiting on public endpoints
  • CORS configured to allow only your frontend domain