Episode 3 — NodeJS MongoDB Backend Architecture / 3.3 — Backend Architectures

3.3.a — Introduction to Backend Architecture

Software architecture is the blueprint of your application. It defines how code is organized, how components communicate, and how the system evolves over time. Bad architecture turns small changes into week-long nightmares; good architecture makes even complex features feel manageable.


Home | Next: 3.3.b — MVC Architecture


1. What Is Software Architecture?

Software architecture is the high-level structure of a software system. It defines:

  • How code is organized into modules, layers, or services
  • How those parts communicate with each other
  • What rules govern data flow through the system
  • Where boundaries exist between different responsibilities

Think of it like a building's blueprint. You can build a house without a plan, but the plumbing will end up in the wrong place, the walls won't be load-bearing where they need to be, and adding a second floor later will require tearing everything down.

Architecture vs. Design

AspectArchitectureDesign
ScopeSystem-wide decisionsComponent-level decisions
ChangesExpensive to change laterEasier to refactor
FocusStructure, communication, constraintsAlgorithms, data structures, interfaces
Who decidesTech leads, architectsIndividual developers
Example"We use microservices with an event bus""This function uses a binary search"

2. Why Architecture Matters

2.1 Scalability

Architecture determines how your app handles growth.

BAD ARCHITECTURE (everything tangled)
+------------------------------------------+
|  route handler:                          |
|    - validates input                     |
|    - queries database directly           |
|    - formats response                    |
|    - sends emails                        |
|    - logs analytics                      |
|    - handles errors                      |
|  (one function doing EVERYTHING)         |
+------------------------------------------+
     Cannot scale individual pieces.
     Cannot add caching without rewriting.
     One bug can break everything.

GOOD ARCHITECTURE (separated concerns)
+----------+  +------------+  +---------+  +----------+
|  Routes  |->| Controller |->| Service |->|  Model   |
+----------+  +------------+  +---------+  +----------+
                                  |
                            +-----+------+
                            |  Email     |  Analytics |
                            |  Service   |  Service   |
                            +------------+------------+
     Each piece scales independently.
     Add caching at the service layer only.
     Bug in email doesn't break user creation.

2.2 Maintainability

When a bug appears in a well-architected system, you know exactly where to look:

  • Data coming back wrong? Check the Model.
  • Wrong HTTP status code? Check the Controller.
  • Business rule violated? Check the Service layer.

2.3 Team Collaboration

Good architecture lets multiple developers work without stepping on each other's code.

Team MemberWorks OnDoesn't Touch
Frontend developerRoutes, Controllers (API contract)Model internals
Backend developerServices, ModelsRoute definitions
Database engineerModels, SchemasControllers
DevOps engineerDeployment, ConfigBusiness logic

2.4 Testability

Separated architecture means you can test each layer in isolation:

// Testing a service WITHOUT needing Express or a real database
const userService = require('../services/userService');

// Mock the model
jest.mock('../models/User');
const User = require('../models/User');

test('createUser hashes password before saving', async () => {
  User.create.mockResolvedValue({ id: 1, name: 'Alice' });
  
  const result = await userService.createUser({
    name: 'Alice',
    password: 'secret123'
  });
  
  // Verify the password was hashed (not stored raw)
  const callArgs = User.create.mock.calls[0][0];
  expect(callArgs.password).not.toBe('secret123');
  expect(callArgs.password).toMatch(/^\$2[aby]\$/); // bcrypt hash pattern
});

3. Monolithic vs. Microservices (Overview)

3.1 Monolithic Architecture

Everything lives in one codebase, deployed as one unit.

+--------------------------------------------------+
|                  MONOLITH                         |
|                                                  |
|  +--------+  +---------+  +--------+  +-------+  |
|  |  Auth  |  |  Users  |  | Orders |  | Email |  |
|  +--------+  +---------+  +--------+  +-------+  |
|                                                  |
|  +--------------------------------------------+  |
|  |          Shared Database                    |  |
|  +--------------------------------------------+  |
+--------------------------------------------------+
        Deployed as ONE application

Advantages:

  • Simple to develop, test, and deploy initially
  • No network latency between components
  • Easy to debug (one process, one log stream)
  • Single database means simple transactions

Disadvantages:

  • Entire app redeploys for any change
  • One module's crash can bring down everything
  • Hard to scale individual components
  • Codebase becomes unwieldy over time

3.2 Microservices Architecture

Each feature is an independent service with its own database.

+--------+     +----------+     +---------+
|  Auth  |     |  Users   |     | Orders  |
| Service|     | Service  |     | Service |
+---+----+     +----+-----+     +----+----+
    |               |                |
+---+----+     +----+-----+     +----+----+
| Auth DB|     | Users DB |     |Orders DB|
+--------+     +----------+     +---------+
    |               |                |
+---+---------------+----------------+---+
|              API Gateway               |
+----------------------------------------+

Advantages:

  • Deploy services independently
  • Scale bottleneck services only
  • Different teams own different services
  • Technology flexibility per service

Disadvantages:

  • Network complexity (services calling services)
  • Distributed transactions are hard
  • Deployment infrastructure is complex
  • Debugging across services is difficult

3.3 Quick Comparison

FactorMonolithMicroservices
DeploymentAll at oncePer service
ScalingEntire appPer service
DatabaseSharedPer service
Team couplingHighLow
Initial complexityLowHigh
Best starting pointAlmost always YESRarely for new projects

4. Choosing Architecture Based on Project Size and Team

Decision Framework

START HERE
    |
    v
Is your team < 5 people?
    |           |
   YES         NO
    |           |
    v           v
Monolith    Do you have DevOps expertise?
(MVC)           |           |
               YES         NO
                |           |
                v           v
        Microservices   Monolith (MVC)
        (if needed)     (evolve later)

Practical Recommendations

Project TypeRecommended ArchitectureWhy
Personal project / MVPMonolith with MVCShip fast, iterate quickly
Startup (seed stage)Monolith with clean layersMaintain speed while keeping code organized
Growing company (20+ devs)Modular monolith or SOATeams need boundaries without full microservices overhead
Enterprise (100+ devs)MicroservicesTeams need independence, different scaling needs
Event-heavy system (IoT, real-time)Event-drivenDecouples producers from consumers
Sporadic workloads (cron jobs, webhooks)ServerlessPay only for what you use

5. Common Architectural Patterns

5.1 Layered Architecture

Code is organized into horizontal layers, each with a specific responsibility.

+---------------------------+
|    Presentation Layer     |   (Routes, Controllers)
+---------------------------+
|    Business Logic Layer   |   (Services, Use Cases)
+---------------------------+
|    Data Access Layer      |   (Models, Repositories)
+---------------------------+
|    Database Layer         |   (MongoDB, PostgreSQL)
+---------------------------+

Rule: Each layer only talks to the layer directly below it.

5.2 Event-Driven Architecture

Components communicate through events, not direct calls.

// Event emitter pattern in Node.js
const EventEmitter = require('events');
const appEvents = new EventEmitter();

// Publisher: User service emits an event
async function createUser(data) {
  const user = await User.create(data);
  appEvents.emit('user:created', user);  // fire and forget
  return user;
}

// Subscriber: Email service listens for the event
appEvents.on('user:created', async (user) => {
  await sendWelcomeEmail(user.email);
});

// Subscriber: Analytics service also listens
appEvents.on('user:created', async (user) => {
  await trackSignup(user.id);
});

5.3 Microservices Architecture

Independent services communicating over HTTP or message queues.

// User service (runs on port 3001)
app.post('/api/users', async (req, res) => {
  const user = await UserModel.create(req.body);
  
  // Call order service via HTTP (inter-service communication)
  await axios.post('http://order-service:3002/api/carts', {
    userId: user._id
  });
  
  res.status(201).json(user);
});

5.4 Serverless Architecture

Individual functions deployed to cloud platforms, invoked on demand.

// AWS Lambda function (no Express, no server)
exports.handler = async (event) => {
  const { name, email } = JSON.parse(event.body);
  
  const user = await db.collection('users').insertOne({ name, email });
  
  return {
    statusCode: 201,
    body: JSON.stringify({ id: user.insertedId, name, email })
  };
};

6. Architecture Documentation and Diagrams

Good architecture must be documented. Undocumented architecture is just tribal knowledge that disappears when people leave.

What to Document

DocumentPurposeAudience
System context diagramShows how the system interacts with external systemsEveryone
Component diagramShows major components and their relationshipsDevelopers
Data flow diagramShows how data moves through the systemDevelopers, QA
API contractsDefines request/response formatsFrontend + Backend
Decision records (ADRs)Explains WHY a decision was madeFuture developers

Architecture Decision Record (ADR) Template

# ADR-001: Use MVC with Service Layer

## Status
Accepted

## Context
We are building a REST API for an e-commerce platform.
The team has 4 developers, all familiar with Express.js.

## Decision
We will use MVC architecture with an added Service layer:
Routes -> Controllers -> Services -> Models

## Consequences
- Positive: Clear separation of concerns, easy to test
- Positive: New team members understand the structure quickly
- Negative: More files and folders than a flat structure
- Negative: Simple CRUD operations require touching multiple files

ASCII Diagram Example (System Context)

                    +------------------+
                    |   Mobile App     |
                    +--------+---------+
                             |
                    +--------v---------+
 +----------+      |                   |      +-------------+
 | Payment  | <--> |   Our Backend    | <--> | Email (SMTP) |
 | Gateway  |      |   (Express API)  |      | Service      |
 +----------+      |                   |      +-------------+
                    +--------+---------+
                             |
                    +--------v---------+
                    |    MongoDB       |
                    +------------------+

7. The Architecture Spectrum

Most real-world systems are not purely one pattern. They sit on a spectrum:

Simple ◄──────────────────────────────────────────► Complex

Monolith     Modular       SOA        Microservices    Nano-
(all in one) Monolith   (services)   (independent)    services
             (bounded                                 (serverless
              modules)                                 functions)

Fewer moving parts ◄─────────────────────► More moving parts
Easier to start     ◄─────────────────────► Harder to start
Harder to scale     ◄─────────────────────► Easier to scale

The golden rule: Start with the simplest architecture that meets your needs. Evolve when the pain of the current architecture outweighs the cost of migration.


Key Takeaways

  1. Architecture is about trade-offs, not about picking the "best" pattern. Every choice has costs.
  2. Monoliths are not bad. They are the right starting point for most projects. The goal is a well-structured monolith.
  3. Separation of concerns is the single most important architectural principle. Every pattern enforces it differently.
  4. Architecture affects everything: how fast you ship, how many bugs you create, how easily new developers onboard, and how well the system scales.
  5. Document your decisions. Future developers (including future you) will thank you.
  6. Start simple, evolve intentionally. Premature complexity is as dangerous as no architecture at all.

Explain-It Challenge

Scenario: Your friend is starting a new project, a food delivery app. They have a 3-person team and want to build with microservices because "that's what Netflix uses."

Explain to them, in plain language:

  1. Why starting with microservices is likely a mistake for their team size
  2. What architecture you would recommend instead and why
  3. At what point it might make sense to start extracting microservices
  4. Which part of their system might be the first candidate for extraction and why

Home | Next: 3.3.b — MVC Architecture