ルートハンドラ

ルートハンドラを使用すると、Mirageサーバーが処理できるURLを定義できます。

最も単純なルートハンドラは、URLをオブジェクトにマッピングします。

this.get("/movies", { movies: ["Interstellar", "Inception", "Dunkirk"] })

これで、アプリが/moviesにGETリクエストを行うと、このオブジェクトがJSONデータとして返されます。

APIがアプリとは異なるホストまたはポートにある場合は、urlPrefixを設定します。

    routes() {
      this.urlPrefix = 'http://localhost:3000';

2番目の引数として関数を受け渡すことで、関数ルートハンドラを作成することもできます。

this.get("/movies", (schema, request) => {
  return ["Interstellar", "Inception", "Dunkirk"]
})

関数ルートハンドラは、Mirageのデータレイヤーとリクエストオブジェクトにアクセスできるため、最も柔軟なルートハンドラです。ほとんどのルートハンドラは関数になります。

HTTP動詞を任意に使用してAPIルートを定義できます。各動詞メソッドは同じシグネチャを持っています。最初の引数はパス(URL)、2番目の引数はレスポンスを返す関数です。

this.get('/movies', () => { ... });
this.post('/movies', () => { ... });
this.patch('/movies/:id', () => { ... });
this.put('/movies/:id', () => { ... });
this.del('/movies/:id', () => { ... });
this.options('/movies', () => { ... });

タイミング

ルートハンドラの最後の引数は、タイミングを調整するために使用できるオプションオブジェクトです。これを使用して特定のルートのレスポンスを遅延させ、低速なネットワークと通信する場合のアプリの動作を確認します。

this.get(
  "/movies",
  () => {
    return ["Interstellar", "Inception", "Dunkirk"]
  },
  { timing: 4000 }
)

デフォルトの遅延は開発中は400ms、テスト中は0です(テストが高速に実行されます)。

すべてのルートに対してグローバルなタイミングパラメータを設定することもできます。個々のタイミングパラメータはグローバル設定をオーバーライドします。

createServer({
  routes() {
    this.namespace = "api"
    this.timing = 2000

    this.get("/movies", () => {
      return ["Interstellar", "Inception", "Dunkirk"]
    })

    this.get(
      "/complex-query",
      () => {
        return [1, 2, 3, 4, 5]
      },
      { timing: 3000 }
    )
  },
})

テストに遅延を追加する場合は、テストにタイミングパラメータを配置することで、個々のテストのタイミングをオーバーライドできます。

test("this route works with a delay", function () {
  server.timing = 10000

  // ...
})

各テストの後にサーバーがリセットされるため、このオプションはスイートの残りの部分に影響しません。

データレイヤへのアクセス

ルートハンドラは、最初のパラメータとしてschemaを受け取ります。これにより、Mirageのデータレイヤーにアクセスできます。

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

ほとんどのルートハンドラは、何らかの形でデータレイヤーとやり取りします。

2番目のパラメータはrequestオブジェクトです。これには、アプリが行ったリクエストに関する情報が含まれています。たとえば、動的なURLセグメントにアクセスできます。

this.get("/movies/:id", (schema, request) => {
  let id = request.params.id

  return schema.movies.find(id)
})

リクエストボディにアクセスして、データを含むPOSTまたはPATCHリクエストを処理することもできます。

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

  return schema.movies.create({ attrs })
})

normalizedRequestAttrsヘルパー(下記に説明)は、リクエストデータの処理を簡素化します。

動的パスとクエリパラメータ

ルートハンドラに挿入されるリクエストオブジェクトには、動的なルートセグメントとクエリパラメータが含まれています。

動的なセグメントを持つルートを定義するには、パスにコロン構文(:segment)を使用します。動的な部分はrequest.params.[segment]から利用できます。

this.get("/authors/:id", (schema, request) => {
  let id = request.params.id

  return schema.authors.find(id)
})

リクエストからのクエリパラメータにもrequest.queryParams.[param]からアクセスできます。

ステータスコードとヘッダ

デフォルトでは、Mirageはルートに使用されている動詞に基づいてレスポンスのHTTPステータスコードを設定します。

  • GETは200
  • PATCH/PUTは204
  • POSTは201
  • DELETEは204

レスポンスボディがある場合、PATCH/PUTとPOSTは200に変更されます。

さらに、Content-Typeヘッダはapplication/jsonに設定されます。

ルートハンドラでResponseクラスのインスタンスを返すことで、レスポンスコードとヘッダの両方をカスタマイズできます。

import { createServer, Model, Response } from "miragejs"

createServer({
  models: {
    author: Model,
  },
  routes() {
    this.post("/authors", function (schema, request) {
      let attrs = JSON.parse(request.requestBody).author

      if (attrs.name) {
        return schema.authors.create(attrs)
      } else {
        return new Response(
          400,
          { some: "header" },
          { errors: ["name cannot be blank"] }
        )
      }
    })
  },
})

外部オリジン

Mirageを使用して、他オリジンリクエストをシミュレートできます。

this.get('/contacts', ...)

デフォルトでは、以下の様なルートは、アプリを提供している同一オリジンに対して行われたリクエストをインターセプトします。異なるオリジンを処理するには、完全修飾ドメイン名を使用します。

this.get('http://api.twitter.com/v1', ...)

アプリ全体で外部(他オリジン)APIを使用する場合は、urlPrefix を介してグローバルにドメインを設定できます。

createServer({
  routes() {
    this.urlPrefix = 'https://my.api.com';

    // This route will intercept requests to https://my.api.com/contacts
    this.get('/contacts', ...)
  }
})

ヘルパー

関数ルートハンドラを作成する際には、いくつかのヘルパーを使用できます。

Mirageを初めて使用する場合は、まだこれらを理解する必要はありません。これらは、より高度なルートハンドラを作成する際に役立ちます。

serialize

このヘルパーは、シリアライザレイヤーを通過させた後、指定されたモデルまたはコレクションのJSONを返します。返却前にシリアライズされたJSONを最終的に変更する場合に役立ちます。

// Note: Be sure to use function() here, rather than () => {}
this.get("/movies", function (schema) {
  let movies = schema.movies.all()
  let json = this.serialize(movies)

  json.meta = { page: 1 }

  return json
})

デフォルトでは、このメソッドは指定されたモデルまたはコレクションの名前付きシリアライザを使用します。2番目の引数として特定のシリアライザ名を指定できます。

this.get("/movies", function (schema) {
  let movies = schema.movies.all()
  let json = this.serialize(movies, "sparse-movie")

  json.meta = { page: 1 }

  return json
})

シリアライザについては、このガイドの後半で詳しく説明します。

normalizedRequestAttrs

このヘルパーは、リクエストの本文を正規化された形式で返します。これは、レコードの作成や操作に適しています。基本的に、APIペイロードからフォーマットロジックを削除し、Mirageのデータベースを変更するために使用できる基礎となる属性を提供します。

たとえば、アプリが次のデータでPOSTリクエストを行う場合

// POST /users

{
  "data": {
    "type": "users",
    "attributes": {
      "first-name": "Conan",
      "middle-name": "the",
      "last-name": "Barbarian"
    },
    "relationships": {
      "team": {
        "data": {
          "type": "teams",
          "id": 1
        }
      }
    }
  }
}

normalizedRequestAttrs() は次のように使用できます。

this.post("/users", function (schema, request) {
  let attrs = this.normalizedRequestAttrs()
  /*
    attrs is this object:
      {
        firstName: 'Conan',
        middleName: 'the',
        lastName: 'Barbarian',
        teamId: '1'
      }
  */
  return schema.users.create(attrs)
})

属性キーはキャメルケースに変換され、team外部キーが抽出されていることに注意してください。これは、userteam外部キーを所有しているためです。別のリレーションシップがリクエストに含まれているが、userがその外部キーを所有していない場合、抽出されません。

このヘルパーメソッドは、シリアライザのnormalizeメソッドを利用します。上記の例では、アプリがJSONAPISerializerを使用しており、#normalizeメソッドが既に記述されていると仮定しています。バンドルされたシリアライザのいずれも使用していない場合は、#normalizeを実装し、JSON:APIドキュメントを返す必要があります。

さらに、ES6アロー関数(例:() => { ... })ではなく、完全なfunctionを使用する必要があります。normalizedRequestAttrsは関数ハンドラからthisコンテキストを必要とし、アロー関数は外部スコープからthisをバインドするためです。

normalizedRequestAttrs()は動作するためにmodelNameに依存しており、リクエストのURLに基づいて自動的に検出しようとします。従来のURL(例:PATCH /users/1)を使用する場合は、ヘルパーは動作するはずです。カスタムのURL(例:PATCH /users/edit/1)を使用する場合は、ヘルパーに最初の引数としてmodelNameを提供する必要があります。

this.patch("/users/edit/:id", function (schema, request) {
  let attrs = this.normalizedRequestAttrs("user")
  // ...
})

低レベルな関数ルートハンドラの記述については以上です!

関数ルートハンドラは柔軟性がありますが、すべてのエンドポイントに対して記述するのは面倒です。十分に従来的なAPIを使用している場合は、関数ルートハンドラを少なく記述し、ショートハンドをより多く記述できるようになるでしょう。次にそれらについて説明します!