Auto-generated tile from GitHub (10 skills)
92
94%
Does it follow best practices?
Impact
92%
1.16xAverage score across 44 eval scenarios
Advisory
Suggest reviewing before use
Use close-with-grace for proper shutdown handling:
import Fastify from 'fastify';
import closeWithGrace from 'close-with-grace';
const app = Fastify({ logger: true });
// Register plugins and routes
await app.register(import('./plugins/index.js'));
await app.register(import('./routes/index.js'));
// Graceful shutdown handler
closeWithGrace({ delay: 10000 }, async ({ signal, err }) => {
if (err) {
app.log.error({ err }, 'Server closing due to error');
} else {
app.log.info({ signal }, 'Server closing due to signal');
}
await app.close();
});
// Start server
await app.listen({
port: parseInt(process.env.PORT || '3000', 10),
host: '0.0.0.0',
});
app.log.info(`Server listening on ${app.server.address()}`);Implement comprehensive health checks:
app.get('/health', async () => {
return { status: 'ok', timestamp: new Date().toISOString() };
});
app.get('/health/live', async () => {
return { status: 'ok' };
});
app.get('/health/ready', async (request, reply) => {
const checks = {
database: false,
cache: false,
};
try {
await app.db`SELECT 1`;
checks.database = true;
} catch {
// Database not ready
}
try {
await app.cache.ping();
checks.cache = true;
} catch {
// Cache not ready
}
const allHealthy = Object.values(checks).every(Boolean);
if (!allHealthy) {
reply.code(503);
}
return {
status: allHealthy ? 'ok' : 'degraded',
checks,
timestamp: new Date().toISOString(),
};
});
// Detailed health for monitoring
app.get('/health/details', {
preHandler: [app.authenticate, app.requireAdmin],
}, async () => {
const memory = process.memoryUsage();
return {
status: 'ok',
uptime: process.uptime(),
memory: {
heapUsed: Math.round(memory.heapUsed / 1024 / 1024),
heapTotal: Math.round(memory.heapTotal / 1024 / 1024),
rss: Math.round(memory.rss / 1024 / 1024),
},
version: process.env.APP_VERSION,
nodeVersion: process.version,
};
});Create an optimized Dockerfile:
# Build stage
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
# Production stage
FROM node:22-alpine
WORKDIR /app
# Run as non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
# Copy from builder
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodejs:nodejs /app/src ./src
COPY --from=builder --chown=nodejs:nodejs /app/package.json ./
USER nodejs
EXPOSE 3000
ENV NODE_ENV=production
ENV PORT=3000
# Health check
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
CMD ["node", "src/app.ts"]# docker-compose.yml
services:
api:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=postgres://user:pass@db:5432/app
- JWT_SECRET=${JWT_SECRET}
depends_on:
db:
condition: service_healthy
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
- POSTGRES_DB=app
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d app"]
interval: 5s
timeout: 5s
retries: 5
volumes:
pgdata:Deploy to Kubernetes:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: fastify-api
spec:
replicas: 3
selector:
matchLabels:
app: fastify-api
template:
metadata:
labels:
app: fastify-api
spec:
containers:
- name: api
image: my-registry/fastify-api:latest
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: "production"
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: api-secrets
key: database-url
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health/live
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /health/ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"]
---
apiVersion: v1
kind: Service
metadata:
name: fastify-api
spec:
selector:
app: fastify-api
ports:
- port: 80
targetPort: 3000
type: ClusterIPConfigure logging for production:
import Fastify from 'fastify';
const app = Fastify({
logger: {
level: process.env.LOG_LEVEL || 'info',
// JSON output for log aggregation
formatters: {
level: (label) => ({ level: label }),
bindings: (bindings) => ({
pid: bindings.pid,
hostname: bindings.hostname,
service: 'fastify-api',
version: process.env.APP_VERSION,
}),
},
timestamp: () => `,"time":"${new Date().toISOString()}"`,
// Redact sensitive data
redact: {
paths: [
'req.headers.authorization',
'req.headers.cookie',
'*.password',
'*.token',
'*.secret',
],
censor: '[REDACTED]',
},
},
});Configure appropriate timeouts:
const app = Fastify({
connectionTimeout: 30000, // 30s connection timeout
keepAliveTimeout: 72000, // 72s keep-alive (longer than ALB 60s)
requestTimeout: 30000, // 30s request timeout
bodyLimit: 1048576, // 1MB body limit
});
// Per-route timeout
app.get('/long-operation', {
config: {
timeout: 60000, // 60s for this route
},
}, longOperationHandler);Configure for load balancers:
const app = Fastify({
// Trust first proxy (load balancer)
trustProxy: true,
// Or trust specific proxies
trustProxy: ['127.0.0.1', '10.0.0.0/8'],
// Or number of proxies to trust
trustProxy: 1,
});
// Now request.ip returns real client IPServe static files efficiently. Always use import.meta.dirname as the base path, never process.cwd():
import fastifyStatic from '@fastify/static';
import { join } from 'node:path';
app.register(fastifyStatic, {
root: join(import.meta.dirname, '..', 'public'),
prefix: '/static/',
maxAge: '1d',
immutable: true,
etag: true,
lastModified: true,
});Enable response compression:
import fastifyCompress from '@fastify/compress';
app.register(fastifyCompress, {
global: true,
threshold: 1024, // Only compress > 1KB
encodings: ['gzip', 'deflate'],
});Expose Prometheus metrics:
import { register, collectDefaultMetrics, Counter, Histogram } from 'prom-client';
collectDefaultMetrics();
const httpRequestDuration = new Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'status'],
buckets: [0.01, 0.05, 0.1, 0.5, 1, 5],
});
const httpRequestTotal = new Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'route', 'status'],
});
app.addHook('onResponse', (request, reply, done) => {
const route = request.routeOptions.url || request.url;
const labels = {
method: request.method,
route,
status: reply.statusCode,
};
httpRequestDuration.observe(labels, reply.elapsedTime / 1000);
httpRequestTotal.inc(labels);
done();
});
app.get('/metrics', async (request, reply) => {
reply.header('Content-Type', register.contentType);
return register.metrics();
});Support rolling updates:
import closeWithGrace from 'close-with-grace';
// Stop accepting new connections gracefully
closeWithGrace({ delay: 30000 }, async ({ signal }) => {
app.log.info({ signal }, 'Received shutdown signal');
// Stop accepting new connections
// Existing connections continue to be served
// Wait for in-flight requests (handled by close-with-grace delay)
await app.close();
app.log.info('Server closed');
});evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10
scenario-11
scenario-12
scenario-13
scenario-14
scenario-15
scenario-16
scenario-17
scenario-18
scenario-19
scenario-20
scenario-21
scenario-22
scenario-23
scenario-24
scenario-25
scenario-26
scenario-27
scenario-28
scenario-29
scenario-30
scenario-31
scenario-32
scenario-33
scenario-34
scenario-35
scenario-36
scenario-37
scenario-38
scenario-39
scenario-40
scenario-41
scenario-42
scenario-43
scenario-44
skills
documentation
fastify
init
linting-neostandard-eslint9
node
nodejs-core
rules
oauth
octocat
snipgrapher