🌙
☀️ Dark
PART 12

Backend Engineering

Services, repositories, validation, dependency injection.

Advanced 45 min read
Volume 12: Backend Engineering

Chapter Title: Backend Architecture & Engineering

Engineering Challenge

Design a rate limiter from scratch using Redis. Ensure it supports sliding window algorithms to handle bursts accurately without punishing consistent, valid traffic. Write unit tests mocking Redis.

View Solution Approach

Use a Redis Sorted Set for the sliding window. The score and value are the timestamp. When a request comes in: 1) Remove elements with scores older than (current_time - window_size). 2) Count remaining elements. 3) If count < limit, add new timestamp and allow; else, reject.

Revision Sheet

  • Layers: Router -> Controller -> Service -> Repository.
  • Queues: Offload slow tasks (Emails, Webhooks, Image Resizing).
  • Middleware: Cross-cutting concerns (Auth, Logs, Errors).
  • Pagination: Prefer cursor over offset for large tables.

Connections

Understanding these backend principles leads directly into Distributed Systems and Cloud Architecture, where these same concepts scale across multiple servers and containers.

Learning Objectives

By the end of this chapter, you will be able to engineer a robust, scalable, and production-ready backend system. You will master the separation of concerns (Controllers, Services, Repositories), handle cross-cutting concerns (Middleware, Error Handling, Logging, Validation), secure your application (Authentication, Authorization, Rate Limiting), and scale operations (Background Jobs, Queues, Caching, File Uploads, Webhooks, Scheduled Jobs).

▶ View Solution

Solution implementation.

Prerequisites

Solid understanding of HTTP, basic Node.js and TypeScript, introductory relational database knowledge, and comfort with asynchronous programming.

▶ View Solution

Solution implementation.

Why Does This Exist?

A backend is not just an API that reads from a database. It is the engine of a product. It exists to securely process business logic, persist data reliably, handle immense traffic seamlessly, and communicate with external systems. Without a structured backend architecture, codebases devolve into unmaintainable "spaghetti" where business rules, database queries, and HTTP parsing are jumbled together.

▶ View Solution

Solution implementation.

The Problem Before the Solution

In a naive application, a developer creates a single massive function for a route (e.g., POST /users). This function parses the request, validates input, hashes the password, writes to the database, sends a welcome email, and returns a response. It handles everything.

▶ View Solution

Solution implementation.

Why the Old Approach Breaks

As the application grows, testing becomes impossible without mocking everything. If you want to create a user from an internal admin script instead of an HTTP request, you can't reuse the logic. When errors occur, it's hard to trace where it failed. Scaling specific tasks (like sending emails) blocks the main HTTP thread, leading to timeout errors and a degraded user experience.

▶ View Solution

Solution implementation.

History

Backend architectures evolved from monolithic CGI scripts to Model-View-Controller (MVC) paradigms popularized by frameworks like Ruby on Rails and Django. As systems grew, service-oriented architectures and microservices emerged. The industry standardized on patterns like Domain-Driven Design (DDD) and layered architectures (Controllers -> Services -> Repositories) to maintain order in massive codebases.

▶ View Solution

Solution implementation.

Mental Model (Analogy -> Reality)

Think of your backend as a high-end restaurant.

  • Router/Middleware: The Maître D' checks reservations (Authentication), ensures you are dressed appropriately (Validation/Rate Limiting), and seats you (Routing).
  • Controller: The Waiter takes your order (HTTP Request), translates it into a standard ticket, hands it to the kitchen, and delivers the food back (HTTP Response).
  • Service: The Head Chef executes the recipe (Business Logic). They don't care how the order arrived; they just cook.
  • Repository: The Sous Chef fetches raw ingredients from the pantry (Database Queries).
  • Background Jobs/Queues: The dishwashers and prep cooks working asynchronously so the Head Chef is never blocked.

In reality, this translates to distinct classes or modules where each layer only communicates with the one directly below it.

▶ View Solution

Solution implementation.

Internal Working (Memory, stack, process, network, etc.)

When an HTTP request arrives, Node's event loop offloads network I/O to the OS. The request passes through middleware functions that operate on the request object. Controllers parse data and push it down the call stack to Services. For Database access (Repository) or API calls, asynchronous operations are placed in the libuv thread pool. Background jobs are pushed to external systems (like Redis queues) to free the V8 engine to handle the next request immediately.

▶ View Solution

Solution implementation.

Visual Explanation (ASCII diagrams)

[Client] 
   | (HTTP POST /users)
   v
[Router] --> [Middleware (Auth, Rate Limit, Logging)]
   |
   v
[Controller] (Parses HTTP, Validates DTO)
   |
   v
[Service] (Business Logic: Check if user exists, hash password)
   | \
   |  \----> [Queue (Redis)] -> [Background Worker] (Sends Email)
   v
[Repository] (SQL Queries)
   |
   v
[Database] (PostgreSQL)
▶ View Solution

Solution implementation.

Syntax

We use TypeScript classes and Dependency Injection (DI) to construct these layers securely.

▶ View Solution

Solution implementation.

Tiny Example

javascript
▶ View Solution

Solution implementation.


// Controller
class UserController {
  constructor(private userService: UserService) {}
  
  async create(req: Request, res: Response) {
    const user = await this.userService.createUser(req.body);
    res.status(201).json(user);
  }
}
▶ View Solution

Solution implementation.

▶ View Solution

Solution implementation.

Walkthrough

In this architecture:

  1. Routing: Directs URL paths to specific Controller methods.
  2. Validation/Serialization: Middleware validates payload against schemas (e.g., Zod) before it reaches the Controller.
  3. Controllers: Extract data and call the Service.
  4. Services: Orchestrate business rules and call Repositories.
  5. Repositories: Isolate ORM/SQL logic.
  6. Error Handling: A centralized catch-all middleware formats exceptions into standard JSON responses.
▶ View Solution

Solution implementation.

Break It

If you inject the Express req object directly into your Service layer, you have broken the separation of concerns. The Service is now tightly coupled to HTTP. If you try to run the Service via a cron job, it fails because there is no HTTP request object.

▶ View Solution

Solution implementation.

Debug It

To fix coupling, extract the required fields from req.body in the Controller and pass a simple TypeScript object (DTO) to the Service.

▶ View Solution

Solution implementation.

Mini Project (20-30 min)

Build a User Onboarding API. Implement a Router, a rate-limiter middleware, Zod validation, a Controller, a Service that hashes passwords, and a Repository that saves to Postgres. Enqueue a welcome email using BullMQ.

▶ View Solution

Solution implementation.

Bigger Project (1-2 hours)

Build a RESTful API for a Todo app with Zod validation, request logging, and a global error handler.

▶ View Solution
typescript
// Implementation for Backend Engineering
console.log("Bigger project solution");

Interview Questions

Easy: What is the purpose of the Controller layer?

To handle HTTP-specific logic, parse requests, and format responses, delegating actual business rules to the Service layer.

Medium: Why should we use Background Queues for sending emails instead of doing it in the HTTP request?

Network calls to email providers can be slow or fail. Doing it synchronously blocks the HTTP response, degrading user experience. Queues allow asynchronous processing, automatic retries, and failure isolation.

Hard: How do you handle distributed transactions across multiple microservices without locking?

By using patterns like Saga or two-phase commit, relying on eventual consistency, and implementing compensatory transactions in case a step fails.

Senior: Explain the implementation of cursor-based pagination and its advantages over offset-based pagination in high-volume databases.

Offset-based pagination forces the database to scan and discard rows before returning results, causing O(N) degradation. Cursor pagination uses a unique, sequential index (like a timestamp or ID) to fetch the next set of rows directly using `WHERE id > cursor`, which remains O(1) regardless of page depth.

🏠 Curriculum NextVolume 2