REST APIクライアントの作成

ToDo一覧を実装する際には、バックエンドのREST APIにアクセスする必要があります。ここでは、OpenAPIドキュメントを利用してREST APIのクライアントを作成します。

OpenAPIドキュメントの確認

REST APIをOpenAPI仕様で記述したOpenAPIドキュメントは、プロジェクト作成時に作成しています。(参考:OpenAPI - Specification

バックエンドのrest-api-specification/openapi.yamlファイルがOpenAPIドキュメントになるため、内容を確認します。

例えば、ToDoの一覧を取得するためのREST APIは、次のように定義されています。

  /api/todos:
    get:
      summary: ToDo一覧の取得
      description: >
        登録しているToDoを全て取得する。
      tags:
        - todos
      operationId: getTodos
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Todo'
              examples:
                example:
                  value:
                    - id: 2001
                      text: やること1
                      completed: true
                    - id: 2002
                      text: やること2
                      completed: false
        '403':
          description: Forbidden

summarydescriptionには、ドキュメント化したときの説明文を定義します。

tagsには、REST APIをグルーピングするための名前を定義します。ここでは、ToDo管理とユーザー認証のREST APIを区別するために使用します。このtagsを指定することで、OpenAPIドキュメントから様々な成果物を生成したりする際に、グルーピングされるようになります。例えば、REST APIのクライアントコードでは、tagsに指定した名前ごとにクラス(ここではTodosApi)が生成されるようになります。

operationIdには、それぞれのREST APIを一意に識別するためのIDを定義します。これはクライアントを自動生成した際の関数名にもなるため、それを考慮して定義します。

responsesには、レスポンスを定義します。responsesにはステータスコードごとに定義します。JSON形式で返却するため、contentとしてapplication/jsonを定義します。

content内のschemaには、レスポンスデータの形式を定義します。ここでは、配列を返すように定義し、配列の中については$refを使って共通で定義したコンポーネントを参照するよう定義します。複数のREST APIで扱うToDoレスポンスの形式が同じであるため、このような定義にしています。

参照先となる形式は、次のように定義しています。

components:
  schemas:
    Todo:
      title: Todo
      type: object
      description: ToDo情報
      properties:
        id:
          type: integer
          description: ToDoのID
        text:
          type: string
          description: ToDoのタイトル
        completed:
          type: boolean
          description: ToDoのステータス
      required:
        - id
        - text
        - completed
      additionalProperties: false

propertiesには、このコンポーネントの項目を定義します。全ての項目が必ず必要であるため、requiredには全ての項目を定義します。ここで定義していない項目が追加で返却されることは想定していないため、additionalPropertiesにはfalseを定義します。

content内のexamplesに、実際に返却される例を定義します。

これらから、REST APIで返却されるJSONは、次のようなイメージになります。

{
  [
    {
      "id": 2001,
      "text": "やること1",
      "completed": true
    },
    {
      "id": 2002,
      "text": "やること2",
      "completed": false
    }
  ]
}

クライアントコードの生成

REST APIのクライアントコードを、OpenAPIドキュメントから生成します。

クライアントコードの生成には、OpenAPI Generatorを使用します。OpenAPIが提供しているツールで、OpenAPIドキュメントから様々なものを生成することができます。TypeScript用のクライアントコードについても様々な実装を生成することができますが、ここではtypescript-fetchを使用します。

OpenAPI Generatorはコンテナイメージでも提供されています。そのコンテナイメージを使用して実行するためのDocker Composeファイルとしてdocker-compose.api-gen.ymlを予め作成しているため、Docker Composeを使用してDockerコンテナ上で生成します。

frontendディレクトリで、次のコマンドを実行します。

$ docker-compose -f docker/docker-compose.api-gen.yml up

実行が完了すると、srcの下にbackendディレクトリが生成され、その配下にgenerated-rest-clientディレクトリが生成されます。このgenerated-rest-client配下に、自動生成されたクライアントコードが格納されています。

フロントエンドでREST APIを呼び出す際には、このクライアントコードを使用していきます。

クライアントコードのラッパーを作成

OpenAPIドキュメントからREST APIのクライアントコードを生成しましたが、使用時には同じ設定を行うことが多くなります。そこで、共通的な設定がされたクライアントコードを使用するために、生成したクライアントコードをラッピングしたBackendSerivceを作成します。コンポーネントからREST APIにアクセスする際には、生成したクライアントコードは直接使用せずに、このBackendSerivceを使用するようにします。

src/backendディレクトリにBackendService.tsを作成します。

src/backend/BackendService.ts

import {
  Configuration,
  TodosApi,
  Middleware,
  UsersApi
} from './generated-rest-client';

const requestLogger: Middleware = {
  pre: async (context) => {
    console.log(`>> ${context.init.method} ${context.url}`, context.init);
  },
  post: async (context) => {
    console.log(`<< ${context.response.status} ${context.url}`, context.response);
  }
};

const configuration = new Configuration({
  middleware: [requestLogger]
});

const todosApi = new TodosApi(configuration);

const usersApi = new UsersApi(configuration);

const signup = async (userName: string, password: string) => {
  return usersApi.signup({ inlineObject2 : { userName, password }});
};

const login = async (userName: string, password: string) => {
  return usersApi.login({ inlineObject3: { userName, password }});
};

const logout = async () => {
  return usersApi.logout();
};

const getTodos = async () => {
  return todosApi.getTodos();
};

const postTodo = async (text: string) => {
  return todosApi.postTodo({ inlineObject: { text }});
};

const putTodo = async (todoId: number, completed: boolean) => {
  return todosApi.putTodo({ todoId, inlineObject1: { completed }});
};

export const BackendService = {
  signup,
  login,
  logout,
  getTodos,
  postTodo,
  putTodo
};

生成されたクライアントコードでは、REST APIを呼び出すためにはTodosApi等のAPIクラスのオブジェクトを生成する必要があります。生成時には設定オブジェクトを渡すことで、様々な設定をすることができます。

生成したクライアントコードでは、Middlewareと呼ばれる部品を作成することで、リクエストやレスポンスに対する共通的な処理を実装することができます。ここでは、開発時にREST APIの呼び出しを確認しやすいように、リクエストとレスポンスをコンソールにログ出力するMiddlewareを作成します。

なお、OpenAPIドキュメントにはToDoを削除するためのREST APIも定義されていますが、削除については後の演習にて自身で実装できるよう、ここでは作成しません。

results matching ""

    No results matching ""