Build a simple client-side MVC app with RequireJS

Require JS Logo

As a web developer, you certainly often started coding your JavaScript in a single file, and, as the code base gets larger and larger, it became really difficult to maintain. To solve this problem you can split your code in several files, add more script tags and use global variables to reach functions declared in other files. But this pollutes the global namespace and for each file an additional HTTP request consumes bandwidth, which slows down the loading time of the page.

If this happened to you, you certainly understand that there is a strong need to organize our front-end code differently, particularly if we have to build large-scale web apps with thousands of lines of JavaScript. We need a new way to organize all this mess to make it easier to maintain. This new technique consists in using script loaders. Plenty of them are available on the web, but we’ll focus on a very good one called RequireJS.

In this step by step tutorial you will learn how to build a simple MVC (Model – View – Controller) app using RequireJS. You don’t need any particular previous knowledge of script loading – we’ll see the basics together.

Introduction

What is RequireJS and why it’s awesome

RequireJS is an implementation of AMD (Asynchronous Module Definition), an API for declaring modules and loading them asynchronously on the fly when they’re needed. It’s developed by James Burke, and it just reach the symbolic 1.0 version after 2 years of development. RequireJS can help you organize your code with modules and will manage for you the asynchronous and parallel downloads of your files. Since scripts are loaded only when needed and in parallel, it reduces the loading time of your page, which is great!

MVC for front-end?

MVC is a well known design pattern to organize server-side code and make it modular and sustainable. What about using it for front-end? Can we apply this design pattern to JavaScript? If you’re just using JavaScript for animating, validating few forms or any simple treatment that doesn’t require many lines of code (let’s say less than 100 lines), there is no need to structure your files using MVC, and probably no need to use RequireJS either. However if you’re building a rich web app with many different views, definitely yes!

The app we’ll create

To illustrate how to organize your MVC code using RequireJS, we’ll create a very simple app with 2 views:

  • The first view will display a list of users (represented by a name attribute),
  • The second view will allow you to add a user.

This is how it will look like at the end:

Require JS MVC App

The business logic is obviously super simple so you can focus on understanding what’s really important here: structuring your code. And since it’s that simple, I strongly recommend that you really try to do it in parallel of reading this tutorial. It won’t take long, and if you have never done modular programming or used RequireJS before, coding this example will really help you become a better programmer. Seriously, it’s worth it, you should do it.

HTML and CSS files

Here is the HTML markup we’ll use for this example:

<!doctype html>
<html>
<head>
	<meta charset="utf-8">
	<title>A simple MVC structure</title>
	<link rel="stylesheet" href="css/style.css">
</head>
<body>
	<div id="container">
		<h1>My users</h1>
		<nav><a href="#list">List</a> - <a href="#add">Add</a></nav>
		<div id="app"></div>
	</div>
	<script data-main="js/main" src="js/require.js"></script>
</body>
</html>

The navigation in our app will be the links of the nav menu which will remain present on each page of the app, and all the magic of the MVC application will happen in the #app div. We also included RequireJS (which you can grab here) at the bottom of the body, and you can notice a special attribute on the script tag: data-main="js/main". The value passed to this attribute is used by RequireJS as the entry point of the entire application.

Let’s also add just a little bit of basic styling:

#container{
	font-family:Calibri, Helvetica, serif;
	color:#444;
	width:200px;
	margin:100px auto 0;
	padding:30px;
	border:1px solid #ddd;
	background:#f6f6f6;
	-webkit-border-radius:4px;
	   -moz-border-radius:4px;
	        border-radius:4px;
}

h1, nav{
	text-align:center;
	margin:0 0 20px;
}

OOP reminder: What is a module?

In JavaScript Object-Oriented Programming, there is a very common design pattern called Module Pattern. It is used to encapsulate methods and attributes in objects (which are the “modules”) to avoid polluting the global namespace. It is also used to kind of simulate classes from other OOP languages like Java or PHP. This is how you would define a simple MyMath module in our main.js file:

var MyMath = (function(){

	// Put your private variables and functions here
	
	return { // Here are the public methods
		add:function(a, b){
			return a + b;
		}
	};
})();

console.log(MyMath.add(1, 2));

Public methods are declared using the object literal notation, which is not very convenient. You can alternatively use the Revealing Module Pattern, which returns private attributes and methods:

var MyMath = (function(){

	// With this pattern you can use the usual function notation:
	
	function add(a, b){
		return a + b;
	}
	
	return {
		add:add // But don't forget to declare it in the returned object!
	};
})();

console.log(MyMath.add(1, 2));

I will be using the Revealing Module Pattern in the rest of this article.

RequireJS

Defining a module with RequireJS

In the last section we declared a module in a variable to call it later. This is just one way to declare modules. We’re now going to see a (barely!) different method used by RequireJS. The purpose of RequireJS is to split our JavaScript files for a better maintainability, so let’s create a MyMath.js file to define our MyMath module in the same folder as main.js:

define(function(){

	function add(a, b){
		return a + b;
	}
	
	return {
		add:add
	};
});

Instead of declaring a variable, we just put the module as a parameter of the define function. This function is provided by RequireJS and will make our module accessible from the outside.

Requiring a module from the main file

Let’s go back to our main.js file. RequireJS provides a second function called require that we’re going to use to call our MyMath module. Here is what our main.js now looks like:

require(['MyMath'], function(MyMath){
	
	console.log(MyMath.add(1, 2));	

});

The call to MyMath is now wrapped in the require function, which takes 2 parameters:

  • An array of modules we want to load, declared with their path relative to the entry point (remember the data-main attribute in the HTML) and without the .js extension,
  • A function to call once these dependencies are loaded. The modules will be passed as parameters of this function so you can simply name those parameters with the same modules names.

You can now reload the page and… congratulations! You just called a method from an other file! Yes, that was super easy and you’re now ready for the big scary MVC architecture (which works exactly like the module definition you just did, so be sure you’ll be doing great!).

The MVC structure

Important note: In this tutorial, we’ll mimic the server-side MVC in which 1 controller = 1 view. In front-end development, it is very common to have multiple views per controller. In this case views become visual components like buttons or fields. MVC JavaScript frameworks like Backbone use this different approach which is not the purpose of this article. My goal here is not to create a real life entire MVC framework, but simply illustrate how RequireJS works through a structure that many of you already know well.

Let’s start with the part I’m sure you love: creating all the folders and files of our project. We want to use Models to represent data, the business logic will be handled by Controllers, and those controllers will call specific Views to render pages. So guess what? We need 3 folders: Models, Controllers and Views. Considering our simple app case, we’ll have 2 controllers, 2 views and 1 model.

Our JavaScript folder now looks like this:

  • Controllers
    • AddController.js
    • ListController.js
  • Models
    • User.js
  • Views
    • AddView.js
    • ListView.js
  • main.js
  • require.js

Got the structure ready? Great! Let’s start implementing the simplest part: the Model.

The Model: User.js

In this example, a User will be a simple class with a name attribute:

define(function(){
	
	function User(name){
		this.name = name || 'Default name';
	}
	
	return User;
});

If we now come back to our main.js file, we can declare the dependency to User in the require method, and manually create a set of users for the purpose of this example:

require(['Models/User'], function(User){
	
	var users = [new User('Barney'),
	             new User('Cartman'),
	             new User('Sheldon')];
	
	for (var i = 0, len = users.length; i < len; i++){
		console.log(users[i].name);
	}
	
	localStorage.users = JSON.stringify(users);
});

We then serialize in JSON the users array and store it in the HTML5 local storage to make it accessible just like a database:

Chrome Console showing the users

Note: JSON serialization with stringify and deserialization with parse need a polyfill to work in IE7 and inferiors. You should use Douglas Crockford’s json2.js from his Github repository for this.

Displaying the users list

It’s time to display those users in the app interface! We’ll have to work with ListController.js and ListView.js to do that. Those 2 components are obviously related, and they’ll be linked someway. There are many ways we can do that, and to keep this example simple, here is what I suggest: The ListView will have a render method and our ListController will simply get the users from the local storage and call ListView’s render method by passing the users as a parameter. So obviously, ListController needs ListView as a dependency.

Just like with require, you can pass an array of dependencies to define if the module relies on other modules. Let’s also create a start method (or any other name that makes sense to you – like run or main), to put the main behavior of the controller in it:

define(['Views/ListView'], function(ListView){
	
	function start(){
		var users = JSON.parse(localStorage.users);
		ListView.render({users:users});
	}
	
	return {
		start:start
	};
});

Here we deserialize the users from the local storage and pass it to render as an object. Now, all we have to do is implementing a render method in ListView.js:

define(function(){
	
	function render(parameters){
		var appDiv = document.getElementById('app');

		var users = parameters.users;
		
		var html = '<ul>';
		for (var i = 0, len = users.length; i < len; i++){
			html += '<li>' + users[i].name + '</li>';
		}
		html += '</ul>';
		
		appDiv.innerHTML = html;
	}

	return {
		render:render
	};
});

This method simply loops on our users to concatenate them in an HTML string we inject in the #app element.

Important: Using plain HTML in a JavaScript file like this is not an ideal solution, because it’s very hard to maintain. You should instead consider templating. Templates are an elegant way to insert data in HTML markup. Many very good templating systems are available on the web. You can for instance use jQuery-tmpl or Mustache.js. But this is beyond the scope of this article and it would add complexity to the current architecture, so I prefer keeping it simple.

Now, all we need to do is to “run” our ListController module. To do that let’s declare it as a dependency of require in our main.js file, and call ListController.start():

require(['Models/User', 'Controllers/ListController'], function(User, ListController){
	
	var users = [new User('Barney'),
	             new User('Cartman'),
	             new User('Sheldon')];
	
	localStorage.users = JSON.stringify(users);
	
	ListController.start();	
});

You can now refresh your page to get this wonderful list of users:

MVC users list

Woooaaaah, it works! Congratulations if you coded this too!

Note: For the moment, we can only manually declare the controller we want to run since we don’t have any routing system yet. But we’ll create a very simple one pretty soon, be patient!

Adding a user

We now want to be able to add users to our list. We’ll display a simple text input and a button, with an event handler when the button is clicked to add the user to the local storage. Let’s start with AddController, just like we did in the previous section. This file will be pretty simple since we don’t have any parameter to give to the view. Here is AddController.js:

define(['Views/AddView'], function(AddView){

	function start(){
		AddView.render();
	}

	return {
		start:start
	};
});

And its corresponding view:

define(function(){
	
	function render(parameters){
		var appDiv = document.getElementById('app');
		appDiv.innerHTML = '<input id="user-name" /><button id="add">Add this user</button>';
	}
	
	return {
		render:render
	};
});

You can now declare AddController as a dependency in your main file and call its start method to successfully get the expected view:

MVC Add user

But since we don’t have any event bind on the button yet, this view is not very useful… Let’s work on that. I have a question for you: Where should we put the event logic for this event? In the view? In the controller? If we put it in the view it would be the right place to add the events listeners, but putting the business logic in a view would be a very bad practice. Putting the event logic in the controller seems to be a better idea, even if it’s not perfect because we don’t want to see any div’s ID in there, which belong to the view.

Note: The best way to go would be having event listeners in the view, which would call business logic methods located in the controller or in a new dedicated events module. This is really easy to do but it would make this example more complex and I don’t want you to be lost. Feel free to try doing it for practicing!

As I said, let’s put all the event logic in the controller. We can create a bindEvents function in AddController and call it after the view has finished rendering the HTML:

define(['Views/AddView', 'Models/User'], function(AddView, User){

	function start(){
		AddView.render();
		bindEvents();		
	}
	
	function bindEvents(){
		document.getElementById('add').addEventListener('click', function(){
			var users = JSON.parse(localStorage.users);
			var userName = document.getElementById('user-name').value;
			users.push(new User(userName));
			localStorage.users = JSON.stringify(users);
			require(['Controllers/ListController'], function(ListController){
				ListController.start();
			});
		}, false);
	}

	return {
		start:start
	};
});

In bindEvents, we simply add an event listener for clicks on the #add button (feel free to use your own function to deal with IE’s attachEvent – or just use jQuery). When the button is clicked, we get the users string from the local storage, deserialize it to get the array, push a new user with the name contained in the #user-name input field, and put the updated users array back to the local storage. After that we finally require the ListController to execute its start method so we can see the result:

MVC List - User added

Brilliant! It’s time for you to take a break, you’ve done a very good job if you’re still doing this example with me. You deserve a cup of coffee before we continue!

Navigation between views with routes

Okay back to work. Our little app is pretty cool but it really sucks that we still cannot navigate between views to add more users. What is missing is a routing system. If you’ve worked with server-side MVC frameworks before, you’re probably familiar with this. Each URL leads to a different view. However here we’re client-side and it’s slightly different. JavaScript single page interfaces like this one use the hash of the URL to navigate between different parts of the app. In our case, we want to be able to reach the 2 different views when hitting those URLs:

  • http://yourlocalhostpath/#list
  • http://yourlocalhostpath/#add

This will make each page of our app bookmarkable and easily reachable.

Note: Firefox, Chrome and Opera also have a pretty good support of HTML5 history management (pushState, popState, replaceState), which saves you from dealing with hashes.

Browsers compatibility and functioning

Managing history and hash navigation can be very painful if you need a good compatibility with old browsers. Depending on the browsers you aim to support here are different solution you can consider:

In our case we will do the simple manual monitoring, which is pretty easy to implement. All we need to do is checking if the hash changed every n milliseconds, and fire some function if a change is detected.

Note: A jQuery plugin is also available to manage this for you.

The routes and the main routing loop

Let’s create a Router.js file next to main.js to manage the routing logic. In this file we need a way to declare our routes and the default one if none is specified in the URL. We can for instance use a simple array of route objects that contain a hash and the corresponding controller we want to load. We also need a defaultRoute if no hash is present in the URL:

define(function(){

	var routes = [{hash:'#list', controller:'ListController'},
	              {hash:'#add',  controller:'AddController'}];
	var defaultRoute = '#list';
	var currentHash = '';
	
	function startRouting(){
		window.location.hash = window.location.hash || defaultRoute;
		setInterval(hashCheck, 100);
	}
	
	return {
		startRouting:startRouting
	};
});

When startRouting will be called, it will set the default hash value in the URL, and will start a repetition of calls to hashCheck that we haven’t implemented yet. The currentHash variable will be used to store the current value of the hash if a change is detected.

Check for hash changes

And here is hashCheck, the function called every 100 milliseconds:

function hashCheck(){
	if (window.location.hash != currentHash){
		for (var i = 0, currentRoute; currentRoute = routes[i++];){
			if (window.location.hash == currentRoute.hash)
				loadController(currentRoute.controller);
		}
		currentHash = window.location.hash;
	}
}

hashCheck simply checks if the hash has changed compared to the currentHash, and if it matches one of the routes, calls loadController with the corresponding controller name.

Loading the right controller

Finally, loadController just performs a call to require to load the controller’s module and execute its start function:

function loadController(controllerName){
	require(['Controllers/' + controllerName], function(controller){
		controller.start();
	});
}

So the final Router.js file looks like this:

define(function(){
	
	var routes = [{hash:'#list', controller:'ListController'},
	              {hash:'#add',  controller:'AddController'}];
	var defaultRoute = '#list';
	var currentHash = '';
	
	function startRouting(){
		window.location.hash = window.location.hash || defaultRoute;
		setInterval(hashCheck, 100);
	}
	
	function hashCheck(){
		if (window.location.hash != currentHash){
			for (var i = 0, currentRoute; currentRoute = routes[i++];){
				if (window.location.hash == currentRoute.hash)
					loadController(currentRoute.controller);
			}
			currentHash = window.location.hash;
		}
	}
	
	function loadController(controllerName){
		require(['Controllers/' + controllerName], function(controller){
			controller.start();
		});
	}
	
	return {
		startRouting:startRouting
	};
});

Using the new routing system

Now all we have to do is to require this module from our main file and call startRouting:

require(['Models/User', 'Router'], function(User, Router){
	
	var users = [new User('Barney'),
	             new User('Cartman'),
	             new User('Sheldon')];
	
	localStorage.users = JSON.stringify(users);

	Router.startRouting();	
});

If we want to navigate in our app from a controller to another, we can just replace the current window.hash with the new controller’s hash route. In our case we still manually load the ListController in AddController instead of using our new routing system:

require(['Controllers/ListController'], function(ListController){
	ListController.start();
});

Let’s replace those 3 lines with a simple hash update:

window.location.hash = '#list';

And this is it! Our app has now a functional routing system! You can navigate from a view to another, come back, do whatever you want with the hash in the URL, it will keep loading the right controller if it matches the routes declared in the router. Sweet!

Here is an online demo of the app.

Conclusion

You can be proud of yourself, you built an entire MVC app without any framework! We just used RequireJS to link our files together, which is really the only mandatory tool to build something modular. So now what are the next steps? Well if you liked the minimalist Do It Yourself approach I had in this tutorial, you can enrich our little framework with new features as your app grows and has new technical needs. Here are some ideas of potential interesting next steps:

  • Integrate a templating system,
  • Create a small external library to put all what is not purely related to your app (like the routing system) to be able to reuse it on other projects,
  • Define what a Model, a View and a Controller are with objects in this library,
  • Create a new abstraction layer to deal with getting the data from various sources (RESTful APIs, localStorage, IndexedDB…).

This DIY approach is excellent to learn, but the current state of our framework is really not sufficient for real life projects at this point. If you’re too lazy to implement the features listed above (and this list is absolutely not exhaustive!), you can also start learning how to use an existing MVC framework. The most popular are:

I personally like Backbone because it’s lightweight (less than 5kb minified), so my next tutorial will be about mixing RequireJS and Backbone, which works pretty well! Follow @verekia for updates about this. You should also follow Addy Osmani and read his tutorials about large scale JavaScript applications and modular JavaScript, and James Burke the creator of RequireJS. Both are very good sources of information for building modular JavaScript apps. Addy Osmani also started a project called TodoMVC to compare how different MVC frameworks can be used to build the same simple web app, which is really helpful to choose the right framework for your needs.

That’s all for today folks, thanks for reading!

25 thoughts on “Build a simple client-side MVC app with RequireJS

  1. Great article, I’m using RequireJS & Backbone every day and this is definitely a good way to go for large scale web app when you don’t want/need big frameworks like Sencha/Sproutcore…. About templating, for those wanting simple (micro) templates as AMD modules, I’ve made a RequireJS plugin based on UnderscoreJS’s templates https://github.com/ZeeAgency/requirejs-tpl

    • Hi Julien,
      I just tried your plugin and it works exactly as expected, so I’m probably going to write a blog post about it soon. Thank you very much for your contribution to the community!

  2. Thanks very much for the tutorial.

    You say at the start that:

    “Public methods are declared using the object literal notation, which is not very convenient.”

    Can you explain why it’s not convenient? Is it to do with JS performance?

    • Hi Steve,

      It’s just about syntax and personal preferences, not performance. If you don’t bother declaring your function with the object literal notation, you can use the simple Module Pattern. The Revealing Module Pattern is just here to add some sugar!

  3. I am actually already using this technique for building out my new UI. I like this a lot more than the current MVC’s out there because it allows you to structure the design as you want.

    Currently, client side MVC’s allow Views to talk directly to Models and I find that as a flawed pattern. This allows me to make my own as well as preserve the state with hashes.

  4. Great post. I have the same structure. Controllers are the ones who can talk to the View and Model. Views can’t talk to Models.

    When it comes to events, you mentioned to define it in the view and it will call the business logic in the controller. Does it mean that when defining the View you need to pass a reference to the Controller?

    • Hi Dante,
      Yes, that’s how I would do it. You can for instance pass this as the second parameter of render in the controller (it’s probably the simplest way to do it), or implement a more generic solution to bind a controller to a view.

  5. really great article! what’s the alternatives of using ‘setInterval(…)’ for routing? Is every n millisec. function-call a burden of performance?

  6. Great article but I have one question about integration of data access layer using REST services. In fact, I need to call different services in order to populate my view and need to render different parts of my view …

    This is my implementation of start method but I do not know if it is good way or not:

    define(["views/HomePageView", "models/ResourceSearchCriteria"], function(HomePageView, ResourceSearchCriteria) {
    ....
    function start() {
            HomePageView.render();
            bindEvents();
    
            var lastRecentlyAdded = new ResourceSearchCriteria();
            lastRecentlyAdded.size = 5;
            lastRecentlyAdded.view = "medium";
    
            resourceClient.findResourcesByCriteria(lastRecentlyAdded, {
                        success: function(data) {
                            HomePageView.displayResources($("#recentlyAddedBox .resourcelibrary-gallery-items"), data);
                        }
                    }
            );
    
            resourceClient.findTags(30, {
                        success: function(data) {
                            HomePageView.displayPopularTags(data);
                        }
                    }
            );
        }
    ...
    
    • Hi Subigre,

      It looks good to me!
      However you still have an ID / class selector in this controller, which should ideally be located in the view, just like you did for displayPopularTags.

      Good job! :)

      • Thanks for your response.

        I have an id selector because the method displayResources is used to display the last recentrly added and the last favorites. So it is just to reuse the same code.
        But, I am forced to use id/class selector in bindEnvents() method ?

        I have also another question … is it better to pass an argument in start() method or save it in localeStroage and retrieve the value in the start() method ?
        (for instance if I have a SearchController which needs a SearchCriteria to perform a REST request and display the results).

        Regards

  7. Your article is very inspiring and pretty much explains most essential components for front-end MVC . It’s very good introduction for people who want to step in some MVC frameworks like backbone. Hope you can keep writing and I will definitely go back

  8. This post is very nice. :)
    I’m programmer in Korea. This is really useful to front-end developers.
    If you don’t mind, can I translate your post in my blog?
    I will link to your post and write origin author is you.
    thanks~

  9. Hi, Can you share the source code. Not sure why modules/methods in main.js are not called. It seems like i have pointed the script correctly but no luck

    • Make sure you’re using the http:// protocol and not just file:// (basically working on some localhost server), that’s usually the issue people run through :)

  10. Hi Jonathan,

    My compliments to you for this concise and focused article. It has really helped me gain an understanding for MVC in javascript using requirejs. Your deliberate pruning down of off topic subjects like templating, or eventing using 3rd party libraries has allowed the MVC pattern to shine through.

    Regards, Robert.

  11. A nice tutorial that for once doesn’t confuse things by relying on a library or framework.

    One point that I don’t think is right is accessing the underlying storage mechanism directly from the controllers rather than through the model. The point of the model is to abstract/hide all the implementation details from the rest of the system. Adding ‘get’, ‘set’ and ‘add’ methods to the model is easy and makes the controller code even cleaner: the addcontroller just has:

    var userName = document.getElementById('user-name').value;
    console.log(userName);
    User.add(userName);

    It would be nice to see an update to this tutorial to use custom messaging between the view and controller and between the model and controller, as a sort of halfway house to using something like backbone which does this already.

    Thanks a lot

    Bob

  12. Really enjoyed this tutorial, and learnt a lot from you bringing bits and pieces together in a way that I can follow. It was also cool to see minimal use of libraries – just the necessities. Thank you.

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>