2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

HonoでTodo APIを作ってRESTfulなAPI設計を少し理解した

2
Posted at

はじめに

以前、Honoを使って、シンプルなTodo APIを作りました。

最初は、Todoの一覧取得・追加・更新ができればいいかな〜くらいの気持ちで実装していました。
実際に動くAPIは作れたのですが、あとから見直してみると、HTTPメソッドの使い方やステータスコードの返し方など、API設計として改善できそうな点がいくつかありました。

そこで今回は、最初に作ったTodo APIを少し見直して、RESTfulなAPI設計に近づけるためにリファクタしてみました。

この記事では、Honoで作ったTodo APIを題材に、以下のような点を整理します。

  • 作成時は201 Createdを返す
  • Todoの完了状態だけを更新する処理はPATCHを使う
  • エラー時もJSONで返す
  • DELETE /todos/:idを追加して、Todoリソースの操作を揃える

厳密なRESTのすべてを扱うわけではありませんが、実装を見直しながら、RESTfulなAPI設計の基本を少し理解することを目的にしました。

最初に作っていたTodo API

最初に作っていたTodo APIでは、以下の3つのエンドポイントと役割を用意していました。

GET  /todos      Todo一覧を取得する
POST /todos      Todoを作成する
PUT  /todos/:id  Todoの完了状態を更新する

この時点でも、Todoの一覧取得・追加・完了状態の更新はできていました。

ただ、あとから見直してみると、いくつか改善できそうな点がありました。

  • POST /todosで作成成功時もデフォルトの200 OKを返していた
  • 完了状態だけを変更しているのにPUTを使っていた
  • エラー時にJSON形式で返していなかった
  • Todoを削除するためのエンドポイントがなかった

動くAPIにはなっていましたが、RESTfulなAPI設計として考えると、もう少し意味が伝わりやすい形にできそうだと感じました。

RESTっぽくするために見直したこと

最初の実装でもTodo APIとしては動いていましたが、RESTfulなAPI設計に近づけるために、いくつか見直しました。

今回意識したのは、主に以下の4つです。

  • 作成時は201 Createdを返す
  • 完了状態だけを更新する処理はPUTではなくPATCHを使う
  • エラー時もJSON形式で返す
  • DELETE /todos/:idを追加する

作成時は201 Createdを返す

Honoでは、ステータスコードを指定しない場合、デフォルトで200 OKが返ります。

ただ、POST /todosは新しいTodoを作成する処理です。
新しいリソースを作成した場合は、200 OKよりも201 Createdを返す方が意味として自然です。
MDN - 201 Created

そこで、c.json()の第2引数に201を指定しました。

return c.json({ todo }, 201);

これで、Todoの作成に成功したときに201 Createdが返るようになりました。

PUTではなくPATCHで完了状態を更新する

最初は、Todoの完了状態を更新する処理をPUT /todos/:idとして実装していました。

app.put("/todos/:id", async (c) => {
  const { id } = c.req.param();
  const { completed } = await c.req.json();

  // 更新処理
});

ただ、今回更新しているのはTodo全体ではなく、completedだけです。

PUTはリソース全体を置き換えるときに使われることが多いため、今回のように一部の項目だけを変更する場合は、PATCHの方が意味として近いと感じました。
MDN - PUT request method

そこで、エンドポイントをPATCH /todos/:idに変更しました。

app.patch("/todos/:id", async (c) => {
  const { id } = c.req.param();
  const { completed } = await c.req.json();

  // 更新処理
});

React側の呼び出しも、PUTからPATCHに変更しました。

const response = await fetch(`http://localhost:3000/todos/${id}`, {
  method: "PATCH",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    completed: !todos.find((todo) => todo.id === id)?.completed,
  }),
});

これで、「Todoの一部を更新している」という意図がHTTPメソッドからも伝わりやすくなりました。

エラー時もJSONで返す

最初は、対象のTodoが見つからなかった場合にc.notFound()を返していました。

return c.notFound();

これでも404 Not Foundは返せますが、APIとしてはエラー時もJSON形式で返した方が、フロントエンド側で扱いやすいです。

そこで、エラー時のレスポンスをJSON形式に変更しました。

return c.json({ error: "Todo not found" }, 404);

これにより、ステータスコードだけでなく、エラーの内容もレスポンスとして返せるようになりました。

{
  "error": "Todo not found"
}

今回はシンプルにerrorだけを返していますが、今後はcodemessageを分けるなど、エラー形式を統一するとさらに扱いやすくなりそうです。

DELETE /todos/:idを追加する

最初の実装では、Todoを削除するAPIはありませんでした。

ただ、Todoというリソースを扱うAPIとして考えると、取得・作成・更新だけでなく、削除もできると操作が揃います。

そこで、DELETE /todos/:idを追加しました。

app.delete("/todos/:id", async (c) => {
  const { id } = c.req.param();

  try {
    await prisma.todo.delete({
      where: { id: Number(id) },
    });

    return c.body(null, 204);
  } catch {
    return c.json({ error: "Todo not found" }, 404);
  }
});

削除に成功した場合は、返すデータがないため204 No Contentを返すようにしました。

return c.body(null, 204);

対象のTodoが見つからなかった場合は、他のエラーと同じようにJSONで404 Not Foundを返します。

return c.json({ error: "Todo not found" }, 404);

これで、Todoリソースに対する基本的な操作が揃いました。

GET     /todos      一覧取得
POST    /todos      作成
PATCH   /todos/:id  部分更新
DELETE  /todos/:id  削除

設計のまとめ

今回のリファクタでは、Todo APIをRESTfulな設計に少し近づけるために、URL・HTTPメソッド・ステータスコード・エラーレスポンスを見直しました。

最終的なエンドポイントは以下のようになりました。

GET     /todos      Todo一覧を取得する
POST    /todos      Todoを作成する
PATCH   /todos/:id  Todoの完了状態を更新する
DELETE  /todos/:id  Todoを削除する

URLでは、createTododeleteTodoのような動詞ではなく、Todoというリソースを表す/todosを使いました。

操作の内容はURLではなく、HTTPメソッドで表すようにしました。

GET     取得
POST    作成
PATCH   一部更新
DELETE  削除

また、レスポンスのステータスコードも意味を意識して返すようにしました。

200 OK          取得・更新に成功したとき
201 Created     作成に成功したとき
204 No Content  削除に成功したとき
404 Not Found   対象のTodoが見つからないとき

エラー時には、ステータスコードだけでなくJSON形式でエラー内容を返すようにしました。

return c.json({ error: "Todo not found" }, 404);

今回の設計で意識したことをまとめると、以下の4つです。

  • URLはリソースを表す名詞にする
  • 操作はHTTPメソッドで表す
  • ステータスコードを意味のある値で返す
  • エラー時もJSON形式で返す

まだバリデーションやエラー形式の統一など改善できる点はありますが、最初の実装よりもAPIの意図が伝わりやすくなったと感じました。

実装して初めて分かったこと

今回、最初は「動くTodo APIを作る」ことを優先して実装しましたが、あとからRESTfulな設計を意識して見直してみると、HTTPメソッドやステータスコードにはそれぞれ意味があることに気づきました。

例えば、Todoを作成したときに200 OKではなく201 Createdを返すだけでも、「新しいリソースが作成された」という意味がレスポンスから伝わりやすくなります

また、Todoの完了状態だけを変更する場合、PUTよりもPATCHの方が意図に近いことも、実際に実装を見直す中で理解できました。

設計が先にあると、実装が楽

今回のリファクタを通して、API設計を先に整理しておくと実装時に迷いにくいと感じました。

例えば、Todoを削除する処理を追加するときも、

DELETE /todos/:id

と決めておくと、URLやHTTPメソッドで迷わずに実装できます。

ステータスコードも先に決めておくと、レスポンスの返し方を考えやすくなります。

201 Created     作成に成功したとき
204 No Content  削除に成功したとき
404 Not Found   対象のTodoが見つからないとき

最初は「とりあえず動けばOK」と思っていましたが、設計を少し意識するだけで、コードの意味がわかりやすくなると感じました。

APIを作るときは、いきなり実装するだけでなく、

  • どのURLにするか
  • どのHTTPメソッドを使うか
  • 成功時にどのステータスコードを返すか
  • エラー時にどんな形式で返すか

を先に整理しておくと、実装時に迷うことが減りそうです。

学んだこと

今回Honoで作ったTodo APIを見直すことで、RESTfulなAPI設計の基本を少し理解できました。

特に、URLにはリソースを表す名詞を使い、操作はHTTPメソッドで表すという考え方が印象に残りました。

GET     /todos      一覧取得
POST    /todos      作成
PATCH   /todos/:id  一部更新
DELETE  /todos/:id  削除

また、ステータスコードを意識して返すことの大切さも学びました。

c.json()をそのまま使うとデフォルトで200 OKが返りますが、作成時には201 Created、削除時には204 No Contentのように、処理の意味に合ったステータスコードを返す方がAPIとしてわかりやすくなります。

エラー時のレスポンスも、ただ404を返すだけでなく、JSON形式で返すことでフロントエンド側から扱いやすくなると感じました。

return c.json({ error: "Todo not found" }, 404)

今回の実装を通して、API設計は単に「動くURLを作る」ことではなく、URL・HTTPメソッド・ステータスコード・レスポンス形式を使って、処理の意味を伝えることでもあると学びました。

まだ厳密なREST設計を完全に理解できたわけではありませんが、Todo APIのような小さな題材でも、設計を見直すことで学べることが多いと感じました。

APIの処理の流れも少し見えた

スクリーンショット 2026-05-16 15.26.16.png
今回Todo APIを作ってみて、APIの中でどのような処理が行われているのかも少しイメージできるようになりました。

例えば、POST /todosでTodoを作成する場合、処理の流れは以下のようになります。

1. クライアントからAPIにリクエストを送る
2. Hono APIがリクエストを受け取る
3. リクエストボディからtitleを取り出す
4. DBにTodoを保存する
5. 作成したTodoをレスポンスとして返す

実際のコードでは、まずc.req.json()でリクエストボディを受け取ります。

const { title } = await c.req.json();

その後、受け取ったtitleをもとにTodoを作成します。

const todo = await prisma.todo.create({
  data: {
    title,
    completed: false,
  },
});

最後に、作成したTodoをJSONで返します。

return c.json({ todo }, 201);

この流れを実装したことで、APIは単にURLを用意するだけではなく、

  • リクエストを受け取る
  • 必要なデータを取り出す
  • 処理を実行する
  • レスポンスを返す

という流れで動いていることがわかりました。

2
0
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
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?