Last reviewed in July 2019 with Ember Octane Mirage 1.0
Mirage is a fantastic mocking library. It allows us to build frontend apps really fast, without the need of a real backend API.
Today we are going to create a blog with commenting capabilities:
Let's first set up an Ember app:
$ ember new ember-mirage-blog -b @ember/octane-app-blueprint --no-welcome
Next, install Mirage:
$ ember install ember-fetch # required as we don't have jquery
$ ember install ember-cli-mirage
installing ember-cli-mirage
create /mirage/config.js
create /mirage/scenarios/default.js
create /mirage/serializers/application.js
Installed addon package.
This will create a mirage
folder at the project root.
Lastly we'll install Faker which we are going to use to generate dummy data:
$ npm install --save-dev faker
We need an Ember model to work with, plus the list and single post pages:
$ ember g model post
$ ember g route posts
$ ember g route post --path 'posts/:post_id'
In Mirage, the starting point is the configuration file:
// mirage/config.js
export default function() {
this.get('/posts');
this.get('/posts/:id');
}
These are shorthands to declare endpoints for a list of posts (/posts
) and a single post (/posts/:id
).
By default, Mirage ships with a JSON API serializer so we can seamlessly start using it with Ember Data.
Let's define the attributes on the Ember Post
model. We want it to have a title, a body and published date.
// app/models/post.js
import Model, { attr } from '@ember-data/model';
export default class PostModel extends Model {
@attr title;
@attr body;
@attr('date') publishedAt;
}
Mirage automatically uses Ember Data models for the backend! Isn't that awesome?
Factories enable us to define “blueprints” or “templates” for our posts:
$ ember g mirage-factory post
Adding some attribute definitions (Faker will auto-generate gibberish for us):
// mirage/factories/post.js
import { Factory } from 'ember-cli-mirage';
import faker from 'faker';
export default Factory.extend({
title() {
return faker.lorem.sentence();
},
body() {
return faker.lorem.paragraph();
},
publishedAt() {
return faker.date.past();
}
});
And finally using the default scenario to seed the actual data:
// mirage/scenarios/default.js
export default function(server) {
// generate 10 posts
server.createList('post', 10);
}
server
variable.That's it! A fully functional backend for posts.
All that's left to do is add two simple routes to bring this blog to life!
Snacks is the best of Ember Octane in a highly digestible monthly newsletter. (No spam. EVER.)
// app/routes/posts.js
import Route from '@ember/routing/route';
export default class PostsRoute extends Route {
model() {
return this.store.findAll('post');
}
}
<!-- app/templates/posts.hbs -->
<h1>Posts</h1>
<ul>
{{#each @model as |post|}}
<li>
<LinkTo @route="post" @model={{post.id}}>{{post.title}}</LinkTo>
</li>
{{/each}}
</ul>
// app/routes/post.js
import Route from '@ember/routing/route';
export default class PostRoute extends Route {
model(params) {
return this.store.findRecord('post', params.post_id);
}
}
<!-- app/templates/post.hbs -->
<h1>{{@model.title}}</h1>
<p>
<strong>Published: {{@model.publishedAt}}</strong>
</p>
<code>{{@model.body}}</code>
Running ember s
and visiting /posts
should hopefully show us the ten posts we told Mirage to generate for us:
Clicking on a title takes us to the post page:
Success 😄!
Let's generate the Comment
model and add a relationship with Post
:
$ ember g model comment
// app/models/comment.js
import Model, { attr, belongsTo } from '@ember-data/model';
export default class CommentModel extends Model {
@attr text;
@attr('date') publishedAt;
@belongsTo post;
}
Update the Post
model, too:
// app/models/post.js
import Model, { attr, hasMany } from '@ember-data/model';
export default class PostModel extends Model {
@attr title;
@attr body;
@attr('date') publishedAt;
@hasMany comments;
}
Snacks is the best of Ember Octane in a highly digestible monthly newsletter. (No spam. EVER.)
Back to Mirage! Similarly to posts, let's create a comments factory.
$ ember g mirage-factory comment
// mirage/factories/comment.js
import { Factory } from 'ember-cli-mirage';
import faker from 'faker';
export default Factory.extend({
text() {
return faker.lorem.sentence();
},
publishedAt() {
return faker.date.past();
}
});
But how are we going to query for comments? The answer is: JSON API!
Let's tell our post factory to create a bunch of comments with every single post:
// mirage/factories/post.js
import { Factory } from 'ember-cli-mirage';
import faker from 'faker';
export default Factory.extend({
title() {
return faker.lorem.sentence();
},
body() {
return faker.lorem.paragraph();
},
publishedAt() {
return faker.date.past();
},
afterCreate(post, server) {
server.createList('comment', 3, { post });
}
});
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 JS file that we'll fill with dummy data:
// mirage/fixtures/comments.js
export default [
{ id: 1, text: "Amazing post number 1! Enjoyed it" },
{ id: 2, text: "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.
Ready to see comments in the blog post? Let's tell Ember Data to include comments any time we request a post:
// app/routes/post.js
import Route from '@ember/routing/route';
export default class PostRoute extends Route {
model(params) {
return this.store.findRecord('post', params.post_id, { include: 'comments' });
}
}
And display them:
<!-- app/templates/post.hbs -->
<h1>{{@model.title}}</h1>
<p>
<strong>Published: {{@model.publishedAt}}</strong>
</p>
<code>{{@model.body}}</code>
<h4>Comments</h4>
<ul>
{{#each @model.comments as |comment|}}
<li>
{{comment.text}}<br>
<small> commented on {{comment.publishedAt}}</small>
</li>
{{/each}}
</ul>
Results in:
Lastly, we are going to allow visitors to post comments. Simply add a shorthand to POST comments!
// mirage/config.js
export default function() {
this.get('/posts');
this.get('/posts/:id');
this.post('/comments');
}
In our UI we'll add a textarea:
<!-- app/templates/post.hbs -->
<h1>{{@model.title}}</h1>
<p>
<strong>Published: {{@model.publishedAt}}</strong>
</p>
<code>{{@model.body}}</code>
<h4>Comments</h4>
<Textarea @value={{this.commentDraft}} style="width: 400px; height: 50px" /><br>
<button {{on "click" (action this.addComment)}}>Add comment</button>
<ul>
{{#each @model.comments as |comment|}}
<li>
{{comment.text}}<br>
<small> commented on {{comment.publishedAt}}</small>
</li>
{{/each}}
</ul>
We just introduced this.commentDraft
and this.addComment
and since we are working in a template – we need to define these in its Javascript counterpart: the controller.
$ ember g controller post
So this.commentDraft
will be a tracked property, a comment draft temporary value (it backs the value in the textarea).
And the submit action, which creates an Ember Data record and saves it. Pretty self-explanatory!
// app/controllers/post.js
import Controller from '@ember/controller';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
export default class PostController extends Controller {
@tracked commentDraft;
@action
addComment() {
const comment = this.store.createRecord('comment', {
text: this.commentDraft,
publishedAt: new Date(),
post: this.model
});
comment.save();
this.commentDraft = "";
}
}
Trying it out in the browser…
It works!
In Getting Started with Ember Octane we use a simplified version of a blog in Mirage, too!
(Exercise for the reader: Can you add comments to that app?)
How did you enjoy Mirage? Let me know in the comments! A big thanks to its creator @samselikoff.
Code available at: https://github.com/frank06/ember-mirage-blog
Snacks is the best of Ember Octane in a highly digestible monthly newsletter. (No spam. EVER.)