CodingNic

Project Structure & MVC

MVC Architecture

Project Structure & MVC 10 min read

MVC Architecture

Objectives

By the end of this lesson, you should be able to:

  • Explain what Model, View, and Controller each represent in an Express app
  • Identify which parts of code written so far in this course belong to which layer
  • Explain why separating these concerns matters as an app grows

💡 Why this matters: Every lesson in this course so far put a route’s data, logic, and response handling in the same function. That’s fine for a small example, it becomes genuinely unmanageable once an app has dozens of routes, this module’s whole purpose is fixing that.

💡 Note: this lesson is conceptual, establishing the vocabulary and reasoning the rest of this module builds on directly, with real, runnable multi-file code starting in Lesson 2.

The Three Pieces

MVC (Model-View-Controller) separates an application into three concerns. The Model owns data and the logic for working with it, creating, reading, updating, deleting, independent of HTTP entirely, a model has no idea what a req or res even is. The View is what gets sent back to a client, an EJS or Handlebars template (Module 6) rendering HTML, or, for a JSON API, simply the shape of the response itself. The Controller sits between them, receiving a request, asking the model for data (or telling it to change something), and handing the result to a view, or to res.json() directly, a controller contains almost no logic of its own, it coordinates.

Where This Already Existed, Unseparated

Every route handler written since Module 5 mixed all three together in one function:

javascript
// everything in one place, from Module 9
app.get('/books/:id', (req, res) => {
  const book = books.find(b => b.id === Number(req.params.id)); // model concern
  if (!book) {
    return res.status(404).json({ error: 'NotFound' }); // controller concern
  }
  res.json(book); // view concern (the response shape)
});

The books.find(...) line is genuinely a model concern, finding and returning data, the res.status(404).json(...) and res.json(book) lines are controller/view concerns, deciding what response to send. Nothing was wrong with writing it this way for a single-file example, but every one of this course’s route handlers has quietly been doing all three jobs at once.

Why Separate Them

A model with no HTTP knowledge at all can be tested on its own, called from a script, or reused by a completely different route, without dragging req/res along with it. A controller that only coordinates, rather than containing real logic, stays short and easy to read, even once a route’s behavior gets complicated. And when the underlying data source changes, an in-memory array today, a real PostgreSQL database in the next course in this track, only the model needs to change, the controller (and the routes calling it) stay exactly the same, because they never depended on how the data was stored, only on what the model’s functions returned.

Try It

  1. Take a route handler from a project earlier in this course, and identify, line by line, which parts are model concerns, which are controller concerns, and which are view concerns.
  2. Explain, in your own words, why a model function shouldn’t take req or res as a parameter.
  3. Explain, in your own words, why swapping a data source (an array today, a database later) is easier in an app that already separates models from controllers.
  4. Sketch, in words, what a taskModel’s functions might be named for a simple to-do list app (without writing code yet, this is covered in the next lesson).

Recap

  • Model owns data and logic, independent of HTTP, View is the response shape (HTML template or JSON), Controller coordinates between them.
  • Every route handler in this course so far mixed all three concerns into a single function, workable for a small example, unmanageable at scale.
  • Separating them makes each piece testable and reusable on its own, and means changing a data source later doesn’t require touching routes or controllers at all.

Next lesson: the model layer, extracting data and logic into its own module.