REST API 考え方
API参考
エンドポイント
APIにアクセスするためのURIのこと
よいREST API = 良いエンドポイント = 良いURI
HTTPメソッド
GET, POST, PUT, PATCH, DELETE, HEAD
リソースをどのように操作するか。
formだとget/postしか使えない
URI
操作する対象のリソース
URIで対象を決めて、メソッドでなにするかを決める
良いURIとは
どんな機能を持つか推測しやすい
・短い
・理解できる
・すべて小文字
・推測可能
・一貫性がある
・サーバーアーキテクチャに左右されない
短いURI
同じことを表しているなら短くてシンプルな方が良い
http://api.hoge.com/service/api/search
http://api.hoge.com/search
理解できる
パット見で用途がわかるようにする
・省略しない
・適切な単語を使用する。単語を一般的なものに合わせる
・過去形, 複数形を間違えない
http://api.hoge.com/u/
http://api.hoge.com/user/
# findよりsearchの方が一般的
http://api.hoge.com/find
http://api.hoge.com/search
# registという単語はない
http://api.hoge.com/regist
http://api.hoge.com/register
すべて小文字
大文字小文字が入り組んでいると見にくい
(google cloud apiなどでキャメルケースを使用しているケースもある)
推測可能
↓12345がitemIdで、他のitemIdを入れれば違うItemの情報が見れることが推測できる。
http://api.hoge.com/items/12345
サーバーアーキテクチャに左右されない
・サーバーアーキテクチャ変更時にpathが影響されると辛い
・API利用者にサーバー側の情報は不要
http://api.hoge.com/items/get.php
一貫性がある
同じフォーマットの方が分かりやすい
http://api.hoge.com/items/100
http://api.hoge.com/customer/get?customer=123
method
method | 用途 |
---|---|
GET | 取得 |
POST | 登録 |
PUT | 更新 |
PATCH | 一部更新 |
DELETE | 削除 |
HEAD | メタ情報取得 |
エンドポイント例
エンドポイント | method | 用途 |
---|---|---|
http://api.hoge.com/v1/users | GET | user一覧を取得 |
http://api.hoge.com/v1/users | POST | user追加 |
http://api.hoge.com/v1/users/:id | GET | userを取得 |
http://api.hoge.com/v1/users/:id | PUT | userを更新 |
http://api.hoge.com/v1/users/:id | PATCH | userを一部更新 |
http://api.hoge.com/v1/users/:id | DELETE | userを削除 |
一覧表示の例
- | - |
---|---|
http://api.hoge.com/v1/users | GET |
http://api.hoge.com/v1/users/list | GET |
relationの例
user -> friend
Head | Head | Head |
---|---|---|
http://api.hoge.com/users/:userId/friends | GET | フレンド一覧取得 |
http://api.hoge.com/users/:userId/friends | POST | フレンド追加 |
http://api.hoge.com/users/:userId/friends/:friendId | GET | フレンド取得 |
http://api.hoge.com/users/:userId/friends/:friendId | DELETE | フレンド削除 |
:fiendIdはフレンドのuserIdか、フレンド関係を表すテーブルのidのどちらが良いか
フレンドのuserIdの方が良い。
=> アーキテクチャに依存しない方が良い
=> フレンドidの方が推測可能
REST APIとそうじゃないAPI
・REST API
リソース主体でエンドポイントが決まる。
エンドポイントに処理は含まない
POST http://api.hoge.com/v1/users/
・そうじゃないAPI
処理でエンドポイントが決まる
POST http://api.hoge.com/v1/add-user
検索
検索パラメータ
・取得数(par_page, limit)
・取得位置(page, offset)
組み合わせ
・par_pageとpage
・limitとoffset
pageは1始まり, offsetは0始まり
pageはページ数を、offsetはアイテム数を指定する
相対位置による検索
・limitとoffsetによる検索はDBの負荷が大きくなる
・途中でinsertされると取得位置がずれる
絶対位置による検索
どこから検索するかパラメータで指定する(最後に取得したidや時刻など)
検索クエリ
q="hoge" : 部分一致しそう
name="hoge" : 完全一致しそう
検索エンドポイントのURIにsearchは入れるべきか?
・入れるとリソースを指すREST APIとは思想が離れる
・エンドポイントを検索用として強調しないなら入れても良さそう
クエリパラメータ or pathどちらにすべきか
ポイント
・URIはリソースを表現するためのもの
・省略可能かどうか
リソース特定になるならpath
省略可能ならクエリパラメータ(省略してデフォルト値使用できるので)
自己参照
自分自身を指すmeやselfキーワードを使うことでより便利なエンドポイントにできる。
自分のidを知らなくてもエンドポイントが決まる。
http://api.hoge.com/users/me
LSUDsとSSKDs
どちらにも言えることだけど、汎用的で分かりやすいものが良い
LSUDs
・Large Set of Unknown Developers
・大多数の外部developer向け
SSKDs
・Small Set of Known Developers
・コントロール可能な開発者向け(社内サービス開発エンジニアとか)
社内サービス開発はSSKDsになる。
社内サービス開発では、そのサービスのUXを高めることに注力してAPIを開発すべき。(汎用化しすぎて複数のAPIを実行しないとアプリが起動しなかったらUX最悪)
どの情報を返すかユーザーが選べるようにすると便利
不要なデータを返すとレスポンスサイズが無駄に大きく時間がかかる。
http://api.hoge.com/v1/users/111?fields=name,age
予めいくつかのfieldを組み合わせて提供する方法もある
small = name, email, age...
http://api.hoge.com/v1/users/111?fields=small
適切なエンベロープを選ぶ
エンベロープ = データ構造
↓レスポンスにheadersを入れなくてもレスポンスヘッダーを使えば良い。不要なデータ構造を含まないようにする
{
headers : { 略 }
body : { 略 }
}
データ構造はフラットに。でも階層構造を持ったほうが良い場合は使ってもok。
↓ 無理に階層構造持たせる必要はない
{
name: "hoge"
age: 13
profile: {
nickname: "ho"
}
}
{
name: "hoge"
age: 13
nickname: "ho"
}
↓階層構造があるとわかりやすく、データ量が少なくできるかも
{
name: "hoge",
age: 13,
hoge : {
id: 1
fuga: 0
}
}
{
name: "hoge",
age: 13,
hoge_id: 1
hoge_fuga: 0
}
配列を返すときのフォーマット
そのまま配列を返すか、オブジェクトの中に配列を入れるか。
jsonの仕様的には配列をそのまま入れてもok
[
{ name: "hoge", age: 1 },
{ name: "piyo", age: 1 },
]
{
users: [
{ name: "hoge", age: 1 },
{ name: "piyo", age: 1 },
]
}
どちらも大差ない。好みの問題になる
・オブジェクトにするとmetaを追加で入れやすい。拡張性考えるならオブジェクトの方が良さそう。
・オブジェクトにする場合は、そのレスポンスを見て人間がなんのデータかひと目で分かる
・配列そのまま返せばreact + typescriptだとキャストしやすい?
ページネーション
・全件数が必要?
=> db負荷かかるから注意
=> 普通にcount(*)するだけだとレコード数の増加に伴ってレスポンスが遅くなりがち
=> ↓回避する方法もある(ロールバックで欠番出る可能性あるから厳密なケースには使えないけど)
・続きがあるかどうかを返すのもあり
クライアントに続きあるかのフラグを見て判断してもらう。(UI的に厳しそうだけど)
このケースの場合、全件数を取得する必要がない。
limit 20件なら21件取得して、21件取れれば次がある、20件以下なら次はないと判断できる
{
hoges [ {}, {}, {}... ],
hasNext: false
}
↑こういうデータ構造にするなら配列だけ返すのではなくオブジェクトにしたほうが改修楽そう
・次ページに必要なパラメータを返す
HATEOASという考え方。
next_idとか返せば良さそう。
大きな数字
大きな数字を返すときは桁あふれに注意。
javascriptだと大きな数値があるjson文字列をobjectすると桁あふれしてもエラーがでず、数値が微妙に変わっていたりする(未確認)
idとかなら数値ではなく文字列で返すのもあり
参考
エラーレスポンス
・ステータスコードを正しく使う
200系 : 正常終了
300系 : リダイレクト
400系 : クライアントに問題あり
500系 : サーバーに問題あり
・エラーレスポンス返すならステータスコードは400 or 500系にする。
レスポンスボディにエラーメッセージを入れていても正しくステータスコードを入れる。
クライアントライブラリはステータスコードを見てエラーハンドリングするため
・エラーレスポンスのフォーマット
参考
・エラーレスポンスがうっかりhtmlにならないようにする
jsonが期待されているならjsonを返す
特に404のときとか
成功ステータスコード
200系
200 : ok
201 : 何か作成した
202 : accept。処理を受け付けたが完了していない
204 : not content。削除成功時
202に注意。
リクエストに応じて何かしらの非同期処理が行われる場合、リクエストは202で返す。
(ファイルの変換リクエストなど)
作成/変更/削除時のレスポンス
何をレスポンスするか。
作成(POST) : 作成したデータ。201
更新(PUT/PATCH) : 更新後のデータ。200
削除(DELETE): 空。204ステータスコード
クライアントエラーステータスコード
400系
400 : Bad Request。他のステータスコードでは合わない場合
401 : 認証エラー Unauthorized
403 : 認可エラー Forbidden
404 : Not found。ないよ
429 : too many requests。スロットル制限
サーバーエラーステータスコード
500系
500 : Internal Server Error
503 : メンテナンス中, インスタンスに問題があるなどの場合にGatewayが代わりにエラーを出す