はじめに
goを学ぶ上でweb apiについて調査したので備忘録として残したいと思います。
要約(Go Web APIライブラリ/フレームワークの選び方)
特に性能面の大きな制限がない場合や、特定のフレームワークへのこだわりがない場合は、事実上のデファクトスタンダードであるGinの使用が推奨されます。
-
シンプルさ/標準ライブラリ志向:
- net/httpを使用する。フレームワークを使わずシンプルに構築したい場合に適しています。メモリ使用率が低く、リクエスト処理の効率が良い点が魅力です。
-
汎用的な開発:
- Gin(デファクトスタンダード)。ルーティング、ミドルウェア、JSONバインディングなど、Web API開発に必要な機能が最適化されており、取り回しが最も容易です。
-
超高速/高スループット:
-
fiberを使用する。
fasthttpをベースにしており、超高速で大量のリクエストを捌く必要がある場合(例:自前でのキャッシュ構築など)に選択肢となります
-
fiberを使用する。
net/http(Go 標準ライブラリ)
- 特徴: Goの標準ライブラリ。基本的なWeb API機能が実装されており、依存性がなくシンプルなAPIを必要とする場合に最適です
- 利点: 標準機能だけで動作するため、外部ライブラリへの依存がゼロです
- 注意点: 外部のミドルウェアや高度な機能(JSONバインディング、バリデーションなど)が必要な場合は、自前で実装するか他のライブラリを組み合わせる必要があります
💡 Go 1.22での機能強化:
Go 1.22でルーティングやパスパラメータ(例:/users/{id}のid)を取得する機能が標準ライブラリに追加され、httprouterやgorilla/muxといったルーティング特化型ライブラリの優位性が相対的に小さくなりました。以前に比べ、net/http単体でより扱いやすくなっています。
func helloHandler(w http.ResponseWriter, r *http.Request) {
// レスポンスのContent-TypeをJSONに設定
w.Header().Set("Content-Type", "application/json")
// 1. クエリパラメータのマップを取得
queryValues := r.URL.Query()
// 2. 特定のパラメータを取得 (Get() はキーが存在しない場合、空文字列 "" を返します)
name := queryValues.Get("name")
// 3. 取得した値を利用してJSONレスポンスを作成
var response map[string]string
if name == "" {
response = map[string]string{"message": "Hello, stdlib API Server!"}
}else {
response = map[string]string{"message": fmt.Sprintf("Hello, %s", name)}
}
// JSONにエンコードして書き込む
json.NewEncoder(w).Encode(response)
// 200 OK を返します
w.WriteHeader(http.StatusOK)
}
func helloPathHandler(w http.ResponseWriter, r *http.Request) {
// レスポンスのContent-TypeをJSONに設定
w.Header().Set("Content-Type", "application/json")
// 1. リクエストからパス全体を取得する
name := r.PathValue("name")
// 抽出したIDが空でないか、または追加でバリデーションを行う
if name == "" {
http.Error(w, `{"error": "name is missing"}`, http.StatusBadRequest)
return
}
// JSONレスポンスを作成
response := map[string]string{"message": fmt.Sprintf("Hello, %s", name)}
// JSONにエンコードして書き込む
json.NewEncoder(w).Encode(response)
// 200 OK を返します
w.WriteHeader(http.StatusOK)
}
func main() {
// ルーター(ServeMux)を作成
mux := http.NewServeMux()
// hello apiのエンドポイントを追加する
mux.HandleFunc("GET /hello", helloHandler)
// hello name apiのエンドポイントを追加する
mux.HandleFunc("/hello/{name}", helloPathHandler)
log.Println("Server listening on :8080...")
// サーバーを起動
if err := http.ListenAndServe(":8080", mux); err != nil {
log.Fatal(err)
}
}
fasthttp
-
特徴:
net/httpとは別の独自のHTTP実装を持つライブラリで、高い処理速度と低いメモリ使用率を追求しています -
利点: クエリパラメータやリクエストボディの取得が
net/httpよりも簡単に行えるなど、開発者にとっての使い勝手は向上しています -
注意点: 標準の
net/httpインターフェースとは互換性がないため、net/httpエコシステムのミドルウェアやライブラリを利用できません。また、ルーティングは自身で実装(switch文などで分岐)する必要があり、大規模化すると可読性が低下しやすいです -
利用シーン:
net/httpのみで構築した上で、さらなる高速化が必要な場合の候補として検討されます
func helloHandler(ctx *fasthttp.RequestCtx) {
// レスポンスのContent-TypeをJSONに設定
ctx.SetContentType("application/json")
// 1. クエリパラメータ:nameを取得
name := ctx.QueryArgs().Peek("name")
var response Response
if len(name) == 0 {
response = Response{
Status: "ok",
Message: "Hello, fasthttp API Server!",
}
} else {
response = Response{
Status: "ok",
Message: fmt.Sprintf("Hello, %s", string(name)),
}
}
// JSONにシリアライズ
jsonBytes, err := json.Marshal(response)
if err != nil {
ctx.Error("Internal Server Error", fasthttp.StatusInternalServerError)
return
}
// JSONにエンコードして書き込む
ctx.SetStatusCode(fasthttp.StatusOK)
ctx.Write(jsonBytes)
}
func helloPathHandler(ctx *fasthttp.RequestCtx) {
// 1. リクエストからパス全体を取得する
path := string(ctx.Path())
// 2. パスをスラッシュで分割する
// 例: "/hello/john" -> ["", "hello", "john"]
segments := strings.Split(path, "/")
// 3. パラメータ(ID)を抽出する
name := segments[2]
// 抽出したIDが空でないか、または追加でバリデーションを行う
if name == "" {
ctx.Error("name is missing", fasthttp.StatusBadRequest)
return
}
// JSONレスポンスを作成
response := Response{
Status: "ok",
Message: fmt.Sprintf("Hello, %s", string(name)),
}
// JSONにシリアライズ
jsonBytes, err := json.Marshal(response)
if err != nil {
ctx.Error("Internal Server Error", fasthttp.StatusInternalServerError)
return
}
// レスポンスのContent-TypeをJSONに設定
ctx.SetContentType("application/json")
// JSONにエンコードして書き込む
ctx.SetStatusCode(fasthttp.StatusOK)
ctx.Write(jsonBytes)
}
func main() {
requestHandler := func(ctx *fasthttp.RequestCtx) {
path := string(ctx.Path())
switch {
case path == "/hello":
helloHandler(ctx)
case strings.HasPrefix(path, "/hello/"):
helloPathHandler(ctx)
default:
ctx.Error("Unsupported path", fasthttp.StatusNotFound)
}
}
fasthttp.ListenAndServe(":8080", requestHandler)
}
gorilla/mux
-
特徴:
net/httpをベースに、高度なルーティング機能を追加するためのライブラリです - 利点: リクエストメソッドごとの分岐や、パスパラメータの取得を容易にします。外部ライブラリへの依存が少ないため、比較的扱いやすいです
-
注意点: Go 1.22以降、
net/httpのルーティング機能が強化されたため、ルーティング機能という点では以前ほどの圧倒的な優位性はありません。クエリパラメータの取得は依然としてnet/httpと同じ方法で行います
func HelloHandler(w http.ResponseWriter, r *http.Request) {
// レスポンスのContent-TypeをJSONに設定
w.Header().Set("Content-Type", "application/json")
// 200 OK を返します
w.WriteHeader(http.StatusOK)
// 1. クエリパラメータを取得
queryValues := r.URL.Query()
name := queryValues.Get("name")
if name == "" {
response := map[string]string{"message": "Hello, stdlib API Server!"}
json.NewEncoder(w).Encode(response)
} else {
response := map[string]string{"message": fmt.Sprintf("Hello, %s", name)}
json.NewEncoder(w).Encode(response)
}
}
func helloPathHandler(w http.ResponseWriter, r *http.Request) {
// レスポンスのContent-TypeをJSONに設定
w.Header().Set("Content-Type", "application/json")
// 200 OK を返します
w.WriteHeader(http.StatusOK)
// mux.Vars(r) で {name} の値を取得できる
name := mux.Vars(r)["name"]
// 抽出したnameが空でないか、または追加でバリデーションを行う
if name == "" {
http.Error(w, `{"error": "name is missing"}`, http.StatusBadRequest)
return
}
// JSONレスポンスを作成
response := map[string]string{"message": fmt.Sprintf("Hello, %s", name)}
// JSONにエンコードして書き込む
json.NewEncoder(w).Encode(response)
}
func main() {
// ルータを作成
r := mux.NewRouter()
r.HandleFunc("/hello", HelloHandler).Methods(http.MethodGet)
r.HandleFunc("/hello/{name}", helloPathHandler).Methods(http.MethodGet)
// ListenAndServe の第2引数に gorilla/mux のルータ r を渡す
if err := http.ListenAndServe(":8080", r); err != nil {
log.Fatal(err)
}
}
go-chi
-
特徴:
net/httpをベースに、軽量かつ高速なルーティングとミドルウェア機能を提供するライブラリです。Web API用に特化しており、サイズが小さく依存性も少ないです - 利点: 軽量で高速に動作し、ミドルウェアのグループ化やサブルーティングといった機能が優れており、構成の整理に役立ちます
- 利用価値: Go 1.22以降も、軽量性とミドルウェア/グループ化機能の利点から、引き続き利用価値は高いです
func helloHandler(w http.ResponseWriter, r *http.Request) {
// レスポンスのContent-TypeをJSONに設定
w.Header().Set("Content-Type", "application/json")
// 1. クエリパラメータのマップを取得
queryValues := r.URL.Query()
// 2. 特定のパラメータを取得 (Get() はキーが存在しない場合、空文字列 "" を返します)
name := queryValues.Get("name")
// 3. 取得した値を利用してJSONレスポンスを作成
var response map[string]string
if name == "" {
response = map[string]string{"message": "Hello, stdlib API Server!"}
}else {
response = map[string]string{"message": fmt.Sprintf("Hello, %s", name)}
}
// JSONにエンコードして書き込む
json.NewEncoder(w).Encode(response)
// 200 OK を返します
w.WriteHeader(http.StatusOK)
}
func helloPathHandler(w http.ResponseWriter, r *http.Request) {
// レスポンスのContent-TypeをJSONに設定
w.Header().Set("Content-Type", "application/json")
// 1. リクエストからパス全体を取得する
name := chi.URLParam(r, "name")
// 抽出したIDが空でないか、または追加でバリデーションを行う
if name == "" {
http.Error(w, `{"error": "name is missing"}`, http.StatusBadRequest)
return
}
// JSONレスポンスを作成
response := map[string]string{"message": fmt.Sprintf("Hello, %s", name)}
// JSONにエンコードして書き込む
json.NewEncoder(w).Encode(response)
// 200 OK を返します
w.WriteHeader(http.StatusOK)
}
func main() {
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Route("/hello", func(r chi.Router) {
r.Get("/", helloHandler)
r.Get("/{name}", helloPathHandler)
})
// サーバーを起動
http.ListenAndServe(":8080", r) // rはhttp.Handlerインターフェースを満たす
}
echo
- 特徴: 高性能/軽量性を謳うWeb APIフレームワーク
-
利点: 豊富な組み込みミドルウェア、リクエストのバインド機能(JSON/フォームから構造体への変換)、レスポンス作成の簡潔化、そしてエラーハンドリング機能が充実しています。全てのHTTPハンドラが
errorを返すという思想で統一されています - 評価: 開発に必要な機能が過不足なく揃っており、非常に人気のある選択肢の一つです
// リクエストを処理するハンドラ関数
func helloHandler(c echo.Context) error {
// 1. クエリパラメータの取得
name := c.QueryParam("name")
// 取得したデータをJSON形式でレスポンスとして返す
if name == "" {
return c.String(http.StatusOK, "Hello, echo api server!")
} else {
return c.String(http.StatusOK, fmt.Sprintf("Hello, %s", name))
}
}
func helloPathHandler(c echo.Context) error {
// 1. パスパラメータの取得
name := c.Param("name")
// 抽出したIDが空でないか、または追加でバリデーションを行う
if name == "" {
return echo.NewHTTPError(http.StatusBadRequest, "name is missing")
}
// 取得したデータをJSON形式でレスポンスとして返す
return c.JSON(http.StatusOK, echo.Map{
"message": fmt.Sprintf("Hello, %s", name),
})
}
func main() {
// Echoインスタンスを作成
e := echo.New()
// ロガーミドルウェア: すべてのリクエスト/レスポンス情報をログに出力します。
e.Use(middleware.Logger())
e.Logger.SetLevel(log.DEBUG)
// リカバリーミドルウェア: パニックが発生した場合にアプリケーションをクラッシュから保護します。
e.Use(middleware.Recover())
// HTTP GETリクエストに対するハンドラを定義
helloGroup := e.Group("/hello")
{
helloGroup.GET("/", helloHandler)
helloGroup.GET("/:name", helloPathHandler)
}
// サーバーをポート8080で起動
e.Logger.Fatal(e.Start(":8080"))
}
fiber
-
特徴:
fasthttpをベースに実装された超高速なWeb APIフレームワークです -
設計思想: Express(Node.jsのフレームワーク)の設計思想に近く、
.でメソッドを連続して呼び出すメソッドチェーンの書き方が多く使用されます -
利点:
fasthttpの性能を享受しつつ、フレームワークとしての使いやすさを提供します。フロントエンド開発経験が多いエンジニアにとっては直感的に書きやすいかもしれません -
注意点:
fasthttpベースのため、Go標準ライブラリのエコシステム(ミドルウェアなど)との互換性はありません。独自の型や実装が多いため、習得に時間がかかる可能性場合があります
func handler(c *fiber.Ctx) error {
name := c.Query("name")
// 取得したデータをJSON形式でレスポンスとして返す
if name == "" {
return c.JSON(fiber.Map{
"message": "Hello, fiber api server!",
})
} else {
return c.JSON(fiber.Map{
"message": fmt.Sprintf("Hello, %s", name),
})
}
}
func helloPathHandler(c *fiber.Ctx) error {
// 1. パスパラメータの取得
name := c.Params("name")
// 抽出したIDが空でないか、または追加でバリデーションを行う
if name == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "name is missing",
})
}
// 取得したパラメータを使ってJSONを返す
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"message": fmt.Sprintf("Hello, %s", name),
})
}
func main() {
// Fiberアプリの新しいインスタンスを作成
app := fiber.New()
app.Use(logger.New(logger.Config{
Format: "[${ip}]:${port} ${status} - ${method} ${path} | ${body}\n",
}))
// --- ルーティングの定義 ---
// 1. 基本的なテキストレスポンス (GET /)
helloGroup := app.Group("/hello")
helloGroup.Get("/", handler)
helloGroup.Get("/:name", helloPathHandler)
// app.Listen()はエラーを返すので、log.Fatalでエラー処理を行う
log.Fatal(app.Listen(":8080"))
}
Gin
-
特徴: GoでWeb APIを作成する際のデファクトスタンダードといえる存在。
net/httpをベースに作成されています - 利点: ルーティングやJSONバインディングなどの機能が最適化されており、必要な機能はほとんど備わっています。標準でロギングなどの便利な機能が有効になっており、すぐに開発を始めやすいです
- 評価: 「とりあえずGoでWeb APIを作るならこれ」と選ばれることが多く、情報量も豊富なため、初心者から経験者まで幅広くおすすめできるフレームワークです
func handler(c *gin.Context) {
name := c.Query("name")
// 取得したデータをJSON形式でレスポンスとして返す
if name == "" {
c.JSON(http.StatusOK, gin.H{
"message": "Hello, gin api server!",
})
} else {
c.JSON(http.StatusOK, gin.H{
"message": fmt.Sprintf("Hello, %s", name),
})
}
}
func helloPathHandler(c *gin.Context) {
// 1. パスパラメータの取得
name := c.Param("name")
// 抽出したIDが空でないか、または追加でバリデーションを行う
if name == "" {
c.JSON(http.StatusBadRequest, gin.H{
"message": "name is missing",
})
}
// 取得したデータをJSON形式でレスポンスとして返す
c.JSON(http.StatusOK, gin.H{
"message": fmt.Sprintf("Hello, %s", name),
})
}
// メイン関数
func main() {
// 1. Ginルーターを作成
router := gin.Default()
// 2. ルート(エンドポイント)の定義
// GET /ping のルート定義
// リクエストを受け取るとJSONで {"message": "pong"} を返す
helloGroup := router.Group("/hello")
{
helloGroup.GET("/", handler)
helloGroup.GET("/:name", helloPathHandler)
}
// 3. サーバーの起動
// ポート8080でリクエストの待ち受けを開始します
router.Run(":8080")
}
結論
フレームワークを使用してWeb APIを作成する場合、基本的な書き方に大きな違いはないため、必要な機能があるかや開発コミュニティの規模/情報量を基準に選んでも問題ないでしょう。
特に、標準ライブラリのnet/httpだけでも十分にWeb APIを作成できるという点は、Go言語の大きな魅力であり、シンプルさと堅牢性が好まれている理由だと思います。