WebAPI作りで最低限押さえておいたほうが良いと思ったこと

WebAPI作りで最低限押さえておいたほうが良いと思ったこと

はじめに

おばんです、人生初健保鮨行きが決定して楽しみな田中です。🍣

このエントリでは、私がここ最近WebAPIを作っていてクライアントサイドとのやりとりを通して学んだ、「話して決めておいたほうがよいこと」をまとめます。出てくるコード例はNode.jsで書いています。

エラーはわかりやすく、管理しやすく

エラーが発生したとき、HTTPステータスコードだけで原因を区別するのが難しい場合があります。エラーをわかりやすく、管理しやすくするためには そのエラーが「なんなのか」、「なぜ発生したのか」、「次にどうすべきなのか」 を明記し、デバッグや管理のための 手間を減らす 必要があります。

今回、例として取り扱うのが以下のjsonです。これはリクエストボディに含んだエラーを示しています。このエラーのなにが良いかを解説していきます。

{
  "error": "LackOfLoginParameter",
  "message": "ログインに必要なリクエストパラメータが不足しています。リクエストパラメータを確認してください。",
  "display_message": "ログインに失敗しました。ID/Passwordに間違いがないかご確認のうえ、再度ログインしてください。"
}

エラーメッセージは開発者向けのものとユーザー向け(表示用)のものを二つ用意する

開発者向けのメッセージは開発のデバッグ時に、発生したエラーの原因をわかりやすくするために必要です。なぜそのエラーが起きたのか、なにが不足していたのかを特定するのに役立ちます。適切にメッセージが書かれていれば、変なハマり方をして時間を無駄にしなくて済みます。

ユーザー向けのメッセージは発生したエラーをユーザーに伝える必要がある場合に考えます。また、ユーザーに伝えるメッセージをサーバーサイドで管理する場合に当てはまります。サーバーサイドでユーザー向けのエラーメッセージを管理しておくと、クライアントサイドは返却されたメッセージを表示するだけでいいので、実装コストを抑えられるというメリットもあります。

{
  "error": "LackOfLoginParameter",
  "message": "ログインに必要なリクエストパラメータが不足しています。リクエストパラメータを確認してください。", // <- 開発者向けメッセージ
  "display_message": "ログインに失敗しました。ID/Passwordに間違いがないかご確認のうえ、再度ログインしてください。" // <- ユーザー向け(表示用)メッセージ
}

messageの内容はどのパラメータが不足しているかまで提示してあげるほうが親切ですので、こんな書き方もアリです。

"ログインに必要なリクエストパラメータの項目、 'ID' が不足しています。リクエストパラメータを確認してください。"

ちなみにこの内容は以下のQiitaに同じトピックが解説されており、参考にさせていただきました。

エラーはエラーコードの連番などではなく文字列で定義する

今回の例で扱っているjsonで error となっている箇所が code として、APIエラーの種類を数字の連番でエラーコードとして定義されている場合もよくあるかと思います。

{
  "code": 01,
  ......,
  ...
}

code として定義するパターンは数字に対応するどのエラーかをドキュメントと突き合わせる必要があって手間です。(それも運良く機能するドキュメントが存在すればの話。)それならいっそ、どんなエラーかを識別する文字列で最初から定義してしまった方が、確認のための二度手間や二重管理が無くなって良いです。

{
  "error": "LackOfLoginParameter", // <- 連番ではなく、そのエラーを指し示す文字列で定義されたエラーコード
  "message": "ログインに必要なリクエストパラメータが不足しています。リクエストパラメータを確認してください。",
  "display_message": "ログインに失敗しました。ID/Passwordに間違いがないかご確認のうえ、再度ログインしてください。"
}

リクエストパラメータの空文字バリデーションをどうするか

空文字をどのように扱うかという話です。許容するか、バリデーションで弾くかどうか。

たとえば必須なリクエストパラメータはnullチェックを行いますが、このときリクエストパラメータに空文字 '' が入ってきた場合の扱いはどのようになるでしょうか?

const emptyString = ''

// パターンA
if (emptyString == null) {
    console.log('空文字は弾かないよ')
}

// パターンB
if (!emptyString) {
    console.log('空文字も弾くよ')
}

答えは 決めの問題 ですが、「どういう扱いになるんだっけ?」ということは把握して、クライアントサイドと共通認識を持つようにしておきましょう。サーバーとクライアントで役割や人がまたがる際には、コミュニケーションロスからバグを発生させがちですが、ここは防ぎたいところです。

また言語やフレームワークによってnullチェックに関わる細かい仕様の差異があるので、WebAPI作成者はこの部分を把握しましょう。バリデーションが思っていたのと違う挙動をしてしまうことがあるかもしれません。

下記のコードはNode.jsの例ですが、こんな違いがあってハマったりしたので注意が必要です。

const emptyString = ''

// nullとundefinedのチェック。空文字は弾かない。
if (emptyString == null) {
    console.log('空文字は弾かないよ') // <- 表示されない。
}

// nullとundefinedと空文字のチェック。空文字も弾く。
if (!emptyString) {
    console.log('空文字も弾くよ') // <- 表示される。
}

また、ユニットテストを書いておくことでバリデーションの仕様を明示できます。あとで「ここの仕様はどうなっていたっけ?」と思った時も、テストに書き起こしておけば、どういう扱いをするのかがわかりやすくなります。(以下のテストコードは mochaassert を利用しています。)

describe('Validaterのテスト', () => {
  const validater = new Validater()
  
  context('nullをバリデーションにかけた場合', () => {
    it('falseが返却されること', () => {
      assert.equal(validater.isValid(null), false)
    })
  })
  
  context('undefinedをバリデーションにかけた場合', () => {
    it('falseが返却されること', () => {
      assert.equal(validater.isValid(undefined), false)
    })
  })
  
  context('空文字をバリデーションにかけた場合', () => {
    const emptyString = ''
    
    it('falseが返却されること', () => {
      assert.equal(validater.isValid(emptyString), false)
    })
  })
})

まとめ

自分が経験したWebAPIのエラーの作り方についてまとめました。

今回はWebAPIと題して解説しましたが、クライアントサイドでインターフェースを考える際にも役立つ観点だと思いますので、気になる方はチームで議論してみると素晴らしいと思います。

参考・関連