A production-ready example demonstrating Fastify 5 REST API with TypeScript, PostgreSQL, Redis, background jobs (BullMQ), and comprehensive OpenTelemetry instrumentation for end-to-end observability.
| Component | Version | Status | Notes |
|---|---|---|---|
| Node.js | 24.x | Active | Latest LTS |
| TypeScript | 5.x | Latest | Strict mode enabled |
| Fastify | 5.x | Latest | Latest stable |
| PostgreSQL | 18 | Active | Alpine variant |
| Redis | 8.x | Active | For BullMQ job queue |
| Drizzle ORM | 0.45.x | Latest | Type-safe SQL |
| BullMQ | 5.x | Active | Background job processing |
| Pino | 10.x | Active | Fast JSON logging |
| OpenTelemetry | 0.211.0 | Latest | SDK Node + auto-instrumentation |
Why This Stack: Demonstrates Fastify 5 with TypeScript for high-performance APIs, PostgreSQL with Drizzle ORM for type-safe database access, Redis/BullMQ for background jobs, Pino for structured logging, and OpenTelemetry for complete observability across all components.
Modular architecture with clear separation of concerns:
- API Layer: Fastify routes with
/api/*prefix, CORS, Helmet security - Security: Rate limiting, JWT authentication, bcrypt password hashing
- Validation: JSON Schema validation with Ajv (Fastify native)
- Data: PostgreSQL with Drizzle ORM, migration support
- Background Jobs: BullMQ with Redis for async notification processing
- Observability: OpenTelemetry automatic + custom instrumentation
Graceful Shutdown: Coordinated shutdown via Node.js signal handlers.
- HTTP requests and responses (Fastify routes)
- PostgreSQL database queries (pg instrumentation)
- Redis commands (IORedis instrumentation)
- Distributed trace propagation (W3C Trace Context)
- Traces: Business spans for auth, CRUD, favorites, background jobs
- Attributes:
user.id,user.email_domain,article.id,article.slug - Metrics: Prometheus metrics at
/metricsendpoint - Logs: Pino structured logging with trace correlation
The article creation flow demonstrates end-to-end trace propagation:
POST /api/articles (HTTP endpoint)
└── article.create (custom span)
├── pg.query INSERT (database)
└── job.enqueue.article-created (queue job)
└── job.article-created (worker, linked via trace context)
- Docker & Docker Compose - For running services
- base14 Scout Account - For viewing traces
- Node.js 24+ (optional) - For local development
git clone https://github.com/base-14/examples.git
cd examples/nodejs/fastify-postgresCreate a .env file with your Scout credentials:
cat > .env << EOF
SCOUT_ENDPOINT=https://your-tenant.base14.io:4318
SCOUT_CLIENT_ID=your_client_id
SCOUT_CLIENT_SECRET=your_client_secret
SCOUT_TOKEN_URL=https://your-tenant.base14.io/oauth/token
SCOUT_ENVIRONMENT=development
EOFdocker compose up --buildThis starts:
- Fastify application on port 3000
- BullMQ worker for background jobs
- PostgreSQL on port 5432
- Redis on port 6379
- OpenTelemetry Collector on ports 4317/4318
./scripts/test-api.sh./scripts/verify-scout.shThis script:
- Generates telemetry by exercising all API endpoints
- Triggers background jobs (article-created, article-favorited)
- Verifies Prometheus metrics are being collected
- Shows expected traces in Scout
- Log into your base14 Scout dashboard
- Navigate to TraceX
- Filter by service:
fastify-postgres-app - Look for the
article.createtrace to see propagation to worker
| Method | Endpoint | Description | Auth |
|---|---|---|---|
GET |
/health |
Health check | No |
GET |
/metrics |
Prometheus metrics | No |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
POST |
/api/register |
Register user | No |
POST |
/api/login |
Login user | No |
GET |
/api/user |
Get profile | Yes |
PUT |
/api/user |
Update profile | Yes |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
GET |
/api/articles |
List articles | Optional |
POST |
/api/articles |
Create article | Yes |
GET |
/api/articles/:slug |
Get article | Optional |
PUT |
/api/articles/:slug |
Update (owner) | Yes |
DELETE |
/api/articles/:slug |
Delete (owner) | Yes |
POST |
/api/articles/:slug/favorite |
Favorite article | Yes |
DELETE |
/api/articles/:slug/favorite |
Unfavorite article | Yes |
| Variable | Description | Required |
|---|---|---|
SCOUT_ENDPOINT |
base14 Scout OTLP endpoint | Yes |
SCOUT_CLIENT_ID |
Scout OAuth2 client ID | Yes |
SCOUT_CLIENT_SECRET |
Scout OAuth2 client secret | Yes |
SCOUT_TOKEN_URL |
Scout OAuth2 token URL | Yes |
| Variable | Description | Default |
|---|---|---|
NODE_ENV |
Environment | development |
PORT |
Application port | 3000 |
DATABASE_URL |
PostgreSQL connection | (required) |
REDIS_URL |
Redis connection | redis://localhost:6379 |
JWT_SECRET |
JWT signing secret | (required) |
JWT_EXPIRES_IN |
JWT token expiration | 7d |
OTEL_SERVICE_NAME |
Service name in traces | fastify-postgres-app |
OTEL_EXPORTER_OTLP_ENDPOINT |
OTLP collector | http://localhost:4318 |
HTTP Spans (automatic):
- Span name:
GET /api/articles,POST /api/register, etc. - Attributes:
http.method,http.route,http.status_code
Database Spans (automatic):
- Span name:
pg.query - Attributes:
db.system=postgresql,db.statement
Redis Spans (automatic):
- Span name:
redis-GET,redis-SET, etc. - Attributes:
db.system=redis
Custom Business Spans:
| Span Name | Description |
|---|---|
user.register |
User registration |
user.login |
User login |
article.create |
Create article |
article.update |
Update article |
article.delete |
Delete article |
article.favorite |
Favorite article |
article.unfavorite |
Unfavorite article |
job.enqueue.* |
Enqueue background job |
job.article-created |
Process article-created job |
job.article-favorited |
Process article-favorited job |
Custom Attributes:
user.id- User IDuser.email_domain- Email domain (privacy-safe)article.id- Article IDarticle.slug- Article slug
Prometheus metrics available at /metrics:
http_requests_total- HTTP request counter by method, route, statushttp_request_duration_seconds- Request duration histogramprocess_*- Node.js process metrics
Pino structured logging with trace correlation:
{
"level": 30,
"time": 1706371200000,
"traceId": "abc123...",
"spanId": "def456...",
"msg": "Article created",
"articleId": 1
}npm install
npm run build
npm run startnpm test # Unit tests (Vitest)
npm run test:watch # Watch mode
npm run test:coverage # Coverage report
./scripts/test-api.sh # API smoke test
./scripts/verify-scout.sh # Scout integration test# Build and start all services
docker compose up --build
# Stop services
docker compose down
# View logs
docker compose logs -f app
docker compose logs -f worker
# Rebuild after code changes
docker compose up --build app worker| Service | URL | Purpose |
|---|---|---|
| Fastify API | http://localhost:3000 | Main application |
| Health Check | http://localhost:3000/health | Service health |
| Metrics | http://localhost:3000/metrics | Prometheus metrics |
| PostgreSQL | localhost:5432 |
Database |
| Redis | localhost:6379 |
Job queue backend |
| OTel Collector | http://localhost:4318 | Telemetry ingestion |
| OTel Health | http://localhost:13133 | Collector health |
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/sdk-node": "^0.211.0",
"@opentelemetry/auto-instrumentations-node": "^0.69.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.211.0",
"@opentelemetry/exporter-metrics-otlp-http": "^0.211.0",
"@opentelemetry/exporter-logs-otlp-http": "^0.211.0",
"@opentelemetry/resources": "^2.4.0",
"@opentelemetry/semantic-conventions": "^1.39.0",
"@fastify/otel": "^0.16.0"Telemetry is initialized in src/telemetry.ts and must be imported before all other modules:
- NodeSDK with OTLP HTTP exporters for traces, metrics, and logs
@fastify/otelplugin for Fastify-specific span enrichment (replaces default auto-instrumentation for Fastify)- Health and metrics endpoints filtered from tracing
- Graceful shutdown on SIGTERM
-
Check collector logs:
docker compose logs otel-collector
-
Verify Scout credentials in
.env -
Check collector health:
curl http://localhost:13133/health
-
Check worker logs:
docker compose logs worker
-
Check Redis connection:
docker exec fastify-postgres-redis-1 redis-cli ping
-
Check PostgreSQL health:
docker compose logs postgres
-
Verify PostgreSQL is ready:
docker exec fastify-postgres-postgres-1 pg_isready -U postgres
-
Check TypeScript compilation:
npm run build
-
View application logs:
docker compose logs app