Deploy your Fastify applications with confidence using these production-ready strategies.
Environment Configuration
Environment Variables
javascript
// config/index.js
require('dotenv').config();
module.exports = {
port: parseInt(process.env.PORT || '3000'),
host: process.env.HOST || '0.0.0.0',
nodeEnv: process.env.NODE_ENV || 'development',
databaseUrl: process.env.DATABASE_URL,
jwtSecret: process.env.JWT_SECRET,
redisUrl: process.env.REDIS_URL,
};
// .env.example
PORT=3000
HOST=0.0.0.0
NODE_ENV=production
DATABASE_URL=postgresql://...
JWT_SECRET=your-secret-key
REDIS_URL=redis://localhost:6379Configuration Validation
javascript
// config/validate.js
const Joi = require('joi');
const envSchema = Joi.object({
NODE_ENV: Joi.string()
.valid('development', 'production', 'test')
.required(),
PORT: Joi.number().default(3000),
DATABASE_URL: Joi.string().required(),
JWT_SECRET: Joi.string().min(32).required(),
}).unknown();
const { value: envVars, error } = envSchema.validate(process.env);
if (error) {
throw new Error(`Config validation error: ${error.message}`);
}
module.exports = {
env: envVars.NODE_ENV,
port: envVars.PORT,
databaseUrl: envVars.DATABASE_URL,
jwtSecret: envVars.JWT_SECRET,
};Logging
Structured Logging with Pino
javascript
const pino = require('pino');
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
transport:
process.env.NODE_ENV === 'development'
? {
target: 'pino-pretty',
options: {
colorize: true,
translateTime: 'HH:MM:ss Z',
ignore: 'pid,hostname',
},
}
: undefined,
});
const fastify = require('fastify')({
logger,
});
// Usage
fastify.log.info({ userId: 123 }, 'User logged in');
fastify.log.error({ error: err }, 'Database connection failed');Log Aggregation
javascript
// Send logs to external service
const pino = require('pino');
const pinoElastic = require('pino-elasticsearch');
const ecsStream = pinoElastic({
node: process.env.ELASTICSEARCH_URL,
index: 'fastify-logs',
});
const logger = pino({}, ecsStream);Security
Helmet Headers
javascript
const fastify = require('fastify')();
fastify.register(require('@fastify/helmet'), {
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", 'data:', 'https:'],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
},
});Rate Limiting
javascript
fastify.register(require('@fastify/rate-limit'), {
max: 100,
timeWindow: '1 minute',
cache: 10000,
allowList: ['127.0.0.1'],
skipOnError: true,
addHeaders: {
'x-ratelimit-limit': true,
'x-ratelimit-remaining': true,
'x-ratelimit-reset': true,
},
});Input Sanitization
javascript
fastify.register(require('@fastify/sanitize'));
fastify.post('/comment', {
schema: {
body: {
type: 'object',
properties: {
text: { type: 'string' },
},
},
},
preHandler: fastify.sanitize({
text: { escape: true, trim: true },
}),
}, async (request, reply) => {
const { text } = request.body;
// Text is now sanitized
});Performance Optimization
Clustering
javascript
const fastify = require('fastify')();
const cluster = require('cluster');
const os = require('os');
if (cluster.isMaster) {
const numCPUs = os.cpus().length;
console.log(`Master ${process.pid} is running`);
console.log(`Forking ${numCPUs} workers...`);
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} died`);
cluster.fork();
});
} else {
const start = async () => {
try {
await fastify.listen({ port: 3000, host: '0.0.0.0' });
console.log(`Worker ${process.pid} started`);
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
start();
}Caching
javascript
fastify.register(require('@fastify/caching'), {
privacy: 'private',
expiresIn: 300, // 5 minutes
});
fastify.get('/data', {
cache: true,
}, async (request, reply) => {
return { data: 'expensive computation' };
});Response Compression
javascript
fastify.register(require('@fastify/compress'), {
encodings: ['gzip', 'deflate'],
threshold: 1024, // Only compress if > 1KB
});Health Checks
javascript
fastify.register(require('@fastify/healthcheck'), {
healthcheckUrl: '/health',
schema: {
response: {
200: {
type: 'object',
properties: {
status: { type: 'string' },
timestamp: { type: 'string' },
},
},
},
},
});
// Custom health check
fastify.get('/health/ready', async (request, reply) => {
const checks = {
database: false,
redis: false,
};
try {
await fastify.db.query('SELECT 1');
checks.database = true;
} catch (err) {
// Database not ready
}
try {
await fastify.redis.ping();
checks.redis = true;
} catch (err) {
// Redis not ready
}
const allHealthy = Object.values(checks).every(Boolean);
return reply
.code(allHealthy ? 200 : 503)
.send({ status: allHealthy ? 'ready' : 'not ready', checks });
});Monitoring
Prometheus Metrics
javascript
const promClient = require('prom-client');
const fastify = require('fastify')();
const register = new promClient.Registry();
promClient.collectDefaultMetrics({ register });
const httpRequestDuration = new promClient.Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'status_code'],
registers: [register],
});
fastify.addHook('onResponse', async (request, reply) => {
const duration = reply.getResponseTime() / 1000;
httpRequestDuration.observe(
{
method: request.method,
route: request.routeOptions.url,
status_code: reply.statusCode,
},
duration
);
});
fastify.get('/metrics', async (request, reply) => {
reply.type('text/plain').send(await register.metrics());
});Graceful Shutdown
javascript
const fastify = require('fastify')();
const gracefulShutdown = async (signal) => {
fastify.log.info(`Received ${signal}, shutting down gracefully...`);
setTimeout(() => {
fastify.log.error('Forced shutdown after timeout');
process.exit(1);
}, 30000);
await fastify.close();
process.exit(0);
};
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));Docker Deployment
Dockerfile
dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./package.json
ENV NODE_ENV=production
ENV PORT=3000
EXPOSE 3000
CMD ["node", "dist/server.js"]Docker Compose
yaml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://db:5432/app
depends_on:
- db
- redis
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: app
POSTGRES_USER: user
POSTGRES_PASSWORD: password
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
volumes:
postgres_data:
redis_data:Kubernetes Deployment
Deployment YAML
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: fastify-app
spec:
replicas: 3
selector:
matchLabels:
app: fastify
template:
metadata:
labels:
app: fastify
spec:
containers:
- name: fastify
image: your-registry/fastify-app:latest
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: "production"
- name: PORT
value: "3000"
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health/ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: fastify-service
spec:
selector:
app: fastify
ports:
- protocol: TCP
port: 80
targetPort: 3000
type: LoadBalancerBest Practices
DOs
- Use environment variables
- Implement proper logging
- Add health checks
- Monitor performance
- Use HTTPS
- Implement rate limiting
- Graceful shutdown
- Test before deploying
DON'Ts
- Hardcode secrets
- Skip logging
- Ignore errors
- Deploy without tests
- Forget about security
- Skip monitoring
- Use default configurations
Conclusion
Deploying Fastify to production requires proper configuration, security, monitoring, and performance optimization. Follow these best practices to ensure your application runs reliably in production. Always test thoroughly and monitor your application's health!