CodingNic

Project Structure & MVC

The Model Layer

Project Structure & MVC 12 min read

The Model Layer

Objectives

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

  • Write a model module exposing data operations, with no HTTP concerns inside it
  • Export multiple functions from a model file using CommonJS
  • Explain why a model function returns data or null, rather than sending a response

💡 Why this matters: The model is where Lesson 1’s separation actually starts, a model file should be readable, testable, and understandable by someone who has never seen Express at all, it’s just data and functions.

⚠️ A note on verification: every snippet and every response shown below was actually run and tested with real HTTP requests, across real, separate files.

A Task Model

javascript
// models/taskModel.js
let tasks = [
  { id: 1, title: 'Write lesson', done: false },
  { id: 2, title: 'Verify code', done: true }
];
let nextId = 3;

function getAll() {
  return tasks;
}

function getById(id) {
  return tasks.find(t => t.id === id);
}

function create(data) {
  const task = { id: nextId++, title: data.title, done: false };
  tasks.push(task);
  return task;
}

function update(id, data) {
  const task = getById(id);
  if (!task) return null;
  Object.assign(task, data);
  return task;
}

function remove(id) {
  const index = tasks.findIndex(t => t.id === id);
  if (index === -1) return false;
  tasks.splice(index, 1);
  return true;
}

module.exports = { getAll, getById, create, update, remove };

Every function here (Module 3’s module.exports) works with plain JavaScript, arrays, objects, .find(), .findIndex() (Module 2), nothing here has ever seen a req or res, and nothing here calls res.json() or sets a status code. getById and update return undefined/null when nothing matches, remove returns false, deciding what HTTP status code that translates to is explicitly not this file’s job, that’s the controller’s job, covered next.

Why the Model Doesn’t Send Responses

If getById called res.status(404).json(...) itself, it would need a res object passed into it, coupling it directly to Express, unusable outside a route handler, untestable without mocking an entire request/response cycle, and unable to be reused by, for example, a background job or command-line script that also needs to look up a task. Returning plain data (or null/false for “not found”) keeps the model usable from anywhere.

Try It

  1. Create a models/taskModel.js file with the functions above, and confirm (with a plain Node.js script, no Express involved yet) that calling getAll(), create(), and getById() in sequence behaves correctly.
  2. Add a toggleDone(id) function to the model, flipping a task’s done boolean, returning the updated task or null if it doesn’t exist.
  3. Explain, in your own words, why update returning null (rather than throwing an error or sending a response) is the right choice for a model function.
  4. Explain, in your own words, what would need to change in this file if tasks were stored in a real database instead of an in-memory array, and what would not need to change in any file calling this model.

Recap

  • A model module exports plain functions working with data, no req/res, no status codes, no response sending.
  • Returning null/false/undefined for a “not found” case, rather than throwing or responding, keeps a model reusable outside of any specific route.
  • Because the model has no HTTP knowledge, swapping its internal storage (an array today, a real database later) never requires changing any code that calls it.

Next lesson: the controller layer, coordinating between routes and the model.