Web Backend Development on Node.js (Fastify)
Fastify chosen not for hype—chosen when throughput matters. Framework handles up to 76,000 requests per second on single core in synthetic tests, this difference felt in real projects with high-frequency API: price-feed, real-time analytics, public API with unpredictable traffic.
Architecture and Code Organization
Fastify built around plugin concept—each plugin isolated context with own decorators, hooks, handlers:
// plugins/db.js
import fp from 'fastify-plugin'
import { Pool } from 'pg'
async function dbPlugin(fastify, opts) {
const pool = new Pool({ connectionString: opts.connectionString })
fastify.decorate('db', pool)
fastify.addHook('onClose', async () => pool.end())
}
export default fp(dbPlugin, { name: 'db' })
Route with Schema and Validation
// routes/products/index.js
export default async function productsRoutes(fastify) {
fastify.get('/', {
schema: {
querystring: {
limit: { type: 'integer', minimum: 1, maximum: 100 },
offset: { type: 'integer', minimum: 0 }
},
response: {
200: { type: 'array', items: { $ref: 'Product#' } }
}
}
}, async (request, reply) => {
const { limit, offset } = request.query
return await fastify.db.query('SELECT * FROM products LIMIT $1 OFFSET $2', [limit, offset])
})
}
Route schema—not documentation, it's input validation and response serialization simultaneously. Fastify uses ajv for validation and fast-json-stringify for serialization. Response serialized 2–5x faster than JSON.stringify.
Authentication with JWT
fastify.decorate('authenticate', async function(request, reply) {
try {
await request.jwtVerify()
} catch (err) {
reply.send(err)
}
})
fastify.get('/profile', {
onRequest: [fastify.authenticate]
}, async (request) => {
return request.user
})
Role-based permissions via preHandler hook:
const requireRole = (role) => async (request, reply) => {
if (request.user.role !== role) {
return reply.code(403).send({ error: 'Forbidden' })
}
}
fastify.delete('/users/:id', {
onRequest: [fastify.authenticate],
preHandler: [requireRole('admin')]
}, handler)
Database: ORM Choice
| Tool | When |
|---|---|
pg + type-safe wrapper |
Full SQL control, critical performance |
Drizzle ORM |
Type-safe SQL without Prisma overhead |
Prisma |
Fast start, auto types, team knows it |
Knex |
Query builder, no ORM overhead |
Drizzle optimal for new Node.js 18+ projects:
import { pgTable, serial, varchar } from 'drizzle-orm/pg-core'
import { drizzle } from 'drizzle-orm/node-postgres'
const users = pgTable('users', {
id: serial('id').primaryKey(),
email: varchar('email', { length: 255 }).notNull().unique()
})
const result = await db.select().from(users).where(eq(users.email, email))
Caching with Redis
fastify.register(fastifyRedis, { url: process.env.REDIS_URL })
fastify.get('/catalog', async (request, reply) => {
const cacheKey = `catalog:${JSON.stringify(request.query)}`
const cached = await fastify.redis.get(cacheKey)
if (cached) {
reply.header('X-Cache', 'HIT')
return JSON.parse(cached)
}
const data = await fetchCatalog(request.query)
await fastify.redis.setex(cacheKey, 300, JSON.stringify(data))
reply.header('X-Cache', 'MISS')
return data
})
WebSocket Support
fastify.register(fastifyWs)
fastify.get('/ws/notifications', { websocket: true }, (socket, req) => {
socket.on('message', (raw) => {
const msg = JSON.parse(raw.toString())
})
const interval = setInterval(() => {
if (socket.readyState === socket.OPEN) {
socket.send(JSON.stringify({ type: 'ping' }))
}
}, 30000)
socket.on('close', () => clearInterval(interval))
})
Metrics for Prometheus
fastify.register(fastifyMetrics, {
endpoint: '/metrics',
defaultMetrics: { enabled: true }
})
Structured logging via pino—fastest Node.js logger.
Development Timeline
Typical REST API for site/web app on Fastify:
- API Design (OpenAPI/Swagger) — 3–5 days: endpoints, schemas, auth
- Basic scaffold: plugins, routes, migrations—3–5 days
- Business logic—depends on complexity; 1–2 weeks for CRUD site, 3–6 weeks for complex app
- Integrations (payment, email, external API)—1–3 weeks
- Tests + docs—3–7 days
For straightforward corporate site with API (catalog, forms, account)—full cycle 4–8 weeks. Fastify advantage: validation and docs written once in route schema—saves time on maintenance.







