Routes and Wiring It Together
Objectives
By the end of this lesson, you should be able to:
- Move route definitions into their own file, mapped to controller functions
- Mount a router onto the main app with a URL prefix
- Assemble a model, controller, and router into one complete, working MVC app
๐ก Why this matters: Lessons 2 and 3 built the model and controller in isolation. This lesson connects them to real URLs and runs the complete app, proving the whole structure actually works together, not just in theory.
โ ๏ธ A note on verification: every snippet and every response shown below was actually run and tested with real HTTP requests, across the complete real, multi-file project.
Project File Structure
Every file this lesson assembles, building on Lessons 2 and 3:
project/
โโโ package.json
โโโ app.js
โโโ models/
โ โโโ taskModel.js
โโโ controllers/
โ โโโ taskController.js
โโโ routes/
โโโ taskRoutes.js
The Routes File
// routes/taskRoutes.js
const express = require('express');
const router = express.Router();
const taskController = require('../controllers/taskController');
router.get('/', taskController.index);
router.get('/:id', taskController.show);
router.post('/', taskController.create);
router.patch('/:id', taskController.update);
router.delete('/:id', taskController.remove);
module.exports = router;
express.Router() (Module 9’s versioning lesson introduced this) creates a self-contained set of routes, each router.get/post/patch/delete call maps a path and method (Module 5, Module 9) directly to the matching controller function, no inline logic here at all, every actual behavior lives in taskController.js.
The Entry Point
// app.js
const express = require('express');
const taskRoutes = require('./routes/taskRoutes');
const app = express();
app.use(express.json());
app.use('/tasks', taskRoutes);
app.listen(4601, () => console.log('listening on 4601'));
app.use('/tasks', taskRoutes) mounts the entire router under the /tasks prefix, every path defined inside taskRoutes.js ('/', '/:id') is relative to that prefix, router.get('/:id', ...) combined with the mount point becomes the real route GET /tasks/:id.
Running the Complete App
curl http://localhost:4601/tasks
[{"id":1,"title":"Write lesson","done":false},{"id":2,"title":"Verify code","done":true}]
curl -X POST http://localhost:4601/tasks -H "Content-Type: application/json" -d '{"title":"Ship it"}'
{"id":3,"title":"Ship it","done":false}
curl -X PATCH http://localhost:4601/tasks/1 -H "Content-Type: application/json" -d '{"done":true}'
{"id":1,"title":"Write lesson","done":true}
curl -i -X DELETE http://localhost:4601/tasks/2
HTTP/1.1 204 No Content
curl -w " [%{http_code}]" http://localhost:4601/tasks/99
{"error":"NotFound","message":"Task not found"} [404]
Every response here traveled through all three layers correctly: app.js routed the request to taskRoutes.js, which called the matching taskController.js function, which called taskModel.js, and the result flowed back the same way, this is Lesson 1’s separation, now proven working end to end, not just described.
Try It
- Build this exact project structure (
models/,controllers/,routes/,app.js) using your owntaskModel.jsandtaskController.jsfrom Lessons 2 and 3, and confirm every route works withcurl. - Add a
GET /tasks?done=truefilter (Module 9’s filtering pattern) to the model, controller, and route together, tracing the change through all three layers. - Mount the same router under a different prefix (
/api/tasksinstead of/tasks) by changing onlyapp.js, and confirm nothing inroutes/,controllers/, ormodels/needed to change. - Explain, in your own words, why a path defined as
router.get('/:id', ...)insidetaskRoutes.jsdoesn’t need to know it will eventually be mounted at/tasks.
Recap
- A routes file imports a controller and maps each path/method combination (
express.Router()) directly to a controller function, no logic of its own. app.use(prefix, router)mounts an entire router under a URL prefix, every path inside the router is relative to that prefix.- The complete request flow,
app.jsto routes to controller to model and back, is what proves Lesson 1’s MVC separation actually works, not just as a diagram, but as running code.
Next lesson: structuring a growing Express app, folder conventions beyond a single resource.