Frosty CMS: Refactoring Routes 🗃️

Intro

Continuing our series on Frosty CMS, where we are building a blog CMS from scratch. Our last post covered user authentication which was a big one. This time let’s do a bit of housekeeping. We are going to simplify our app.js file by refactoring our Express routes into modules.

Current Structure

Currently our routes are organized based on the type of HTTP request (GET, POST, etc).

get route

post route

put route

Another way to organize our routes would be to categorize them based on our REST structure (blogs, settings, etc).

We are going to do both, and we are going to fulfill our primary goal of getting these routes out of our app.js file and into separate modules. Spring cleaning!

New Structure

We start by making a new routes folder in our root directory and a new module file for each of our route categories.

refactor structure

The index category is going to be a catch-all for any routes that don’t fit neatly into the other three categories. For example, the authentication routes.

Then we’ll paste our HTTP Request structure into each of these modules:

// ***************************
// ROUTES
// ***************************

//----------------------------
// .GET routes
//----------------------------

//----------------------------
// .POST routes
//----------------------------

//----------------------------
// .PUT routes
//----------------------------

//----------------------------
// .DELETE routes
//----------------------------

And now we have a structure to start moving our routes into their new homes. This is all basic copy and pasting. It’s worth noting that there are some tricky routes to categorize. For example /blogs/:id/comments . Does this belong in the blogs module or the comments module? This would actually go in the comments module, and you will see why in just a moment.

Express Router

At this point you will start getting all sorts of errors saying that app and all of your models and functions are not defined. Because they aren’t defined! You will need to add them to the head of your route modules just like you did in your primary app. For example:

const express          = require('express'),
      app              = express();

But wait! This time we are going to do something a little bit different. Instead of defining express as app we are going to use the Express Router.

var express = require("express"),
    router  = express.Router();

You can read more about the Express Router middleware in the Express docs (bottom of the page): Express Docs: Routing

Once we have this in place we exchange all of our calls to app with router like so.

// login page
router.get("/login",(req, res) => {
    res.render("login.ejs");
});

What is the point of this? This will allow us to DRY up our code a bit by shortening our routes that are repetitive.

Export Route Modules

Like all other modules these must be exported at the end, so we add that to all our new route modules.

// export module
module.exports = router;

Import Route Modules

Once the routes have been moved to their separate categorized modules we will need to actually import those modules into our application, which would require the following in our main app.js file.

//import comment routes
const commentRoutes    = require("./routes/comments"),
      blogRoutes       = require("./routes/blogs"),
      settingRoutes   = require("./routes/settings"),
      indexRoutes      = require("./routes/index");

And of course, we must tell the app to actually use these modules after they have been imported.

// ***************************
// ROUTE CONFIGURATION
// ***************************

app.use(blogRoutes);
app.use(commentRoutes);
app.use(settingRoutes);
app.use(indexRoutes);

Undefined Models and Functions

At this point we still have some models and function undefined.

blog is not defined

Models

So we must go through and import all of our Mongoose models that have not been included in these modules that require them.

var express          = require("express"),
    router           = express.Router(),
    User             = require('./models/users'),
    Blog             = require('./models/blogs');

And we repeat that for each module with it’s required models. And doing this will actually get us an error which isn’t obvious at first. You will get an error that you cannot find the models. And what is happening here is that the models have not moved, but the location we are calling the models from has moved. Instead of calling these models from the root directory, we are now calling them from inside of the routes folder. So the server is searching for routes/models/blogs which does not exist. We need to direct the server to go back one level in the file structure, and then look for models/blogs. To do that we use the same method we use in the console to go back ..

var express          = require("express"),
    router           = express.Router(),
    passport         = require('passport'),
    User             = require('../models/users'),
    Blog             = require('../models/blogs');

Functions

The quick fix for our functions is to just copy and paste all of our middleware functions like isLoggedIn into each of our modules. So let’s do that just to get the application running, make sure we are all good, and then we can refactor further by turning these functions into their own module.

// ***************************
// MIDDLEWARE FUNCTIONS
// ***************************

// check if user is logged in
function isLoggedIn(req, res, next){
    if(req.isAuthenticated()){
        return next();
    }
    res.redirect("/login");
}

// pass through user data on every route
router.use((req,res,next) => {
    res.locals.currentUser = req.user;
    next();
});

At this point our application is running again. We will leave these functions for now and come back to DRY them up later. But now that we have all this complete we can take advantage of the Express Router and DRY up our route declarations.

Shortening Route Declarations

We start by identifying the repetitive part of each route declaration. For example in the settings section that would be /settings/. Then we insert those repetitive parts into our route declarations in app.js

app.use("/blogs", blogRoutes);
app.use("/blogs/:id/comments", commentRoutes);
app.use("/settings", settingRoutes);
app.use(indexRoutes);

and this will append those portions of the URL to the front of the route declarations in each of those modules. So we go back through our modules and shorten them by removing each of those repetitive parts. For example in the settingRoutes

// settings/dashboard
router.get("/settings/dashboard", isLoggedIn, (req, res) => {
    res.render("settings-dashboard.ejs");
});

would now just become

// settings/dashboard
router.get("/dashboard", isLoggedIn, (req, res) => {
    res.render("settings-dashboard.ejs");
});

And if we try to run this, everything will mostly be ok, however if we try to save a comment now we will get the following error.

cannot read property 'comments' of null

and the reason that is happening is because our request parameters are no longer being passed through, so req.params.id is null. To fix this we pass mergeParams: true into our router options. You can read more about this here: Express 4.x – API Referencej

var express          = require("express"),
    router           = express.Router({mergeParams: true}),
    Blog             = require('../models/blogs');

and we will be good to go.

GitHub Repo

Ncoughlin: Frosty