Edited at

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

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


使用ライブラリ


agreed

https://github.com/recruit-tech/agreed

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


agreed-typed

https://github.com/akito0107/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), []);



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