Modular Routing
Why do we need
Imagine this scenario: There are students, enrolled in a course, and many teachers teach that course. If you use the basic routing methods we learned in the previous blog, then you would have to write all the API in one file, and the index.js file would look something like this:
import express from "express";
const app = express();
// Configuring middlewares
app.use(express.json());
// We will keep data here
let students = [];
let teachers = [];
let courses = [];
// CRUD operations for students
app.get("/students", (req, res) => {
res.send(students);
});
app.post("/students", (req, res) => {
students.push(req.body);
res.send(students);
});
app.put("/students/:id", (req, res) => {
const id = req.params.id;
const student = students.find((student) => student.id == id);
student.name = req.body.name;
res.send(student);
});
app.delete("/students/:id", (req, res) => {
const id = req.params.id;
students = students.filter((student) => student.id != id);
res.send(students);
});
// CRUD operations for teachers
app.get("/teachers", (req, res) => {
res.send(teachers);
});
app.post("/teachers", (req, res) => {
teachers.push(req.body);
res.send(teachers);
});
app.put("/teachers/:id", (req, res) => {
const id = req.params.id;
const teacher = teachers.find((teacher) => teacher.id == id);
teacher.name = req.body.name;
res.send(teacher);
});
app.delete("/teachers/:id", (req, res) => {
const id = req.params.id;
teachers = teachers.filter((teacher) => teacher.id != id);
res.send(teachers);
});
// CRUD operations for courses
app.get("/courses", (req, res) => {
res.send(courses);
});
app.post("/courses", (req, res) => {
courses.push(req.body);
res.send(courses);
});
app.put("/courses/:id", (req, res) => {
const id = req.params.id;
const course = courses.find((course) => course.id == id);
course.name = req.body.name;
res.send(course);
});
app.delete("/courses/:id", (req, res) => {
const id = req.params.id;
courses = courses.filter((course) => course.id != id);
res.send(courses);
});
app.listen(3000);
This is not a good practice. Let's say you want to add routes for handling books, users, attendance, results, timetables, etc. You would have to add all these routes in the same file. It would make your code messy and hard to read and maintain (maintaining means adding more features in the future). As a programmer, you should make your code look cleaner, easier to read, and easier to maintain. OK but what is the solution? 😒
This is where modular routing comes in.
Modular routing is a way to organize your routes in a more structured way. It is a way to split your routes into multiple files and then import them into your main file. This makes your code more readable and maintainable. It also makes it easier to add new routes to your application. There are several ways to create routes. For this blog, I'm, going to use the express.Router middleware. Express.Router is kinda standard for modular routing in Express.js.
Above was the theory part. Now, let's see how we can implement modular routing in Express.js 🚀
Modular Routing
To implement modular routing in Express.js, you can create separate JavaScript files for each group of related routes, and then use the Express.Router() middleware to define the routes for each file.
Here's an example of how you might define modular routes:
-
Create a new JavaScript file for your routes, such as
userRoute.js. -
In this file, create a new instance of the Router middleware by calling
express.Router(). -
Define your routes using the router instance, just as you would in your main file i.e.
index.js. For example:userRoute.jsimport express from "express";const router = express.Router();router.get("/", (req, res) => {res.send("Welcome to the user page!");});router.get("/profile", (req, res) => {res.send("This is the user profile page!");});export default router; -
Export the router instance using
export default routerfrom theuserRoute.jsfile. This will make the router instance available for use in other files. -
Import the router instance into your main Express.js file, for example,
index.js, and use it as middleware for the/userspath. You can do this by callingapp.use()with the path and the router instance as arguments. This will tell Express.js to use the router instance for all routes starting with/users, as shown in the following example:index.jsimport express from "express";import userRoute from "./userRoute.js";const app = express();app.use("/users", userRoute);// other middleware and routes hereapp.listen(3000, () => {console.log("Server listening on port 3000");});
In JavaScript, you can use the import and export keywords to import and export modules. You can read more about this in the Exporting and Importing Modules
Highly recommended to read the Exporting and Importing Modules article before continuing, otherwise, you will struggle to work with import and export in JavaScript.
In this example, we've defined two routes for the /users path using the userRoute.js file. We've then imported and used these routes in our main Express.js file (index.js) by calling app.use() with the prefix path and the router instance as arguments. This tells Express.js to use the router instance for all routes starting with /users.
By organizing your routes into separate modules like this, you can make your code easier to read, understand, and maintain, especially as your application grows larger and more complex.
Now, let's implement the top example using modular routing.
Directory/File structure
First of all, let me show you the directory/file structure of the project. This is how the project will look after implementing modular routing.
├── 📂 modularRouting
│ ├── 📂 routes
│ │ ├── 📄 courses.js
│ │ ├── 📄 students.js
│ │ └── 📄 teachers.js
│ ├── 📄 index.js
│ └── 📄 package.json
package.json:
Create a new folder, and name it anything, I named it modularRouting. Open the folder in VS Code. Now, create a new file, naming it package.json. Copy the following code and paste it into the package.json file.
{
"name": "modular-routing",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
"author": "@mrizwanashiq",
"license": "ISC",
"dependencies": {
"express": "^4.17.2",
"nodemon": "^2.0.15"
}
}
You can also use npm init to create a package.json file, and then do all the changes manually like we did in previous articles.
Now, open the terminal and run the following command to install all the dependencies.
- npm
- Yarn
- pnpm
- Bun
npm install
yarn install
pnpm install
bun install
index.js:
Now, create a new file, naming it index.js. For now, index.js will look like this 👇, but we'll add more code to it later.
import express from "express";
const app = express();
// Configuring middlewares
app.use(express.json());
// Listening to port
app.listen(3000);
routes/students.js:
We'll create routes for students, teachers, and courses by creating a separate file for each. Let's start with students. Create a new folder, naming it routes. Inside the routes folder, create a new file, naming it students.js.
I will use express.Router() to create a router instance. In the previous article, we used app.get(), app.post(), app.put(), and app.delete() methods but here we'll use router.get(), router.post(), router.put(), and router.delete() methods and export the router instance using export default router. The final code will look like:
import express from "express";
const router = express.Router();
// Students array to store students' data
let students = [];
// Routes for students
router.get("/", (req, res) => {
res.send(students);
});
router.post("/", (req, res) => {
students.push(req.body);
res.send(students);
});
router.put("/:id", (req, res) => {
const id = req.params.id;
const student = students.find((student) => student.id == id);
student.name = req.body.name;
res.send(students);
});
router.delete("/:id", (req, res) => {
const id = req.params.id;
students = students.filter((student) => student.id != id);
res.send(students);
});
// Exporting router object, which will be used in index.js
export default router;
routes/teachers.js:
Now, create a new file, naming it teachers.js. The code will look like this:
import express from "express";
const router = express.Router();
let teachers = [];
router.get("/", (req, res) => {
res.send(teachers);
});
router.post("/", (req, res) => {
teachers.push(req.body);
res.send(teachers);
});
router.put("/:id", (req, res) => {
const id = req.params.id;
const teacher = teachers.find((teacher) => teacher.id == id);
teacher.name = req.body.name;
res.send(teachers);
});
router.delete("/:id", (req, res) => {
const id = req.params.id;
teachers = teachers.filter((teacher) => teacher.id != id);
res.send(teachers);
});
export default router;
routes/courses.js:
Now, create a new file, naming it courses.js. The code will look like this:
import express from "express";
const router = express.Router();
let courses = [];
router.get("/", (req, res) => {
res.send(courses);
});
router.post("/", (req, res) => {
courses.push(req.body);
res.send(courses);
});
router.put("/:id", (req, res) => {
const id = req.params.id;
const course = courses.find((course) => course.id == id);
course.name = req.body.name;
res.send(courses);
});
router.delete("/:id", (req, res) => {
const id = req.params.id;
courses = courses.filter((course) => course.id != id);
res.send(courses);
});
export default router;
Modifying index.js:
Time to modify the index.js file. First, import the routes in the index.js file, and attach them to the app object as a middleware using the app.use() method. The final code will look like this:
import express from "express";
// Importing routes
import students from "./routes/students.js";
import teachers from "./routes/teachers.js";
import courses from "./routes/courses.js";
const app = express();
// Configuring middlewares
app.use(express.json());
// Using routes as middleware
app.use("/students", students);
// '/students' is the prefix for the student's routes
// We can use any prefix we want, for example, '/api/students', '/api/v1/students', etc.
// We can also use multiple prefixes, for example, '/api/v1/students', '/api/v2/students', etc. like this: 👇
// app.use('/api/v1/students', '/api/v2/students', students);
// This is useful when we want to use the same routes for multiple versions of the API
app.use("/teachers", teachers);
// '/teachers' is the prefix for the teacher's routes
app.use("/courses", courses);
// '/courses' is the prefix for the courses routes
app.listen(3000);
You see, first, we imported all the routes in the index.js file. Then we used the app.use() method to use the routes. The first parameter is the prefix for the routes. For example, if we use app.use("/students", students), and the path provided in the routes file, and we use router.get("/all", (req, res) => {}) in the routes/students.js file, then the full path will be /students/all. The same goes for the other routes.
If you don't provide the prefix, then the routes will be used without any prefix. For example, if we use app.use(students), and the path provided in the routes file, and we use router.get("/all", (req, res) => {}) in the routes/students.js file, then the full path will be /all.
All the routes that you created let's say in the routes folder, must be imported and attached to the app object as middleware using app.use() method. Otherwise, they won't work, those APIs will not be accessible to the client, and you will get a 404 error. So, don't miss this step. None of the steps are optional, you have to follow all the steps to make it work properly 😊
Code
You can download the related code from here, or you can clone the repository, and then checkout to the example/modular-routing-code branch.
git clone https://github.com/mrizwanashiq/learning-express-js.git
cd learning-express-js
git checkout example/modular-routing-code
If you already have the repository, then just checkout to the example/modular-routing-code branch.
git checkout example/modular-routing-code
Summary
In this article, we learned about modular routing in Express.js. We created a simple API using Express.js, and then we created multiple route files and used them in the index.js file as middleware. We also learned about the prefix for the routes, and how to use the same routes for multiple versions of the API.