Build complete frontend features, even if your API doesn't exist.

Mirage JS is an API mocking library that lets you build, test and share a complete working JavaScript application without having to rely on any backend services.

Create a Server

Mirage runs alongside the rest of your frontend JavaScript code — no new server processes or terminal windows needed. Use the devtools you know and love to write UI code that's ready for the network.

The best DX for frontend development.

Say goodbye to configuring painful backend environments just to hack on your UI. Mirage runs alongside the rest of your frontend code, so there’s no new infrastructure for you to learn.

New collaborators can clone your frontend codebase, run npm install, and have a complete local offline dev environment up and running in seconds – no environment variables or auth tokens needed.

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"))

We’ve been using Mirage at Heroku since 2015 on critical customer-facing apps. It's enabled our team to grow without sacrificing speed in either our development or testing workflows.

For me, the real magic of Mirage is that it lets us write tests from the user's perspective. We take user stories from our product team and translate them 1:1 into tests, without ever having to break flow by stepping outside the front-end toolchain.

Mirage is, in short, an essential tool for every UI developer.

Jamie White

Software Engineer at Salesforce/Heroku

Write high-level UI tests that stress your networking code.

With Mirage, you can write automated tests against your API no matter what state it's in. Test how your app handles 0 blog posts, 10, or 1000 – or even how it behaves when your server is slow or responds with an error.

No messy mocking code or handcrafted API responses in your tests. Just real-world scenarios validating the entire functionality of your full application.

Browsers
it("shows a message if there are no todos", async () => {
  const { getByTestId } = render(<App />)
  await waitForElementToBeRemoved(() => getByTestId("loading"))

  expect(getByTestId("no-todos")).toBeInTheDocument()
})

it("shows existing todos", async () => {
  server.createList("todo", 3)

  const { getByTestId, getAllByTestId } = render(<App />)
  await waitForElementToBeRemoved(() => getByTestId("loading"))

  expect(getAllByTestId("todo")).toHaveLength(3)
})

it("can complete a todo", async () => {
  server.create("todo", { text: "Todo 1", isDone: false })
  server.create("todo", { text: "Todo 2", isDone: false })

  const { getByTestId, getAllByTestId } = render(<App />)
  await waitForElementToBeRemoved(() => getByTestId("loading"))
  const todos = getAllByTestId("todo")
  userEvent.click(todos[1].querySelector("input[type='checkbox']"))
  await waitForElementToBeRemoved(() => getByTestId("saving"))

  expect(todos[0].querySelector('input[type="checkbox"]').checked).toBe(false)
  expect(todos[1].querySelector('input[type="checkbox"]').checked).toBe(true)
  expect(server.db.todos[1].isDone).toBe(true)
})

it("can create a todo", async () => {
  const { getByTestId } = render(<App />)
  await waitForElementToBeRemoved(() => getByTestId("loading"))

  const newTodoForm = await waitForElement(() => getByTestId("new-todo-form"))
  userEvent.type(newTodoForm.querySelector("input"), "Walk the dog")
  fireEvent.submit(getByTestId("new-todo-form"))
  await waitForElementToBeRemoved(() => getByTestId("saving"))

  const todo = getByTestId("todo")
  expect(todo.querySelector('input[type="checkbox"]').checked).toBe(false)
  expect(todo.querySelector('input[type="text"]').value).toBe("Walk the dog")
  expect(server.db.todos.length).toBe(1)
  expect(server.db.todos[0].text).toBe("Walk the dog")
})

it("shows a message if there are no todos", async () => {
  const { getByTestId } = render(<App />)
  await waitForElementToBeRemoved(() => getByTestId("loading"))

  expect(getByTestId("no-todos")).toBeInTheDocument()
})

it("shows existing todos", async () => {
  server.createList("todo", 3)

  const { getByTestId, getAllByTestId } = render(<App />)
  await waitForElementToBeRemoved(() => getByTestId("loading"))

  expect(getAllByTestId("todo")).toHaveLength(3)
})

it("can complete a todo", async () => {
  server.create("todo", { text: "Todo 1", isDone: false })
  server.create("todo", { text: "Todo 2", isDone: false })

  const { getByTestId, getAllByTestId } = render(<App />)
  await waitForElementToBeRemoved(() => getByTestId("loading"))
  const todos = getAllByTestId("todo")
  userEvent.click(todos[1].querySelector("input[type='checkbox']"))
  await waitForElementToBeRemoved(() => getByTestId("saving"))

  expect(todos[0].querySelector('input[type="checkbox"]').checked).toBe(false)
  expect(todos[1].querySelector('input[type="checkbox"]').checked).toBe(true)
  expect(server.db.todos[1].isDone).toBe(true)
})

it("can create a todo", async () => {
  const { getByTestId } = render(<App />)
  await waitForElementToBeRemoved(() => getByTestId("loading"))

  const newTodoForm = await waitForElement(() => getByTestId("new-todo-form"))
  userEvent.type(newTodoForm.querySelector("input"), "Walk the dog")
  fireEvent.submit(getByTestId("new-todo-form"))
  await waitForElementToBeRemoved(() => getByTestId("saving"))

  const todo = getByTestId("todo")
  expect(todo.querySelector('input[type="checkbox"]').checked).toBe(false)
  expect(todo.querySelector('input[type="text"]').value).toBe("Walk the dog")
  expect(server.db.todos.length).toBe(1)
  expect(server.db.todos[0].text).toBe("Walk the dog")
})

Share a fully-working UI without running a backend.

Because Mirage realistically mocks out your entire API server, you can share a clickable, working prototype of your JavaScript application without needing to run any backend services.

Get high-quality feedback from your users and iterate faster than ever, before you start investing in expensive server-side infrastructure.

Honestly, I can't recommend this tool enough. Finally, an idiomatic way for frontend developers to prototype and test an entire feature without touching a real API! Productivity just goes through the roof.

Roman Sandler

Developer at 500tech

Get started using Mirage

Mirage works with all major JavaScript frameworks, libraries and test runners.