Skip to content
bobby_dreamer

Modularizing ExpressJS Routes and Middlewares

nodejs2 min read

As the title says, but what is the need of this ?

Well i have index.js which is about 3000+ lines. I started building an app and at the same time I was learning NodeJS and didn't think about separating routes to external files as i did not think, it will end up this big, so all my routes code ended up in index.js and again at the same time I was also started learning and using Firebase functions, so that also ended up in index.js.

Interestingly there is no problem with the code for last 4-5 years, thats the number of years, i havent touched it or reviewed it. It works perfectly. Currently i got plans to decommission some pages, functions and routes. Since its massive, i am little worried that i might break things unnecessarily.

Looking at the WWW on the topics of Modularizing code in NodeJS came across few patterns which i will be following,

  1. Big Bang Pattern - My current pattern, all in one file. You can call it index.js, app.js, server.js whatever you want but all your app code is in that one file.

  2. All routes are externalized to a separate route folder

  3. tjholowaychuk's way of modularizing

  4. Dynamically including all the routes

Code: Available in GitHub



1. Big Bang Pattern

Not going to explain on Big Bang Pattern much, it basically looks like below code. Middleware, Routes, 404 Invalid Route Handling all in one page.

index.js
1const express = require('express');
2const app = express();
3
4/************************************************/
5// Middleware
6
7// Simple request time logger
8app.use((req, res, next) => {
9 console.log("A new request received at " + Date.now());
10 next();
11});
12
13/************************************************/
14// Routes
15
16app.get('/', (req, res) => res.send('Hello World!'));
17
18app.get('/home', (req, res) => {
19 res.send('Home Page');
20});
21
22app.get('/about', (req, res) => {
23 res.send('About');
24});
25
26app.get('/books/:bookId', (req, res) => {
27 res.send(req.params);
28});
29
30/************************************************/
31// Final Invalid Route
32app.get('*', (req, res) => {
33 res.send('404! This is an invalid URL.');
34});
35
36/************************************************/
37// Listener
38app.listen(3000, () => console.log('Example app listening on port 3000!'));

We will be building upon this for the all pattern examples.


2. All routes are externalized to a route folder

Here we have externalized routes to a separate folder ./routes and created a separate .js file for each route and grouped some in a single file.

Below i have highlighted some important lines

  1. Requiring the files from ./routes folder
  2. Using app.use(), mounting the routes in index.js
  3. Using app.get()

Whats the different between app.use() and app.get()

  1. app.use() is intended for middleware. The path is a "mount" or "prefix" path and limits the middleware to only apply to any paths requested that begin with it. app.use() will respond to all the HTTP verbs(GET, POST, PUT)

  2. app.get() is part of ExpressJS application routing and is intended for matching and handling a specific requested route. This allows you to align responses for requests more precisely than app.use(). When using app.use() for routing, you might require extra code to identify what type of HTTP request, routes and parameters passed.

index.js
1const express = require('express');
2const app = express();
3
4// Routes
5const books = require('./routes/books')
6const number = require('./routes/number')
7const posts = require('./routes/posts')
8const user = require('./routes/user')
9const wiki = require('./routes/wiki')
10
11/************************************************/
12// Routes
13app.use(require('./routes/homepages')); //http://localhost:3000/ http://localhost:3000/about
14app.use("/main",require('./routes/homepages')); //http://localhost:3000/main http://localhost:3000/main/about
15
16app.use(number); //http://localhost:3000/number/12
17
18app.use("/books",books); //http://localhost:3000/books/12
19app.use("/posts",posts); //http://localhost:3000/posts/one
20app.use("/user",user); //http://localhost:3000/user/profile
21
22app.get("/wiki", wiki.wikiData)
23
24/************************************************/
25// Final Invalid Route
26app.get('*', (req, res) => {
27 res.send('404! This is an invalid URL.');
28});
29
30/************************************************/
31// Listener
32let server = app.listen(3000, function () {
33 console.log(`Example app listening at http://localhost:${server.address().port}`)
34});

Here we are using express.Router class to create modular, mountable route handlers. A Router instance is a complete middleware and routing system.

At the end, you export this module and you can import and consume by requiring it index.js

./routes/homepages.js
1var express = require('express');
2var router = express.Router();
3
4/************************************************/
5// Routes
6router.get('/', (req, res) => res.send('Hello World!'));
7
8router.get('/home', (req, res, next) => {
9 try{
10 res.send('Home Page');
11 } catch(error) {
12 next(error) // calling next error handling middleware
13 }
14});
15
16router.get('/about', (req, res, next) => {
17 try{
18 res.send('About Page');
19 } catch(error) {
20 next(error) // calling next error handling middleware
21 }
22});
23
24router.get('/error', (req, res, next) => {
25 try{
26 console.log('Error Page');
27 throw new Error("Error Page")
28 } catch(error) {
29 next(error) // calling next error handling middleware
30 }
31});
32
33module.exports = router;

For wiki, we are directly writing a function and exporting it to be consumed in index.js

./routes/wiki.js
1exports.wikiData = function(req, res){
2 res.send('Wiki');
3};

3. tjholowaychuk's way of modularizing

Source: Modular web applications with Node.js and Express

In this technique, we are moving the routes to its own separate folder under routes. Main index.js is simple enough where we are just requiring and using app.use() to mount the routes.

index.js
1const express = require('express');
2const app = express();
3
4const homepages = require('./routes/homepages')
5const books = require('./routes/books')
6const number = require('./routes/number')
7const posts = require('./routes/posts')
8const user = require('./routes/user')
9const wiki = require('./routes/wiki')
10
11/************************************************/
12// Routes
13app.use(homepages)
14app.use(books)
15app.use(number)
16app.use(posts)
17app.use(user)
18app.use(wiki)
19
20/************************************************/
21// Final Invalid Route
22app.get('*', (req, res) => {
23 res.send('404! This is an invalid URL.');
24});
25
26/************************************************/
27// Listener
28app.listen(3000, () => console.log('Example app listening on port 3000!'));

Highlighted is how we are exporting the app to be used by Main index.js

./routes/wiki/index.js
1var express = require('express');
2var app = module.exports = express();
3
4/************************************************/
5// Routes
6app.get('/wiki', (req, res, next) => {
7 try{
8 res.send('Wiki');
9 } catch(error) {
10 next(error) // calling next error handling middleware
11 }
12});

4. Dynamically including all the routes

In this approach, routes are dynamically included meaning ./routes/index.js will read all the .js files in the folder and export it.

index.js
1const express = require('express');
2const app = express();
3
4/************************************************/
5// Routes
6require('./routes')(app);
7
8/************************************************/
9// Final Invalid Route
10app.get('*', (req, res) => {
11 res.send('404! This is an invalid URL.');
12});
13
14/************************************************/
15// Listener
16app.listen(3000, () => console.log('Example app listening on port 3000!'));

This file reads all .js files in the routes folder and requires it and creates an instance app and thats exported which is consumed by index.js.

./routes/index.js
1var fs = require('fs');
2
3module.exports = function(app){
4 fs.readdirSync(__dirname).forEach(function(file) {
5 if (file === "index.js" || file.substring(file.lastIndexOf('.') + 1) !== 'js') return;
6 var name = file.substring(0, file.indexOf('.'));
7 require('./' + name)(app);
8 });
9}

Below is the books.js routes file which exports one or more routes.

./routes/books.js
1module.exports = function(app){
2
3 app.get('/books/:bookId', (req, res, next) => {
4 try{
5 console.log(req.params.bookId)
6 res.send(req.params);
7 } catch(error) {
8 next(error) // calling next error handling middleware
9 }
10 });
11
12 //Other routes here
13}

Thanks for reading