David Díaz.

Organizing a Javascript MVC Project

I've been doing a lot of javascript development lately. My work as a frontend developer for Laureate has forced me to seriously how we organize the frontend code so it's easy to work with and hard for us to make mistakes.

As it stands, I developed the frontend structure of the project (I am not sure if I am allowed to share it) we are currently building. We use can.js as our MVC framework of choice and use grunt to stick all the files together and check them for errors. All this, in the effort to be as agile as possible.

Coming up with a structure was pretty hard but, I think out current structure works pretty well. Our folder structure looks somewhat like this:

    -Project
        -Application
            -Assets
                -Images
                -Css
            -Javascript
                -Models
                -Views
                -Controllers
                -libs
                    -Plugins
                    jQuery.js
                app.js
        -dist
            -app.min.js
            -libs.min.js
        index.html
        grunt.js

It might look complicated, but it's actually fairly simple. I'll try to break it down, show how we use grunt to generate app.min.js and libs.min.js and how they all generally fit in together.

Folder & File Structure

The folder structure is fairly straight forward. Inside the Application folder we hold all of our important files. The only important folder here is the one called Javascript, this is where we store all of our Models, Views and Controllers which at the end, make up our entire application. You can have more folders inside of these and grunt can still fetch all of their contents to build up the final files.

Here's what a raw controller inside this folder looks like:


;(function(namespace, undefined) {
  'use strict';
  var ControllerName = can.Control({
    'init': function(element, options) {
      var self = this;
      // Actual code stuff
    }
  });

  namespace.Controllers = namespace.Controllers || {};
  namespace.Controllers.ControllerName = ControllerName;
}(this));

As you can see the only thing we are doing which isn't immediately obvious is to add the controller to the namespace's Controller object. In our case, this is the window object. This is great because we don't populate the global namespace with every single controller and because we can access any controller like this: Controllers.ControllerName. We do the same for models:


;(function(namespace, undefined) {
  'use strict';
  var ModelName = can.Model({
    findOne: 'GET /getModel'
  }, {});

  namespace.Models = namespace.Models || {};
  namespace.Models.ModelName = ModelName;
}(this));

In our case, views are simple .ejs files so there's nothing special about how we define them, although you could compile them to javascript and tell grunt to minimize it along with the whole code which would probably faster to load in the long run.

Now that we have our files in place, let's move into using grunt to generate the finalized application files will be using in production.

Grunt

Here's what an example grunt file would look like:


module.exports = function(grunt) {
  grunt.initConfig({
    concat: {
      app: {
        src: [
          'Application/Assets/Javascript/controllers/**/*.js', 
          'Application/Assets/Javascript/models/**/*.js', 
          'Application/Assets/Javascript/app.js'
        ],
        dest: 'dist/app.js'
      },
      libs: {
        src: [
          'Application/Assets/Javascript/libs/jQuery.js', 
          'Application/Assets/Javascript/libs/plugins/**/*.js'
        ],
        dest: 'dist/libs.js'
      }
    },
    min: {
      app: {
        src: ['<config:concat.app.dest>'],
        dest: 'dist/app.min.js'
      },
      libs: {
        src: ['<config:concat.libs.dest>'],
        dest: 'dist/libs.min.js'
      }
    }
  });

  grunt.registerTask('default', 'concat min');
};

This particular grunt file doesn't do much. When you call the grunt command it'll concatenate all of the files in the javascript folder (except for the views) and all of the libraries into two files, libs.js and app.js (and their minimized equivalents). These can be included directly into the html to have the final application files.

That's it

I hope this is somewhat useful to someone, it actually took me a while to find something I was comfortable with so try experimenting with variations and let me know about it. This is how I currently do things but, I am sure there are places where I can improve the design. This mostly revolves around keeping the global namespace as clean as possible and making files that can be easily concatenated to save space when you have to finalized copy which, I think are generally good goals.

Have a comment? Feel free to email me.
Did you enjoy this post? Buy me a coffee ☕️.