LoginSignup
3
1

More than 5 years have passed since last update.

フロントエンドMock APIを静的型付け

Last updated at Posted at 2018-11-18

agreedを利用してモックAPIを実装する備忘録として

使用ライブラリ

agreed

フロントエンド側モックサーバ 兼 バックエンド側テストクライアントとしても使える

agreed-typed

agreedにtypescriptによる静的型付けを提供できる
API定義からjson形式のswaggerファイルを生成できる

実装

インストール

フロント側プロジェクトにローカルインストール

$ npm i -D agreed agreed-typed

agreed

contractと呼ばれるagreedにおけるクライアント要求ファイルを作成する

agreed.js
module.exports = [
  {
    request: {
      path: '/user/:id',
      method: 'GET',
      query: {
        q: '{:someQueryStrings}'
      },
      values: {
        id: 'yosuke',
        someQueryStrings: 'foo'
      }
    },
    response: {
      body: {
        message: '{:greeting} {:id} {:someQueryStrings}',
        images: '{:images}',
        themes: '{:themes}'
      },
      values: {
        greeting: 'hello',
        images: ['http://example.com/foo.jpg', 'http://example.com/bar.jpg'],
        themes: {
          name: 'green'
        }
      }
    }
  }
]

req/resにおけるqueryやbodyに記述する{:foo} と valuesのfooが紐づく

package.jsonにモックAPIサーバ起動タスクを用意するのが一般的かも

package.json
{
  (snip)
  "scripts": {
    "agreed": "agreed-server --path ./agreed.js --port 3010"
  }
}

このように手軽にフロント側でモックAPIサーバを用意できるが、この上API定義に静的型付けや必須/非必須などの情報を追加定義したい

agreed-typed

  • typescriptによるAPI定義の静的型付け
  • swaggerジェネレータとしての機能も提供
agreed.ts
import {
  APIDef,
  GET,
  Capture,
  ResponseDef,
  Success200,
  Error404,
  convert,
} from 'agreed-typed';

/**
 * @summary User Get API
 */
export type UserApi = APIDef<
  GET, // HTTP Method
  ['user', Capture<':id'>], // API Path
  {}, // request header
  { q: string }, // request query
  undefined, // request body
  {}, // response header
  | ResponseDef<Success200, CreateResponseBody>
  | ResponseDef<Error404, { error: 'error test' }> // response body
>;

type CreateResponseBody = {
  message: string;
  images?: string[];
  themes?: object;
};

const User: UserApi[] = [
  {
    request: {
      path: ['user', ':id'],
      method: 'GET',
      query: {
        q: '{:someQueryStrings}',
      },
      body: undefined,
    },
    response: {
      status: 200,
      body: {
        message: '{:greeting} {:id} {:someQueryStrings}',
        images: '{:images}',
        themes: '{:themes}',
      },
      values: {
        greeting: 'hello',
        images: ['http://example.com/foo.jpg', 'http://example.com/bar.jpg'],
        themes: {
          name: 'green',
        },
      },
    },
  },
  {
    request: {
      path: ['user', '9999'],
      method: 'GET',
      query: {
        q: '{:someQueryStrings}',
      },
      body: undefined,
    },
    response: {
      status: 404,
      body: {
        error: 'error test',
      },
    },
  },
];

const agrees = [User].map((a: any) => convert(...a));
module.exports = agrees.reduce((acc, val) => acc.concat(val), []);

contractのモジュール化

contractをモジュール管理することもできるのでメンテナンス性を担保できる

api/sendMessage.ts
import {
  APIDef,
  POST,
  ResponseDef,
  Success200,
  Error404,
} from 'agreed-typed';

/**
 * @summary Send Message API
 */
export type SendMessageApi = APIDef<
  POST, // HTTP Method
  ['send'], // API Path
  {}, // request header
  {}, // request query
  CreateRequestBody, // request body
  {}, // response header
  | ResponseDef<Success200, CreateResponseBody>
  | ResponseDef<Error404, { error: 'error post test' }> // response body
>;

type CreateRequestBody = {
  message: string;
};

type CreateResponseBody = {
  result: string;
};

const SendMessage: SendMessageApi[] = [
  {
    request: {
      path: ['send'],
      method: 'POST',
      body: {
        message: 'foo bar',
      },
    },
    response: {
      status: 200,
      body: {
        result: 'SEND SUCCESS',
      },
    },
  },
  {
    request: {
      path: ['send'],
      method: 'POST',
      body: {
        message: 'error',
      },
    },
    response: {
      status: 404,
      body: {
        error: 'error post test',
      },
    },
  },
];

module.exports = SendMessage;

先に作ったuser情報取得APIも同様にモジュール化して、agreed.tsでまとめるように書き換える

agreed.ts
import { convert } from 'agreed-typed';

import * as GetUser from './api/getUser';
import * as SendMessage from './api/sendMessage';

const agrees = [GetUser, SendMessage].map((a: any) => convert(...a));
module.exports = agrees.reduce((acc, val) => acc.concat(val), []);

備忘録のため一旦ここまで

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1