The Database

At the core of Mirage's data layer is a simple in-memory database. This database stores all of Mirage's initial state, and then your route handlers access and modify that state as you use your application.

The database is what allows Mirage to mimic a production server, giving you the ability to write complete dynamic features in your app.

Most of your Mirage code will not access the database directly, but will interact with it through Mirage's ORM instead. We'll cover the ORM in the next section of these guides.

For now, let's learn the basics of the database so you feel confident interacting with it directly, even if most of your code ends up using the ORM.

Basic usage

Mirage's database is really just a JavaScript object with some conventions around it. It's accessible off your server instance

let server = createServer()

server.db // {} the db is empty

You can call loadData on it to seed it with some data:

server.db.loadData({
  movies: [
    { title: "Interstellar" },
    { title: "Inception" },
    { title: "Dunkirk" },
  ],
})

loadData takes a object whose keys correspond to database tables, and whose values represent an array of database records.

The database automatically assigns IDs to new records (you can also provide your own). We can access our data using a MongoDB-inspired API:

// Get all movies
server.db.movies // [ { id: '1', title: 'Interstellar' }, { id: '2', title: 'Inception' }, { id: '3', title: 'Dunkirk' } ]

// Get the first movie
server.db.movies[0] // { id: '1', title: 'Interstellar' }

// Insert a new movie
server.db.movies.insert({ title: "The Dark Knight" })

Mirage has a seeds method which is a conventional place to put some initial data into your server during development. You can call loadData on the database there:

createServer({
  seeds(server) {
    server.db.loadData({
      movies: [
        { title: "Interstellar" },
        { title: "Inception" },
        { title: "Dunkirk" },
      ],
    })
  },
})

The real power comes from accessing the database in your route handlers. You can get it using the schema argument:

this.get("/movies", (schema, request) => {
  return schema.db.movies
})

This route handler now responds with all the data in Mirage's database at the time of the request. That means if you start out with this data;

[
  { "id": "1", "title": "Interstellar" },
  { "id": "2", "title": "Inception" },
  { "id": "3", "title": "Dunkirk" }
]

but then write a new route handler that inserts data into the movies collection

this.post("/movies", (schema, request) => {
  let attrs = JSON.parse(request.requestBody)

  return schema.db.movies.insert(attrs)
})

and use your app to create a new movie, the second time your app makes a GET request to /movies it would respond with the new database data:

[
  { "id": "1", "title": "Interstellar" },
  { "id": "2", "title": "Inception" },
  { "id": "3", "title": "Dunkirk" },
  { "id": "4", "title": "The Dark Knight" }
]

As you'll learn in the next section, most of your route handlers will interact with the schema ORM object instead of the lower-level database object, but it's still good to know that the database is there if you need it.

The most common place you'll use the database directly is in your tests, where you can access it via server.db. It can be useful to assert against the state of Mirage's database to verify that your app's network requests are sending over the correct data.

test("I can create a movie", async function (assert) {
  await visit("/movies/new")
  await fillIn(".title", "The Dark Knight")
  await click(".submit")

  assert.dom("h2").includesText("New movie saved!")
  assert.equal(server.db.movies[0].title, "The Dark Knight")
})

You can view the rest of the Database APIs in the API reference.


Next, let's learn about Mirage's ORM.