Why separate into layers
If you cram data access, validation and HTTP logic into a single function, you end up with code that is impossible to test and maintain. A clean API separates responsibilities into layers, each with one job:
HTTP request
│
▼
[ middleware ] ── auth (who are you?) and validation (correct data?)
│
▼
[ controller/handler ] ── translates HTTP ↔ data: reads req, responds res
│
▼
[ repository ] ── reads and writes the store (here, an in-memory array)
The repository (data access)
Pure JavaScript functions that operate on the store. They know nothing about HTTP:
function create(tasks, title) {
const task = { id: tasks.length + 1, title, done: false };
tasks.push(task);
return task;
}
function findById(tasks, id) {
return tasks.find((t) => t.id === id);
}
The middleware (auth and validation)
They run before the handler. If something goes wrong, they respond and
cut the chain; if everything is fine, they call next():
function requireAuth(req, res, next) {
if (!req.headers.authorization) {
res.status(401).json({ error: "Unauthorized" });
return;
}
next();
}
The handler (controller)
It is the only one that touches req and res: it reads the input, calls the
repository and chooses the status code and the response body:
function createTask(req, res) {
const task = create(tasks, req.body.title);
res.status(201).json(task);
}
In this module you will build each piece separately and check that it works in isolation. That is how you assemble an API that scales.