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:

a posts listing page

a single post page

a list of comments and a textarea to add new comments

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

Step 1: Reading posts

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.

Seeding data with factories and scenarios

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 ) ; }

One would typically use scenarios in development mode, as acceptance or integration tests would set up mock data via the 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!

Octane news & best practices, straight to your inbox? Success! You're subscribed! An error has occurred. Please e-mail me@frank06.net with your request. Snacks is the best of Ember Octane in a highly digestible monthly newsletter. (No spam. EVER.)

List of posts page

// 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 >

Single post page

// 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 ; }

Octane news & best practices, straight to your inbox? Success! You're subscribed! An error has occurred. Please e-mail me@frank06.net with your request. 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