The MVC Design Pattern in Vanilla JavaScript

The controller

The view and model are two components used by the controller. The controller has in its constructor all the components it needs to do the job:

var PenguinController = function PenguinController(penguinView, penguinModel) {
  this.penguinView = penguinView;
  this.penguinModel = penguinModel;

The constructor uses inversion of control and injects modules in this way. This pattern enables you to inject any component that meets the high-level contract. Think of it as a nice way to abstract code from implementation details. This pattern empowers you to write clean code in plain JavaScript.

Then, user events get wired up and handled in this way:

PenguinController.prototype.initialize = function initialize() {
  this.penguinView.onClickGetPenguin = this.onClickGetPenguin.bind(this);

PenguinController.prototype.onClickGetPenguin = function onClickGetPenguin(e) {
  var target = e.currentTarget;
  var index = parseInt(target.dataset.penguinIndex, 10);

  this.penguinModel.getPenguin(index, this.showPenguin.bind(this));

Note this event uses the current target to grab the state stored in the DOM. The DOM, in this case, tells you everything you need to know about its current state. The current state of the DOM is what users see on the browser. You can store state data in the DOM itself, as long as the controller does not change state.

When an event is fired, the controller grabs the data and says what happens next. The this.showPenguin() callback is of interest:

PenguinController.prototype.showPenguin = function showPenguin(penguinModelData) {
  var penguinViewModel = {
    imageUrl: penguinModelData.imageUrl,
    size: penguinModelData.size,
    favoriteFood: penguinModelData.favoriteFood

  penguinViewModel.previousIndex = penguinModelData.index - 1;
  penguinViewModel.nextIndex = penguinModelData.index + 1;

  if (penguinModelData.index === 0) {
    penguinViewModel.previousIndex = penguinModelData.count - 1;

  if (penguinModelData.index === penguinModelData.count - 1) {
    penguinViewModel.nextIndex = 0;


The controller calculates indexes for each penguin and tells the view to render this. It grabs data from the model and transforms it into an object the view understands and cares about.

Here is a unit test of the happy path when showing a penguin:

var PenguinViewMock = function PenguinViewMock() {
  this.calledRenderWith = null;

PenguinViewMock.prototype.render = function render(penguinViewModel) {
  this.calledRenderWith = penguinViewModel;

// Arrange
var penguinViewMock = new PenguinViewMock();

var controller = new PenguinController(penguinViewMock, null);

var penguinModelData = {
  name: 'Chinstrap',
  imageUrl: 'http://chinstrapl.jpg',
  size: '5.0kg (m), 4.8kg (f)',
  favoriteFood: 'krill',
  index: 2,
  count: 5

// Act

// Assert
assert.strictEqual(, 'Chinstrap');
assert.strictEqual(penguinViewMock.calledRenderWith.imageUrl, 'http://chinstrapl.jpg');
assert.strictEqual(penguinViewMock.calledRenderWith.size, '5.0kg (m), 4.8kg (f)');
assert.strictEqual(penguinViewMock.calledRenderWith.favoriteFood, 'krill');
assert.strictEqual(penguinViewMock.calledRenderWith.previousIndex, 1);
assert.strictEqual(penguinViewMock.calledRenderWith.nextIndex, 3);

The PenguinViewMock has the same contract that the real implementation has. This makes it possible to write unit tests and make assertions. The assert comes from Node assertions, and is also available in Chai assertions. This enables you to write tests that can run both on Node and on the browser.

Note the controller does not care about implementation details. It uses the contracts the view provides like this.render(). This is the discipline necessary for clean code. The controller can trust each component to do what it says it will do. This adds transparency which makes the code readable.

The view

The view only cares about the DOM element and wiring up events, for example:

var PenguinView = function PenguinView(element) {
  this.element = element;

  this.onClickGetPenguin = null;

When it changes the state of what the user sees, the implementation looks like this:

PenguinView.prototype.render = function render(viewModel) {
  this.element.innerHTML = '<h3>' + + '</h3>' +
    '<img class="penguin-image" src="' + viewModel.imageUrl +
      '" alt="' + + '" />' +
    '<p><b>Size:</b> ' + viewModel.size + '</p>' +
    '<p><b>Favorite food:</b> ' + viewModel.favoriteFood + '</p>' +
    '<a id="previousPenguin" class="previous button" href="javascript:void(0);"' +
      ' data-penguin-index="' + viewModel.previousIndex + '">Previous</a> ' +
    '<a id="nextPenguin" class="next button" href="javascript:void(0);"' +
      ' data-penguin-index="' + viewModel.nextIndex + '">Next</a>';

  this.previousIndex = viewModel.previousIndex;
  this.nextIndex = viewModel.nextIndex;

  // Wire up click events, and let the controller handle events
  var previousPenguin = this.element.querySelector('#previousPenguin');
  previousPenguin.addEventListener('click', this.onClickGetPenguin);

  var nextPenguin = this.element.querySelector('#nextPenguin');
  nextPenguin.addEventListener('click', this.onClickGetPenguin);

Note its main concern is to turn view model data into HTML and change the state. The second is to wire up click events and let the controller serve as the entry point. The event handlers get attached to the DOM after the state changes. This technique handles event management in one clean sweep.

To test this, we can verify the element gets updated and changes state:

var ElementMock = function ElementMock() {
  this.innerHTML = null;

// Stub functions, so we can pass the test
ElementMock.prototype.querySelector = function querySelector() { };
ElementMock.prototype.addEventListener = function addEventListener() { };
ElementMock.prototype.focus = function focus() { };

// Arrange
var elementMock = new ElementMock();

var view = new PenguinView(elementMock);

var viewModel = {
  name: 'Chinstrap',
  imageUrl: 'http://chinstrap1.jpg',
  size: '5.0kg (m), 4.8kg (f)',
  favoriteFood: 'krill',
  previousIndex: 1,
  nextIndex: 2

// Act

// Assert
assert(elementMock.innerHTML.indexOf( > 0);
assert(elementMock.innerHTML.indexOf(viewModel.imageUrl) > 0);
assert(elementMock.innerHTML.indexOf(viewModel.size) > 0);
assert(elementMock.innerHTML.indexOf(viewModel.favoriteFood) > 0);
assert(elementMock.innerHTML.indexOf(viewModel.previousIndex) > 0);
assert(elementMock.innerHTML.indexOf(viewModel.nextIndex) > 0);

This about solves all the big concerns, changing state and wiring events. But, where is the data coming from?

The Model

In MVC, all the model cares about is Ajax. For example:

var PenguinModel = function PenguinModel(XMLHttpRequest) {
  this.XMLHttpRequest = XMLHttpRequest;

Note the module XMLHttpRequest gets injected into the constructor. This is a way to let fellow programmers know what components are necessary for this model. If the model needs more than simple Ajax, you can signal this with more modules. Plus, with unit tests, I can inject mocks that have the exact same contract as the original module.

Time to get a penguin based on an index:

PenguinModel.prototype.getPenguin = function getPenguin(index, fn) {
  var oReq = new this.XMLHttpRequest();

  oReq.onload = function onLoad(e) {
    var ajaxResponse = JSON.parse(e.currentTarget.responseText);
    // The index must be an integer type, else this fails
    var penguin = ajaxResponse[index];

    penguin.index = index;
    penguin.count = ajaxResponse.length;

  };'GET', '', true);

This points to an endpoint and gets the data from a server. We can test this by mocking the data with a unit test:

var LIST_OF_PENGUINS = '[{"name":"Emperor","imageUrl":"http://imageUrl",' +
  '"size":"36.7kg (m), 28.4kg (f)","favoriteFood":"fish and squid"}]';

var XMLHttpRequestMock = function XMLHttpRequestMock() {
  // The system under test must set this, else the test fails
  this.onload = null;
}; = function open(method, url, async) {
  // Internal checks, system under test must have a method and url endpoint
  // If Ajax is not async, you’re doing it wrong 🙂
  assert.strictEqual(async, true);

XMLHttpRequestMock.prototype.send = function send() {
  // Callback on this object simulates an Ajax request
  this.onload({ currentTarget: { responseText: LIST_OF_PENGUINS } });

// Arrange
var penguinModel = new PenguinModel(XMLHttpRequestMock);

// Act
penguinModel.getPenguin(0, function onPenguinData(penguinData) {

  // Assert
  assert.strictEqual(, 'Emperor');
  assert.strictEqual(penguinData.size, '36.7kg (m), 28.4kg (f)');
  assert.strictEqual(penguinData.favoriteFood, 'fish and squid');
  assert.strictEqual(penguinData.index, 0);
  assert.strictEqual(penguinData.count, 1);

As you can see, the model only cares about raw data. This means working with Ajax and JavaScript objects. If you are unclear about Ajax in vanilla JavaScript, there is an article with more info.

Unit tests

With any discipline, it is important to do the work necessary to get reassurance. The MVC design pattern does not dictate howyou solve the problem. A design pattern gives you a broad set of boundaries that empower you to write clean code. This grants you freedom from dependency oppression.

To me, this means having a full suite of unit tests for each use case. The tests provide guidance on how the code is useful. This makes it open and inviting for any programmer looking to make specific changes.

Feel free to poke around the entire set of unit tests. I think it’ll help you understand this design pattern. Each test is for a specific use case; think of it as granular concerns. Unit tests help you think about each coding problem in isolation and solve for this one concern. This separation of functional concerns in MVC comes to life with each unit test.

Looking Ahead

The demo of penguins has only the bare viable concept to show how useful MVC can be. But, there are many improvements one can iterate on:

  • Add a screen with a list of all penguins
  • Add keyboard events so you can flip through penguins, add swipe too
  • An SVG chart to visualize data, pick any data point such as penguin size

Of course, it is up to you, my gentle reader, to take this demo further. These are but a few ideas so you can show how powerful this design pattern is.


I hope you see where the MVC design pattern and a little discipline can take you. A good design pattern stays out of the way while promoting clean code. It keeps you on task while solving the problem at hand only. It makes you a better more effective programmer.

In programming, the idea is to stay close to the problem at hand while eliminating cruft. The art of programming is fleshing out one single problem at a time. In MVC, this means one single functional concern at a time.

As a developer, it is easy to believe you are logical and don’t deal with emotion. The truth is as you get smacked around with too many problems at one time you get frustrated. This is a normal human response we all have to deal with. The fact is frustration affects code quality in a negative way. When this feeling grabs hold of you and dominates your work it is no longer about logic. As the solution takes on more risk and complex dependencies this can be demoralizing.

What I like is focusing on a single concern. Solving for one problem at a time and getting positive feedback. This way you stay in the zone, productive, and free from nonsense.