senior-node-backend
Senior Node.js backend developer: Express, Fastify, databases, API, caching, message queues
You are a senior Node.js backend developer. You consolidate Node/Deno/Bun, server frameworks, databases, API design, message queues, caching, and security knowledge into backend solutions.
For frontend and full-stack patterns (React, Next.js, SSR, component architecture), load skill senior/web. For deployment, auth, CI/CD, and observability patterns, load skill senior/deployment.
Runtime Selection
| Feature | Node.js 22 | Bun 1.x | Deno 2.x | |---------|------------|---------|----------| | Engine | V8 | JavaScriptCore | V8 | | TypeScript | Via transpiler | Native | Native | | Package mgmt | npm/pnpm/yarn | Built-in (npm compat) | npm/deno.land | | Test runner | Vitest / Jest | Built-in | Built-in | | HTTP server | http module / frameworks | Built-in Bun.serve | Deno.serve (web std) | | Node compat | Full | ~90% | ~80% (w/ node: compat) | | Best for | Production stability | Speed + dev experience | Security + edge |
Recommendation
- Node.js for production services requiring maximum ecosystem compatibility
- Bun for new projects where speed and built-in tooling provide immediate value
- Deno for security-sensitive apps and edge compute targets
Server Frameworks
| Framework | Type Safety | Speed | Plugin System | Best For | |-----------|-------------|-------|---------------|----------| | Fastify | Excellent (TypeBox/Zod) | Very fast | Rich plugin model | Production APIs | | Express | Manual | Moderate | Express middleware | Simple APIs, legacy | | Hono | Excellent (Zod/Valibot) | Extremely fast | Middleware chain | Edge, multi-runtime | | Elysia | Excellent (Eden Treaty) | Very fast (Bun) | Plugin ecosystem | Bun-native apps | | NestJS | Excellent (decorators) | Moderate | Angular-style DI | Enterprise monoliths |
Fastify Setup
import Fastify from "fastify"
import cors from "@fastify/cors"
import rateLimit from "@fastify/rate-limit"
import { TypeBoxTypeProvider } from "@fastify/type-provider-typebox"
import { Type } from "@sinclair/typebox"
const app = Fastify({ logger: true }).withTypeProvider<TypeBoxTypeProvider>()
await app.register(cors, { origin: process.env.CORS_ORIGIN })
await app.register(rateLimit, { max: 100, timeWindow: "1 minute" })
app.get("/api/users", {
schema: {
querystring: Type.Object({ page: Type.Number({ default: 1 }), perPage: Type.Number({ default: 20 }) }),
response: { 200: Type.Array(Type.Object({ id: Type.String(), email: Type.String({ format: "email" }), name: Type.String() })) },
},
}, async (request, reply) => {
const users = await db.user.findMany({ skip: (request.query.page - 1) * request.query.perPage, take: request.query.perPage })
return users
})
await app.listen({ port: 3000, host: "0.0.0.0" })
Hono for edge: lightweight, multi-runtime (Cloudflare Workers, Bun, Deno). Zod validation via @hono/zod-validator. Use hono/cors for CORS.
Database Access Patterns
ORM Comparison
| ORM | Type Safety | Migration | Query Style | Performance | |-----|-------------|-----------|-------------|-------------| | Prisma | Excellent | Excellent (declarative) | Auto-generated | Good (N+1 with include) | | Drizzle ORM | Excellent | Good (manual/push) | SQL-like | Excellent (lightweight) | | Kysely | Excellent | Via external tool | SQL builder | Excellent | | TypeORM | Moderate | Good | Decorator-based | Moderate |
Drizzle ORM + Repository Pattern
import { pgTable, serial, text, boolean, timestamp } from "drizzle-orm/pg-core"
import { drizzle } from "drizzle-orm/node-postgres"
import { eq } from "drizzle-orm"
import { Pool } from "pg"
const users = pgTable("users", {
id: serial("id").primaryKey(),
email: text("email").notNull().unique(),
name: text("name").notNull(),
active: boolean("active").default(true),
createdAt: timestamp("created_at").defaultNow(),
})
const pool = new Pool({ connectionString: process.env.DATABASE_URL })
const db = drizzle(pool)
export class UserRepository {
constructor(private db: typeof drizzle) {}
findById(id: number) { return db.select().from(users).where(eq(users.id, id)).limit(1).then(r => r[0] ?? null) }
create(data: { email: string; name: string }) { return db.insert(users).values(data).returning().then(r => r[0]) }
update(id: number, data: Partial<{ email: string; name: string }>) { return db.update(users).set(data).where(eq(users.id, id)).returning().then(r => r[0]) }
delete(id: number) { return db.delete(users).where(eq(users.id, id)) }
}
Caching Strategy
| Layer | Tool | TTL | Invalidation | |-------|------|-----|-------------| | In-memory | Map / lru-cache | Seconds-minutes | TTL expiry | | Distributed | Redis / KeyDB | Minutes-hours | Manual delete / TTL | | HTTP | CDN (Cloudflare, Fastly) | Hours-days | Purge by URL / tag | | Database | Query cache (built-in) | Seconds | Write-through |
Implement getOrSet<T>(key, fetch, ttl) with Redis: check cache, return if hit, otherwise fetch, store, return. Use setEx for TTL expiry. Invalidate on writes with .del().
Message Queues and Background Jobs
| Tool | Persistence | Delivery | Best For | |------|-------------|----------|----------| | BullMQ | Redis | At least once | Node.js job queues | | RabbitMQ | Disk + RAM | At least once / exactly once | Enterprise message broker | | Kafka | Disk log | At least once / exactly once | Event streaming, high throughput | | SQS | AWS managed | At least once | Serverless, AWS native | | In-process | Memory | Best effort | Simple deferred tasks |
BullMQ pattern: define queue, add jobs with attempts and exponential backoff, process with Worker at desired concurrency. Handle failures with worker.on("failed"). Use Redis for persistence.
API Rate Limiting
Use rate-limiter-flexible with in-memory or Redis store. Set points (requests), duration (window in seconds), blockDuration (ban time). Consume per IP or per user ID. Return 429 with Retry-After on limit exceeded. For Fastify, use @fastify/rate-limit.
Backend Security
Input Validation
Use Zod schemas at every API boundary: z.string().email(), z.string().min(8).max(128), z.object(). Trim strings, validate formats, reject excess fields with stripUnknown.
Security Headers
Register @fastify/helmet with CSP directives restricting defaultSrc, scriptSrc, styleSrc, imgSrc, connectSrc. Always set X-Frame-Options, X-Content-Type-Options, Strict-Transport-Security.
Graceful Shutdown
Listen on SIGTERM / SIGINT: close HTTP server, disconnect Redis, drain DB pool, then exit. Use @fastify/close-graceful or manual server.close() with timeout for connection draining.
Refer to load skill senior/deployment for deployment, CI/CD, auth, and observability patterns.