Structuring an Express MVC API – Part 2

Summary

In the first post we learned a bit about the MVC pattern itself. Now that we have the pattern down lets see how we can structure our application to follow this pattern. Keep in mind there are many ways to structure an MVC application and this is just one way. Also, since our application is relatively simple we will be organizing it by type instead of by feature. In larger projects organizing your files by feature is nice alternative to use, but for simple project it can be overkill.

However, the most important thing to remember is that whatever structure you choose to follow you stick to it consistently.

Application Structure

The application structure is best introduced from the below image:

todo-list-app-structure
todo-list-app-structure

As you can see the application is nicely laid out into two main folders under the src directory (client and server). Below I will discuss the purpose of each one of the server folders so that you will have a good understanding of how this all fits together.

Server

The server directory is pretty self explanatory. It’s the home to all server side code and it contains the main server side entry point, server.js. This file is very small and it’s purpose is to simply serve as the entry point for our application.

server.js:

process.env.NODE_ENV = process.env.NODE_ENV || 'development';

var express = require('./config/express');
var mongoose = require('./config/mongoose');

var db = mongoose();
var app = express();
app.listen('3000');

module.exports = app;
console.log('Server started on port http://localhost:3000');

List of folders under the server directory:
Config
Controllers
Models
Routes
Views

Config

This directory is home to all of the application configuration stuff. Two main configuration items that I like to have in this directory are:

  • express.js
  • mongoose.js

The express.js file contains all of the express configuration code.

Here is a snippet from express.js.

var express = require('express');
var morgan = require('morgan');
var compress = require('compression');
var bodyParser = require('body-parser');
var methodOverride = require('method-override');

module.exports = function () {
var app = express();

if (process.env.NODE_ENV === 'development') {
    // log all requests
    app.use(morgan('dev'));
} else {
    app.use(compress());
}

app.use(bodyParser.urlencoded(
{
    extended: true
}));
app.use(bodyParser.json());

// support for PUT and DELETE verbs
app.use(methodOverride());

app.set('views', './src/server/views');
app.set('view engine', 'ejs');

require('../routes/index.route.js')(app);
require('../routes/todo.route.js')(app);

As you can see we register all the express middleware and initialize each of our routes from this file.

Mongoose.js

There are two responsibilities of this file:

  • Connect to MongoDB
  • Initialize our Model objects

Here is the file in it’s entirety.

var config = require('./config');
var mongoose = require('mongoose');

module.exports = function () {
var db = mongoose.connect(config.db);

// load models here
require('../models/todo.model');

return db;
};
Env

The env directory is where the server configuration will live. At this time we currently have a development and production file which are loaded based upon the process.evn.NODE_ENV variable. As you can see from the config.js we are concatenating the node environment with the require path so that when we require(config) we will have the correct config settings for our currently running environment.

module.exports = require('./env/' + process.env.NODE_ENV + '.js');

Having an environment specific config is a best practice and will help out when deploying our application to a production server.

Controllers

As I mentioned in the summary section our application structure is organized by type so as you have probably figured out the controllers directory contains all of the controllers. Currently our ToDo application has two controllers: index.controller.js and todo.controller.js. The naming convention that I like use is to add the word controller to the file so that I can re-use both index and todo in other file names but still be able to use IDE navigation shortcuts to clearly see which files I’m navigating.

Models

You can probably guess where this is going by now, but models does just what is says. All models are defined and stored in this directory.

ToDo Model Example:

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var ToDoSchema = new Schema({
    description: {type: String},
    dueDate: {
        type: Date,
        default: Date.now
    },
    isComplete: {type: Boolean, default: false}
});

mongoose.model('Todo', ToDoSchema);

In another post I will dive a lot deeper into mongoose which is what we are using to create our Model objects.

Routes

Routes are the core of any API and one of the conventions that I typically use is to begin each route with /api. The reason that I do this to easily distinguish the difference between api and non api routes.

In our MVC API we don’t have any logic in the routing files. Instead the routes are in charge of simply delegating to the correct controller functions.

todo.route.js

var controller = require('../controllers/todo.controller.js');

module.exports = function (app) {
    app.route('/api/todos')
        .post(controller.create)
        .get(controller.list);

    app.route('/api/todos/:id')
        .get(controller.read)
        .put(controller.update)
        .delete(controller.delete);

    app.param('id',controller.todoById);
};

As you can see on the final line we are calling app.param which will run before any of the other middleware so that by the time our routing code gets ran the :id has already been parsed from the request path and added to the request object as req.todo Once that is complete, all the /api/todos/:id routes will have the req.todo object available.

Here is the magic behind the controller.todoById function:

exports.todoById = function (req, res, next, id) {
    Todo.findOne({_id: id}, function (err, todo) {
        if (err) {
            return next(err);
        } else {
            req.todo = todo;
            next();
        }
    })
};

In the above snippet we are simply using the Mongoose findOne function to find our Todo by _id from mongo. Once successfully found we add the object to the req object and call the next middleware (which happens to be all the routes that require the :id.

Views

The final directory that we have is the views directory and as you can guess this is where the view code will live. What view code you might ask? Well, even in our simple API app we still have one static view for the main index page and this is where that file is held.

index.ejs

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Todo It!</title>
</head>
<body>
    <h1>It's already <%= now %> better get something done! </h1>
    <div class="todo-container">
        <form>
            <input type="text" placeholder="What should I get done?"/>
            <input type="submit"/>
        </form>
    </div>
</body>
</html>

In the above code may have noticed the <%= now %>. This is simply the syntax required from our server side view engine EJS.

Final thoughts

This two part series had quite a bit of content that wasn’t covered in too much depth because topics like MongoDB, Mongoose, Express Routing, EJS, etc… could all be several posts in themselves. Hopefully this still gave you an idea for how to structure a simple Express API using MVC.

Last but not least, we didn’t even give AngularJS love with this post, but rest assured we will be getting into a lot of Angular with future posts.

Leave a Reply

Your email address will not be published. Required fields are marked *