🦁

【フロントエンド】APIモジュールの返り値を考える

2022/09/18に公開

共通のapiインスタンスを作る

「APIどう設計していこうか」
「axiosを使いたいから共通の設定を使えるようにインスタンスを作ろう」

import axios from 'axios';
import { BASE_URL } from '../const';

export const apiInstance = axios.create({
  baseURL: BASE_URL
})

共有のAPIモジュールを作る

「共通のAPIインスタンスを作ったから、」
baseURLはそれぞれのAPI呼び出しの際書かなくていいな」
「API呼び出しはaxiosハードコードしたくないし」
「メソッドをコールすればどこからでも簡単にAPIが呼び出せるようなモジュールを作ろう」

import { Todo } from "../../@types/todo";
import { apiInstance } from './axios';

export const fetchTodos = async (): Promise<Todo[]> => {
  try {
    const { data } = await apiInstance.get<Todo[]>('todos')
    return {
      todos: data
    }
  } catch (error: any) {
    return error
  }
}
export interface Todo {
  userId: number
  id: number
  title: string,
  completed: boolean
}

エラーレスポンス

「成功の時の返り値はバックエンドからのデータをそのまま返せば良い」
「だけどエラーの方はどうしよう・・・」
「この返し方だとエラーの詳細を使用側が意識する必要があるし、」
毎回長ったらしくエラーハンドリングをしないといけない・・・」
「それだと効率もコードの見通しも良くないな」

エラーレスポンス型を作る

「使用側の負担を減らすために、」
「モジュールからは共通のエラーを返すようにしよう」
「そうすれば使用側のエラーハンドリングも簡単になるはずだ」

export interface ApiError {
  code: number
  message: string
}
export const fetchTodos = async (): Promise<Todo[]> => {
  try {
    const { data } = await apiInstance.get<Todo[]>('todos')
    return {
      todos: data
    }
  } catch (error: any) {
    if (error.code === 400) {
      throw {
        code: ERROR_CODES.BAD_REQUEST.code,
        message: ERROR_CODES.BAD_REQUEST.message,
      }
    }
    throw {
        code: ERROR_CODES.INTERNAL_SERVER_ERROR.code,
        message: ERROR_CODES.INTERNAL_SERVER_ERROR.message,
    }
  }
}

「これで使用者側はcatch内でエラーのタイプを判断して、」
messageをトーストやアラートへのメッセージへ流用できる」

「でもこれってモジュールでcatch使ってるのに使用側でも使わなきゃいけないのか・・・」
「tryやthenで全部判断できたらもっと便利な気がするな・・・」

catchの中でreturn

「catchの中でreturnしてあげれば使用側ではcatchを使う必要がないな!」
「でも今の状況だと使用者側はプロパティの有無でエラーかどうか判断しないといけないな・・・」
「例えばこんな感じか」

if ('code' in res) {
   // エラーハンドル
}

「これは悪くないけど、どっちが返ってくるかわからないというのは設計上シンプルじゃないし良くない気がするな・・・」
「そしてこのパターンだとcodeがあるか判断して、」
「その中にエラーハンドリングでネストしてifが生まれる可能性もあるからそれは嫌だな・・・」
「もっとシンプルにしたい・・・」

APIの返り値にエラーレスポンスを継承する

一定の形で返したいからAPIの返り値にエラーレスポンスを含めるのはどうだろう」
「だとするとAPIの返り値の型は一つで良くなる」

export interface FetchTodosResponse extends ApiError {
  todos: Todo[]
}
export const fetchTodos = async (): Promise<FetchTodosResponse> => {
  try {
    const { data } = await apiInstance.get<Todo[]>('todos')
    return {
      code: ERROR_CODES.NO_ERROR.code,
      message: ERROR_CODES.NO_ERROR.message,
      todos: data
    }
  } catch (error: any) {
    const errorResponseTodo = {
      userId: 0,
      id: 0,
      title: '',
      completed: false
    }
    if (error.code === 400) {
      return {
        code: ERROR_CODES.BAD_REQUEST.code,
        message: ERROR_CODES.BAD_REQUEST.message,
        todos: [errorResponseTodo]
      }
    }
    return {
        code: ERROR_CODES.INTERNAL_SERVER_ERROR.code,
        message: ERROR_CODES.INTERNAL_SERVER_ERROR.message,
        todos: [errorResponseTodo]
    }
  }
}

使用側

「使用側はcodeを見て判断すれば良いよね」
「成功の時もエラーの時も一つの型からパターンによって判断できるようになった」

if (res.code === ERROR_CODES.NO_ERROR.code) {
  // 正常処理
}

「エラーコードがNO_ERROR意外だったら例外が発生してるから、」
「コードに合わせてトーストやアラートを表示してあげれば良いよね」

Discussion