What you need to do
The practical activities and techniques you need to master – what to practice, what to get good at, and what to keep doing.
Implementing request-response
Building the synchronous communication layer of your backend – HTTP endpoints that receive requests and return responses. Structure route handlers as thin wrappers: validate input, call a service, return a response. Never put business logic in a route handler. Use middleware for cross-cutting concerns: authentication (verify the user), logging (record what happened), error handling (catch exceptions and return consistent error responses), rate limiting (protect the endpoint). Stack middleware in the right order: logging first, authentication next, validation last. Build your error handling middleware early – it catches unhandled exceptions and returns a consistent shape instead of a stack trace.
Implementing consumers
Building the asynchronous communication layer – workers that process messages from queues, event streams, or pub/sub systems. Not everything belongs in the request-response cycle. Email sending, image processing, webhook delivery – these should happen in background consumers. BullMQ with Redis is the standard for Node.js: define a job, add it to a queue, process it in a worker. Jobs can retry on failure, run on a schedule (cron), and report progress. For simpler needs, Upstash QStash provides serverless job queues. The rule: if it takes more than 200ms or can fail independently, put it in a queue.
Implementing batch jobs
Building scheduled or one-off processes that operate on large datasets – report generation, data migrations, nightly syncs, cleanup tasks. Batch jobs differ from consumers in that they're typically triggered by time (cron schedules) rather than events. Design batch jobs to be idempotent (safe to re-run), resumable (can pick up where they left off after failure), and observable (log progress so you know where they are). Set timeouts and deadlines. Monitor execution time – a batch job that gradually slows down is a leading indicator of data growth problems.
Data layer
Building the interface between your application and its data stores – databases, caches, external APIs. Use an ORM (Prisma, Drizzle) for type-safe database access with parameterized queries. Wrap each external integration in a dedicated client module: one file that handles authentication, request formatting, error mapping, and retries. Never scatter fetch() calls with API keys across your codebase. Validate data at the boundary: use schema validation (Zod) for incoming data and type-safe query clients for outgoing queries. The data layer is the seam that makes your application testable – services that receive their data dependencies can be tested with mocks or fakes.
Configuration and secrets
Environment variables are the standard for configuration – database URLs, API keys, feature flags. Never hardcode secrets in source code. Use a .env file for local development (add it to .gitignore immediately), and your deployment platform's secrets management for production. Validate environment variables at startup with Zod or a similar library – fail fast with a clear error message if a required variable is missing. This prevents the nightmare of discovering a missing API key at 3am when the payment webhook fires.
Frameworks to know
The mental models and structured approaches that guide how you think about this skill – not rules to follow, but lenses to see through.
MVC (Model-View-Controller)
The foundational architectural pattern for separating concerns in web applications. Model handles data and business logic, View handles presentation, Controller handles input and orchestrates between Model and View. In modern backends, this translates to: Models are your domain entities and database schemas, Views are your API response shapes (JSON serialization), Controllers are your route handlers that receive requests and delegate to services. MVC's lasting contribution isn't the specific three-layer split – it's the principle that presentation, business logic, and data access should be independent, swappable, and testable in isolation.
Dependency injection
Instead of importing concrete implementations directly, inject dependencies through constructors or factory functions. A service that needs a database client receives it as a parameter, not as a hardcoded import. This makes testing trivial: pass a mock database client in tests, the real one in production. You don't need a DI framework – a simple factory function that wires up dependencies at startup achieves the same result. The principle matters more than the tool: code that receives its dependencies is easier to test, easier to refactor, and easier to understand than code that reaches out and grabs them.
Good vs. bad
Common mistakes paired with what experienced practitioners do instead – and why the product builder approach changes the game.
Backend development is where product builders spend most of their coding time – and where AI tools give the biggest productivity boost. Claude Code or Cursor with a well-typed TypeScript codebase can scaffold an entire API endpoint in minutes: route handler, service, database query, validation, error handling. The AI understands the types and generates code that fits your existing patterns. The product builder's job shifts from writing boilerplate to reviewing and refining what the AI produces.
I've settled on a simple stack for most backend work: Next.js API routes for the application, Prisma for database access, Zod for validation, and BullMQ for background jobs. It's boring, well-documented, and every TypeScript developer can read it. The temptation to adopt NestJS or Fastify or tRPC is real – and sometimes justified – but for most products, Next.js API routes are enough. Add complexity when you feel pain, not when you anticipate it.
The layered architecture (handlers → services → repositories) isn't about architecture astronautics – it's about testability. When your business logic lives in a service function that takes typed inputs and returns typed outputs, testing it is trivial. When the same logic is tangled inside a route handler that depends on HTTP request objects, testing it requires mocking the entire web framework. I've seen teams abandon testing entirely because their code was too coupled to test. Layers prevent that.
Supabase changed how I think about backend development for new products. Authentication, file storage, real-time subscriptions, and a PostgreSQL database – all managed, all with generous free tiers. For the first version of any product, the question isn't 'should I build auth from scratch?' It's 'can I ship by Thursday?' Supabase says yes. You can always migrate to custom infrastructure later, when you've proven the product has users who care.
The single most impactful backend practice I've adopted is validating environment variables at startup with Zod. It takes 10 minutes to set up and it prevents every 'why is this failing in production?' incident that's caused by a missing or misconfigured environment variable. Server starts, Zod validates every required env var, and if anything is missing, the server exits immediately with a human-readable error. No more discovering a missing Stripe key at 2am.
Go deeper
Books to deepen your understanding beyond what fits on this page.
The definitive guide to Node.js patterns – event loop, streams, async patterns, dependency injection, middleware, and module design. Now in its fourth edition with TypeScript coverage and modern Node.js APIs.
The backend developer's bible for understanding how data moves through systems – storage engines, replication, stream processing, and the trade-offs behind every architectural decision.
The canonical reference on DI – not just how containers work, but why composition root, lifetime management, and constructor injection make code testable and maintainable. Language-agnostic principles with C# examples.
The pattern language for asynchronous messaging – queues, topics, channels, routing, and transformation. The book behind how RabbitMQ, Kafka, and SQS are actually used in production systems.
Foreword by Salvatore Sanfilippo (Redis creator). Covers Redis as cache, queue, pub/sub, leaderboard, session store, and rate limiter – the use cases that make Redis a Swiss Army knife in modern backends.
How to build search that actually returns what users want – with applications for Solr and Elasticsearch. Covers analyzers, scoring, signals modeling, and the practical work of making search relevant.
Explore related
Skills that complement this one – practice them together to build a stronger product builder toolkit.