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

3.3 — Backend Architectures: Quick Revision

Episode 3 supplement -- print-friendly cheat sheet.

How to use

Skim -> drill weak spots in 3.3.a through 3.3.d -> 3.3-Exercise-Questions.md -> 3.3-Interview-Questions.md.


MVC at a Glance

            +------- REQUEST ------+
            |                      |
            v                      |
     +-------------+               |
     |   Router    |  (URL -> handler mapping)
     +------+------+               |
            |                      |
            v                      |
     +-------------+               |
     | Controller  |  (extract input, send response)
     +------+------+               |
            |                      |
            v                      |
     +-------------+               |
     |   Service   |  (business logic, orchestration)
     +------+------+               |
            |                      |
            v                      |
     +-------------+               |
     |    Model    |  (schema, validation, DB operations)
     +------+------+               |
            |                      |
            v                      |
     +-------------+               |
     |  Database   |               |
     +-------------+  --- RESPONSE-+

MVC Component Responsibilities

ComponentDoesDoes NOT
ModelSchema, validation, DB CRUD, pre/post hooks, business rules tied to dataAccess req/res, send HTTP responses, know about routing
ViewJSON response formatting (REST APIs), HTML rendering (template engines)Query the database, contain business logic
ControllerExtract req.params/req.query/req.body, call service, send response, call next(error)Contain business logic, query DB directly, send emails
ServiceBusiness logic, orchestrate multiple models, call external APIs, apply business rulesAccess req/res, know about HTTP status codes

Architecture Comparison Table

MonolithModular MonolithSOAMicroservicesServerless
CodebaseSingleSingle, feature-organizedMultipleMultipleIndividual functions
DatabaseSharedShared (module boundaries)Shared or per-servicePer-servicePer-function or managed
DeploymentAll at onceAll at oncePer-servicePer-servicePer-function
CommunicationFunction callsFunction calls via interfacesESB / API GatewayHTTP / gRPC / Message queuesEvent triggers
ScalingEntire appEntire appPer-servicePer-serviceAutomatic per-function
ComplexityLowLow-MediumMedium-HighHighMedium
Best team size1-15 devs5-30 devs20-100 devs20+ devs (multiple teams)Any
Best forMVPs, startupsGrowing startupsEnterprise integrationLarge-scale, independent teamsEvent-driven, spiky workloads

Standard Folder Structure (MVC + Service Layer)

project/
├── server.js                  # Entry point: app.listen()
├── app.js                     # Express config: middleware, routes
├── config/
│   └── db.js                  # Database connection
├── routes/
│   ├── userRoutes.js          # router.get/post/put/delete
│   ├── productRoutes.js
│   └── orderRoutes.js
├── controllers/
│   ├── userController.js      # Extract req, call service, send res
│   ├── productController.js
│   └── orderController.js
├── services/
│   ├── userService.js         # Business logic, orchestration
│   ├── productService.js
│   └── orderService.js
├── models/
│   ├── User.js                # Mongoose schema + model
│   ├── Product.js
│   └── Order.js
├── middlewares/
│   ├── auth.js                # Authentication
│   ├── errorHandler.js        # Global error handler
│   └── validate.js            # Input validation
├── utils/
│   ├── AppError.js            # Custom error class
│   └── email.js               # Email helper
├── .env
└── package.json

Request Flow (Step by Step)

Example: POST /api/users with JSON body { name, email, password }

StepLayerWhat Happens
1Middlewareexpress.json() parses body, morgan logs, auth middleware checks token
2Routerrouter.post('/users', userController.create) matches the route
3ControllerExtracts { name, email, password } from req.body, calls userService.create(data)
4ServiceChecks for duplicate email, hashes password, calls User.create(data), sends welcome email
5ModelValidates schema, runs pre-save hooks, inserts document into MongoDB
6ServiceReturns the created user to the controller
7ControllerSends res.status(201).json({ status: 'success', data: { user } })

When-to-Use Decision Matrix

QuestionIf YesIf No
Fewer than 15 developers?Monolith or Modular MonolithConsider SOA / Microservices
Need independent scaling per feature?Microservices or ServerlessMonolith is fine
Spiky / infrequent traffic?ServerlessDedicated server / container
Multiple teams needing autonomy?MicroservicesModular Monolith
Integrating legacy enterprise systems?SOA with ESBDirect API calls
Building an MVP / prototype?MonolithDo not over-architect
Need real-time event processing?Event-Driven ArchitectureRequest-Response
Product boundaries still unclear?Monolith (extract later)Microservices if boundaries are well-defined

Fat Controller vs Thin Controller

// FAT (bad) -- business logic in the controller
exports.register = async (req, res) => {
  const { email, password } = req.body;
  if (password.length < 8) return res.status(400).json({ error: 'Too short' });
  const exists = await User.findOne({ email });
  if (exists) return res.status(409).json({ error: 'Taken' });
  const hashed = await bcrypt.hash(password, 12);
  const user = await User.create({ email, password: hashed });
  await sendWelcomeEmail(user.email);
  res.status(201).json(user);
};

// THIN (good) -- controller delegates to service
exports.register = async (req, res, next) => {
  try {
    const user = await userService.register(req.body);
    res.status(201).json({ status: 'success', data: { user } });
  } catch (error) {
    next(error);
  }
};

Key Principles (One-Liners)

  • Separation of concerns = each layer has one job and delegates everything else.
  • Model = data brain. Knows schema, validation, DB. Knows nothing about HTTP.
  • Controller = traffic cop. Extracts input, calls service, sends response. No business logic.
  • Service = business logic hub. Orchestrates models and external services. No req/res.
  • Monolith first = start simple, extract services when you have clear scaling or team signals.
  • Distributed monolith = the worst outcome. Microservice complexity + monolith coupling.
  • Event-driven = publish events, consumers react independently. Great for decoupling.
  • Serverless = no server management, pay per invocation. Watch out for cold starts.

End of 3.3 quick revision.