Mock JSON API Backend with Mirage

A mock backend for development and testing is often very useful. Using the classic blog example, we are going to create a posts/comments backend with the fantastic Ember CLI Mirage.

Latest versions fully support JSON API. Let’s start by installing it:

$ ember install ember-cli-mirage

This will create a mirage folder at the project root.

Our Mirage backend will support loading posts and loading/posting comments.

This article will focus purely on the backend, for integration testing support read Mocking Integration Test Data with Ember CLI Mirage

Step 1: An API for posts

Our starting point is Mirage’s configuration file:

// mirage/config.js

export default function() {
  this.namespace = 'api';
  this.timing = 400;  // simulate network delay

  this.get('/posts');
  this.get('/posts/:id');
}

We used shorthands to define posts, which behave as we expect. Posts are stored in an embedded database until the next page reload or test setup.

Next thing we define is our Mirage Post model:

$ ember generate mirage-model post

which rather unsurprisingly creates a pre-filled mirage/models/post.js file:

// mirage/models/post.js

import { Model } from 'ember-cli-mirage';

export default Model.extend({
});

We only use models to define relationships. Attributes are specified in factories as we’ll see below.

A serializer tells Mirage we want data in the JSON API form:

$ ember g mirage-serializer application

It generated a JSONAPISerializer that behaves just like any other server-side JSON API implementation:

// mirage/serializers/application.js

import { JSONAPISerializer } from 'ember-cli-mirage';

export default JSONAPISerializer.extend({
});

Now that we set everything up, this “structure” is ready to see some data flow through it.

Seeding data with factories and scenarios

Factories enable us to define blueprints for our models.

$ ember g mirage-factory post

Which for Post results in:

import { Factory } from 'ember-cli-mirage';

export default Factory.extend({
  title(i) {
    return `Post ${i+1}`
  },
  body: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
});

We use scenarios to seed the actual data:

// mirage/scenarios/default.js

export default function(server) {

  // generate 10 posts
  server.createList('post', 10);

}

One would typically use scenarios in development mode, as acceptance or integration tests would set up mock data via the server variable.

That is it! A fully functional backend for posts.

Ember best practices delivered straight to your inbox? Tell me where:

(One e-mail every month. No BS. Unsubscribe anytime!)

Step 2: Adding comments

The Comment model follows.

$ ember generate mirage-model comment

We make sure we include the association to Post

// mirage/models/comment.js

import { Model, belongsTo } from 'ember-cli-mirage';

export default Model.extend({
  post: belongsTo('post')
});

Let’s update our post to reflect the relationship with comments:

// mirage/models/post.js

import { Model, hasMany } from 'ember-cli-mirage';

export default Model.extend({
  comments: hasMany('comment')
});

So far so good!

We want to be able to retrieve comments for each post. That will be accessible at /posts/:id/comments.

Back to our config, let’s append:

// mirage/config.js

export default function() {
  this.namespace = 'api';

  this.get('/posts');
  this.get('/posts/:id');

  this.get('/posts/:id/comments', function(schema, request) {
    const postId = request.params.id;
    return schema.comments.where({ postId: postId });
  });

  this.post('/comments');

}

Here we started messing with schema and collections as well as adding an endpoint for comment creation. Again, the code is lean and self-explanatory.

We gotta come up with comment data. Just like Post, we create a Factory for Comment:

// mirage/factories/comment.js

import { Factory } from 'ember-cli-mirage';

export default Factory.extend({
  body(i) {
    return `Lorem comment ${i}`
  }
});

An alternative to factories is adding data is through fixtures.

Creating a fixture file is super easy:

$ ember g mirage-fixture comments

It will generate a plain file that we’ll fill with dummy data:

// mirage/fixtures/comments.js

export default [
  { id: 1, postId: 1, body: "Amazing post number 1! Enjoyed it" },
  { id: 2, postId: 1, body: "Dude this post 1 is great, thanks!" }
];

Like factories, fixtures have to be explicitly loaded in our scenario. And yes, we can mix and match:

// mirage/scenarios/default.js

export default function(server) {

  // generate 10 posts
  server.createList('post', 10);

  server.loadFixtures('comments');
  server.create('comment', { postId: 5 });

}

Any arguments passed to the factory will override whatever it had generated for those particular attributes.

Step 3: Quickly testing the result

Add the following to any route and template:

// any route

const { RSVP, $ } = Ember;

export default Ember.Route.extend({
  model() {
    return RSVP.hash({
      posts: $.getJSON('/api/posts'),
      post2: $.getJSON('/api/posts/2'),
      commentsFor1: $.getJSON('/api/posts/1/comments'),
      commentsFor5: $.getJSON('/api/posts/5/comments')
    });
  }
});
{{! any route's template }}

<ul>
  <li>Posts length: {{model.posts.data.length}}</li>
  <li>Post 2 name: {{model.post2.data.attributes.title}}</li>
  <li>Comments for one: {{model.commentsFor1.data.length}}</li>
  <li>Comments for five: {{model.commentsFor5.data.length}}</li>
</ul>

Which should output…

  • Posts length: 10
  • Post 2 name: Post 2
  • Comments for one: 2
  • Comments for five: 1

…to demonstrate our Mirage backend is flawless!

Enjoyed this article? Don't miss my next one!

Leave me your e-mail for content that will help you master Ember:

Do you want to master Ember fast?

Leave me your e-mail for helpful updates delivered straight to your inbox.

(A few e-mails per month. No BS. Unsubscribe anytime!)