Mirage.js
demo.env
Przemek Suchodolski @przemuh
Agenda
- What is mirage.js ? (walkthrough)
- Demo Mode (use case)
About me
- @przemuh / przemuh.dev
- Frontend dev since 2012
-
π°π· corp, π΅π± software-house, πΊπΈ startup
-
Musician amateur πΈ π₯ πΉ (soundcloud.com/przemuh)
Mirage.js
Build complete frontend features,
even if your API doesn't exist.
Sam Selikoff
Ryan Toronto
- 2015 - Pretender.js
- 2015 - ember-cli-pretenderify
- ember-cli-mirage
- 2019 mirage.js (standalone)
Β
How to ...π€
- develop frontend & backend in parallel
- mock backend services in tests
- share working app prototype without backend
- fakeAPI
- MSW
- JSON Server
- Interceptors (Pretender.js, cy.intercept())
Data Management
- Network request interceptor (Pretender.js)
- Database (in-memory)
Mirage.js works as...
- Relationships
- ORM
- Factories
import React from "react"
import ReactDOM from "react-dom"
import App from "./App"
import { createServer } from "miragejs"
createServer({
routes() {
this.namespace = "api"
this.get("/todos", ({ db }) => {
return db.todos
})
},
})
ReactDOM.render(<App />, document.getElementById("root"))
import { createServer, Model } from "miragejs"
createServer({
models: {
blogPost: Model,
},
})
import { createServer, Model, belongsTo } from "miragejs"
createServer({
models: {
blogPost: Model.extend({
author: belongsTo(),
}),
author: Model,
},
})
import { createServer, Model, belongsTo, hasMany } from "miragejs"
createServer({
models: {
blogPost: Model.extend({
author: belongsTo(),
comments: hasMany(),
}),
author: Model,
comment: Model
},
})
import { createServer, Model, belongsTo, hasMany } from "miragejs"
createServer({
models: {
blogPost: Model.extend({
author: belongsTo(),
comments: hasMany(),
}),
author: Model,
comment: Model
},
routes() {
this.get("/posts", (schema) => schema.blogPosts.all());
}
})
import { createServer, Model, belongsTo, hasMany } from "miragejs"
createServer({
models: {
blogPost: Model.extend({
author: belongsTo(),
comments: hasMany(),
}),
author: Model,
comment: Model
},
routes() {
// [postModel, postModel]
// { attrs, fks, author, comments }
this.get("/posts", (schema) => schema.blogPosts.all());
}
})
import { createServer, Model, belongsTo, hasMany } from "miragejs"
createServer({
models: {
blogPost: Model.extend({
author: belongsTo(),
comments: hasMany(),
}),
author: Model,
comment: Model
},
routes() {
this.get("/posts", (schema) => schema.blogPosts.all());
this.get("/posts/:id", (schema, request) => schema.blogPosts.find(request.params.id));
}
})
import { createServer, Model, belongsTo, hasMany } from "miragejs"
createServer({
models: {
blogPost: Model.extend({
author: belongsTo(),
comments: hasMany(),
}),
author: Model,
comment: Model
},
routes() {
this.get("/posts", (schema) => schema.blogPosts.all());
this.get("/posts/:id", (schema, request) => schema.blogPosts.find(request.params.id));
this.post("/posts", (schema, request) =>
schema.blogPosts.create(JSON.parse(request.requestBody))
);
}
})
import { createServer, Model, belongsTo, hasMany } from "miragejs"
createServer({
models: {
blogPost: Model.extend({
author: belongsTo(),
comments: hasMany(),
}),
author: Model,
comment: Model
},
routes() {
this.get("/posts", (schema) => schema.blogPosts.all());
this.get("/posts/:id", (schema, request) => schema.blogPosts.find(request.params.id));
this.post("/posts", (schema, request) =>
schema.blogPosts.create(JSON.parse(request.requestBody))
);
this.delete("/posts/:id", (schema, request) =>
schema.blogPosts.find(request.params.is).destroy()
);
// ... PUT, PATCH
}
})
import { createServer, Model, belongsTo, hasMany } from "miragejs"
createServer({
models: {
blogPost: Model.extend({
author: belongsTo(),
comments: hasMany(),
}),
author: Model,
comment: Model
},
routes() {
this.resource("blogPosts", { path: "/posts" });
}
})
Other key features of mirage.js
- Factories / Seeds
- Serializers
Demo Mode
Data "heavy" app
Bussines perspective
- It's hard to show the product
- Self-service
Why mirage.js?
- Easy setup
- Persistent data (db, relationships)
- It can be used in tests
- It can be used in development
Let's do it
REST API
but not that restful...
Custom serializers
this.get("/posts", (schema) => schema.posts.all()) // returns collection
this.get("/posts", (schema) => schema.db.posts.all()) // returns RAW data
Attributes builder
export default class ProtectModel extends Model {
buildAttrs() {
return new AttrsBuilder(this.attrs, this.fks);
}
}
class AttrsBuilder {
constructor(attrs, fks) {
this.value = clone(attrs);
this.fks = fks;
}
omit(keys) {}
omitFks() {}
pick(keys) {}
mapId(name) {}
addProperties(obj) {}
mapPropertyName(targetKey, newKey) { }
build() {
return this.value;
}
}
this.get("/folders/:sourceId/:parentId", (schema, { params: { parentId, sourceId } }) => {
const folders = schema.folders.where({
parentId: parentId,
sourceId,
});
return folders.models.map((folder) =>
folder.buildAttrs().pick(["id", "parentId", "path"]).build(),
);
});
this.get("/folders/:sourceId/:parentId", ({ db }, { params: { parentId, sourceId } }) => {
const folders = db.folders.where({
parentId: parentId,
sourceId,
});
return folders.map(({ id, parentId, path }) => ({ id, parentId, path }));
});
Performance
Β
createServer({
models: {
folder: Model.extends({
hasMany: files
}),
file: Model.extends({
hasMany: versions
}),
version: Model.extends({
hasMany: matches
}),
match: Model
}
})
class FolderModel extends OurModel {
isSensitve() {
// check if any version of files under this folder
// has some sensitive matches
// π£
}
}
// route
this.get("/sensitive-folders", (schema) =>
schema.folders.all().models.filter(folder => folder.isSensitive())
)
Web Worker API
main thread
http request
worker
intercept only
data
mock
Persistence layer
Β
π€
Where & When ?
- LocalStorage?Β - 5 MB β
- IndexedDB - β
- onbeforeunload
- sync event π + async IndexedDb webWorker
Other lessons learned
- relationship !== inheritance (polymorphic)
- performance -> db vs schema (x10)
- default ids are numbers
Pros of using mirage.js
- single way for mocking backend
(dev.env, demo.env, tests) - app prototyping
- sharable app env config
(bugs reproduction)
Cons
- console.log vs network tab
- lack of model validation
- no db events (onChange etc.)
Summary
AKA - with mirage.js...
- ... you can develop faster
- ... you can prototype app without backend
- ...you can write "better" tests
Thank you
Mirage.js as a demo.env
By Przemek Suchodolski
Mirage.js as a demo.env
- 213