【ITニュース解説】Building Web Servers from First Principles (Part 6)
2025年09月18日に「Dev.to」が公開したITニュース「Building Web Servers from First Principles (Part 6)」について初心者にもわかりやすく解説しています。
ITニュース概要
WebサーバーでPOSTリクエストを扱い、JSONデータを受け取って新しい情報(リソース)を作成する方法を学ぶ。Go言語でリクエストボディを読み込み、JSONを解析してデータを作り、適切な応答を返す。フロントエンドが求める本格的なAPI構築の基本を解説する。
ITニュース解説
この解説では、ウェブサーバーをゼロから構築する学習の一環として、特に「POSTリクエストの処理」と「JSONデータの扱い」に焦点を当てる。これまでは、URLパスからの情報取得(動的ルーティング)、GETリクエストのクエリパラメータ処理、簡単なJSONレスポンスの返却方法を学んできた。しかし、実際のアプリケーションでは、単にデータを読み取るだけでなく、新しいデータを作成したり、更新したりする機能が不可欠となる。これが、今回構築する「真のJSON API」の目的である。
現在のサーバーはデータの読み取り(GETリクエスト)には対応しているが、新しいデータの作成(POSTリクエスト)には未対応である。フロントエンドアプリケーションが新しい情報を登録する際、HTTPのPOSTメソッドを使い、通常はヘッダーに「Content-Type: application/json」を指定し、ボディ部にJSON形式のデータを格納してサーバーに送信する。サーバー側では、このリクエストボディからJSON文字列を読み取り、プログラムで扱えるGoの構造体に変換し、必要な処理を実行した後、適切なレスポンスを返す一連の作業が求められる。
まず、リクエストボディ処理の基礎として、送られてきたJSONデータをそのまま返す「エコーハンドラ」を実装する。Go言語のnet/httpパッケージを用いる。POSTリクエストのハンドラ関数では、http.RequestオブジェクトのBodyフィールドからio.ReadAll(r.Body)を使ってリクエストボディ全体をバイト列として読み込む。ボディの読み込み後は、defer r.Body.Close()を呼び出し、リソースの解放を確実に行うことが重要である。読み込んだバイト列は生のJSON文字列なので、これをGoの構造体へ変換するため、encoding/jsonパッケージのjson.Unmarshal関数を使用する。この関数はJSONバイト列と、データを格納したいGo構造体へのポインタを引数に取り、JSONデータを構造体のフィールドにマッピングする。Go構造体のフィールドにはjson:"message"のような「タグ」を付与することで、JSONキー名と構造体フィールド名のマッピングを制御できる。このエコーハンドラを実装し、curlコマンドでテストすることで、サーバーがJSONリクエストボディを正しく読み取り、パースできることを確認できる。
リクエストボディの読み取りとJSONパースは、POSTリクエストを処理する上で頻繁に登場する共通処理である。そのため、これらの手順をreadBodyという再利用可能な関数として切り出すことで、コードの重複を避け、ハンドラ関数をより簡潔に保つ。readBody関数は、http.Requestと、データを格納する任意の構造体へのポインタを受け取り、内部でio.ReadAllとjson.Unmarshalを実行する。エラーが発生した場合はそれを返し、ハンドラ関数側ではそのエラーに応じて適切なHTTPステータスコードを返したり、エラーメッセージをクライアントに送ったりする。この抽象化により、ハンドラ関数はビジネスロジックに集中できるようになる。
共通のreadBody関数を活用し、いよいよ実際のユーザー作成APIエンドポイントを構築する。POST /usersというパスでリクエストを受け付け、新しいユーザーを作成するcreateUserハンドラ関数を定義する。この関数では、まずreadBody関数を使ってリクエストボディからUserPayload構造体(ユーザー名とメールアドレスを含む)にデータをパースする。次に、受け取ったデータに対して基本的な入力値検証を行う。例えば、ユーザー名やメールアドレスが空ではないかを確認し、不備があれば400 Bad Requestというステータスコードと共にエラーメッセージを返す。検証が成功したら、インメモリで管理しているユーザーリスト(Goのスライス)に新しいユーザー情報を追加する。Idは既存のユーザー数に基づいて単純に採番し、NameとEmailはリクエストボディから取得した値を使用する。最後に、201 Createdというステータスコードと共に成功のメッセージをクライアントに返す。
このAPIをより堅牢でプロフェッショナルなものにするためには、JSON APIのベストプラクティスに従うことが重要である。第一に、レスポンスのContent-Typeヘッダーをapplication/json; charset=utf-8に設定することで、クライアントに対して送り返すデータがJSON形式であり、UTF-8エンコーディングであることを明示する。第二に、プレーンテキストのメッセージではなく、常に構造化されたJSON形式でレスポンスを返すようにする。成功時もエラー時も、例えば{"message": "User created successfully", "data": {...}}のような形式や、{"error": "Validation failed", "message": "Name and email are required"}のような形式で返すことで、クライアント側でのエラーハンドリングやデータ処理が一貫して行えるようになる。Goでは、map[string]anyのようなマップ構造体を使ってJSONオブジェクトを表現し、json.Marshal関数でそれをJSON文字列に変換してレスポンスボディとして書き出す。
HTTPステータスコードの適切な利用も、プロフェッショナルなAPIには不可欠な要素である。例えば、新しいリソースが正常に作成された場合には201 Createdを、クライアントからのリクエストに問題があった場合(JSONの形式が不正、必須項目が欠落しているなど)には400 Bad Requestを返す。サーバー内部で予期せぬエラーが発生した場合には500 Internal Server Errorを、要求されたリソースが見つからなかった場合には404 Not Foundを返す。これらのステータスコードは、クライアントがレスポンスボディの中身を確認する前に、リクエストの結果について大まかな情報を得るための信号のような役割を果たす。
これまでの作業を通じて、私たちはリクエストボディの正確な読み取りとJSONデータのパース、新しいリソースの作成、適切なHTTPステータスコードの利用、構造化されたJSONレスポンスの提供、そして基本的なデータ検証といった、堅牢なAPIを構築するための重要な能力をサーバーに組み込むことができた。これらの概念は、Goの標準ライブラリだけでなく、Ginのような有名なウェブフレームワークが提供する機能と本質的に同じであり、フレームワークは単に低レベルな処理をより少ないコードで記述できるよう、便利な関数や抽象化を提供しているに過ぎない。つまり、今回学んだ基本原則は、どのようなフレームワークを使っても共通して適用される、普遍的な知識なのである。
次回のステップでは、サーバーをさらに本番環境向けにするための重要な機能、「ミドルウェア」の導入について学ぶ予定である。ミドルウェアは、リクエストのログ記録、レスポンスのログ記録、リクエスト処理時間の計測、サーバーのエラー発生時の回復処理など、アプリケーション全体に横断的に適用される共通の機能を実現するために利用され、サーバーのデバッグを容易にし、安定した運用を可能にする。