Fiber - Golang learning step 5-3

  • 公開日
  • カテゴリ:WebFrameworks
  • タグ:Golang,roadmap.sh,学習メモ
Fiber - Golang learning step 5-3

roadmap.sh > Go > Web Frameworks > Fiber の学習を進めていきます。

※ 学習メモとしての記録ですが、後にこのセクションを学ぶ道しるべとなるよう、ですます調で記載しています。

contents

  1. 開発環境
  2. 参考 URL
  3. Fiber
  4. Fiber の特徴
  5. 開発環境のセットアップ
  6. Fiber の基本構造
  7. ルーティング
    1. 動的パラメータ
    2. クエリパラメータ
    3. HTTP メソッド
    4. グループルーティング
  8. ミドルウェア
    1. ミドルウェアの基本構造
    2. ミドルウェアの適用方法
  9. エラーハンドリング
    1. 基本的なエラーハンドリング
    2. カスタムエラーハンドラー
  10. テスト
    1. 必要なパッケージのインストール
    2. テストの実行

開発環境

  • チップ: Apple M2 Pro
  • OS: macOS Sonoma
  • go version: go1.23.2 darwin/arm64

参考 URL

Fiber

Fiber は、Go で非常に人気のある軽量で高速な Web フレームワークです。初心者にとっても学びやすく、Node.js の Express に触発されて設計されているため、Web 開発の経験がある人にも馴染みやすいのが特徴です。

Fiber の特徴

  1. 高速パフォーマンス
    • Fiber は、Go の標準 HTTP パッケージではなく、より高速な Fasthttp の上に構築されており、ゼロメモリアロケーションや最適化されたルーティングを提供します。そのため、パフォーマンスが非常に高く、リアルタイムアプリケーションや高負荷な環境でも利用されています。
  2. シンプルで直感的な API
    • Express.js にインスパイアされた API を採用しており、ルーティングやミドルウェアの設計が直感的でわかりやすいです。初心者でもすぐに使い始められます。
  3. リッチなエコシステム
    • Fiber は公式で多数のミドルウェアを提供しており、認証、CORS、ログ記録、エラーハンドリングなどを簡単に追加できます。
  4. 柔軟性
    • 必要に応じて、Fiber を使いながら Go の標準ライブラリや他のライブラリを組み合わせることが可能です。

開発環境のセットアップ

  1. プロジェクトの作成
mkdir myapp
cd myapp
go mod init myapp
  1. Fiber のインストール
go get -u github.com/gofiber/fiber/v2

Fiber の基本構造

Fiber アプリケーションは次のような構造で記述します。下記コードをプロジェクトルートに main.go として保存します。

package main

import (
  "github.com/gofiber/fiber/v2"
)

func main() {
  // アプリケーションの初期化
  app := fiber.New()

  // ルートハンドラーの定義
  app.Get("/", func(c *fiber.Ctx) error {
    return c.SendString("Hello, World!")
  })

  // サーバーの起動
  err := app.Listen(":3000")
  if err != nil {
    return
  }
}

以下のコマンドでサーバーを起動します。

go run main.go

ブラウザで http://localhost:3000 にアクセスすると、「Hello, World!」が表示されます。

ルーティング

Fiber では、直感的なルーティングを設定できます。動的パラメータ、クエリパラメータ、HTTP メソッド、グループルーティングなど、さまざまなルーティング方法をサポートしています。

動的パラメータ

Fiber では、URL の一部をパラメータとして利用できます。

app.Get("/user/:id", func(c *fiber.Ctx) error {
    id := c.Params("id") // 動的パラメータを取得
    return c.JSON(fiber.Map{
        "user_id": id,
    })
})

URL /user/123 にアクセスすると、次の JSON が返されます・

{
  "user_id": "123"
}

クエリパラメータ

クエリパラメータを取得して処理するのも簡単です。

app.Get("/search", func(c *fiber.Ctx) error {
    keyword := c.Query("keyword") // クエリパラメータを取得
    return c.JSON(fiber.Map{
        "search": keyword,
    })
})

URL /search?keyword=golang にアクセスすると、次の JSON が返されます。

{
   "search": "golang"
}

HTTP メソッド

Fiber では GETPOSTPUTDELETE など、すべての主要な HTTP メソッドに対応しています。

以下は、POST リクエストで JSON データを受け取り、そのままレスポンスとして返す例です。

app.Post("/user", func(c *fiber.Ctx) error {
    // リクエストボディをパース
    var user struct {
        Name string `json:"name"`
        Age  int    `json:"age"`
    }
    if err := c.BodyParser(&user); err != nil {
        return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
            "error": "Invalid request body",
        })
    }
    return c.JSON(fiber.Map{
        "id":   1,
        "name": user.Name,
        "age":  user.Age,
    })
})

ターミナルから curl コマンドで POST リクエストを送信します。

curl -X POST http://localhost:3000/user \
  -H "Content-Type: application/json" \
  -d '{"name": "John Doe", "age": 30}'
{
   "id": 1,
   "name": "John Doe",
   "age": 30
}

グループルーティング

Fiber では、関連するルートをグループ化することで管理しやすくできます。

api := app.Group("/api")
api.Get("/users", func(c *fiber.Ctx) error {
    return c.JSON(fiber.Map{"message": "User list"})
})
api.Get("/users/:id", func(c *fiber.Ctx) error {
    id := c.Params("id")
    return c.JSON(fiber.Map{"message": "User details for " + id})
})

URL /api/users にアクセスすると、次の JSON が返されます。

{
   "message": "User list"
}

URL /api/users/123 にアクセスすると、次の JSON が返されます。

{
   "message": "User details for 123"
}

ミドルウェア

Fiber でも、リクエストとレスポンスの間に共通の処理を挟む「ミドルウェア」を簡単に定義して使用できます。Fiber のミドルウェアは、以下のような用途でよく使われます。

  • リクエストログの記録
  • 認証や権限チェック
  • エラーハンドリング
  • リクエストやレスポンスのデータ加工

ミドルウェアの基本構造

Fiber のミドルウェアは次のような形で定義します。

func requestTimingMiddleware(c *fiber.Ctx) error {
    start := time.Now() // リクエストの開始時間を記録

    // 次のミドルウェアまたはハンドラーを実行
    err := c.Next()

    // リクエストの終了時間を記録し、処理時間を計算
    duration := time.Since(start)
    fmt.Printf("Path: %s | Method: %s | Duration: %s\n", c.Path(), c.Method(), duration)

    return err
}

ミドルウェアの適用方法

1. 全体に適用する

Fiber では、Use メソッドを使ってグローバルにミドルウェアを適用します。以下は、すべてのリクエストにカスタムミドルウェアを適用する例です。

app := fiber.New()

// グローバルミドルウェアを登録
app.Use(requestTimingMiddleware)

app.Get("/hello", func(c *fiber.Ctx) error {
    return c.SendString("Hello, World!")
})

app.Listen(":3000")

/hello にアクセスすると、ターミナルに次のようなログが出力されます。

Path: /hello | Method: GET | Duration: 28.375µs

2. 特定のルートだけに適用する

Fiber では、特定のルートに直接ミドルウェアを適用することも可能です。

app.Get("/special", requestTimingMiddleware, func(c *fiber.Ctx) error {
    return c.SendString("Special route!")
})
  • /special にアクセスすると、リクエスト処理時間が記録されます。
  • /hello や他のルートには影響しません。

3. グループに適用する

Fiber では、関連するルートをグループ化し、グループ全体にミドルウェアを適用することができます。

api := app.Group("/api")

// グループにミドルウェアを適用
api.Use(requestTimingMiddleware)

api.Get("/users", func(c *fiber.Ctx) error {
    return c.JSON(fiber.Map{"message": "User list"})
})
api.Get("/users/:id", func(c *fiber.Ctx) error {
    id := c.Params("id")
    return c.JSON(fiber.Map{"message": "User details for " + id})
})
  • /api/users/api/users/123 にアクセスすると、ターミナルに処理時間が記録されます。
  • /hello や他のルートには影響しません。
Path: /api/users | Method: GET | Duration: 91.123µs
Path: /api/users/:id | Method: GET | Duration: 34.583µs

エラーハンドリング

Fiber では、エラーハンドリングを簡単に実装できます。リクエストの処理中に発生したエラーをキャッチして、適切なステータスコードやエラーメッセージをレスポンスに設定できます。また、カスタムエラーハンドラーを使用して、全体的なエラー処理を統一することも可能です。

基本的なエラーハンドリング

Fiber の Ctx を使って、エラーレスポンスを簡単に返せます。

例: JSON パースとエラーチェック

app.Post("/login", func(c *fiber.Ctx) error {
    var req struct {
        Username string `json:"username"`
        Password string `json:"password"`
    }

    // JSON データのパースとエラーチェック
    if err := c.BodyParser(&req); err != nil {
        return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
            "error": "Invalid JSON payload",
        })
    }

    // 必須フィールドのチェック
    if req.Username == "" || req.Password == "" {
        return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
            "error": "Username and password are required",
        })
    }

    // 成功レスポンス
    return c.JSON(fiber.Map{
        "status": "logged in",
    })
})

curl コマンドで正常データを送信します。

curl -X POST http://localhost:3000/login \
  -H "Content-Type: application/json" \
  -d '{"username": "testuser", "password": "1234"}'

成功レスポンスが返ります。

{
   "status": "logged in"
}

不正な JSON を送信した場合

curl -X POST http://localhost:3000/login \
  -H "Content-Type: application/json" \
  -d 'invalid json'

失敗レスポンスが返ります。

{
   "error": "Invalid JSON payload"
}

カスタムエラーハンドラー

Fiber では、グローバルなエラーハンドリングを設定することができます。以下は、発生したエラーに基づいてカスタムレスポンスを返す例です。

カスタムエラーハンドラーの設定

app := fiber.New(fiber.Config{
    // カスタムエラーハンドラーを設定
    ErrorHandler: func(c *fiber.Ctx, err error) error {
        // ステータスコードを取得 (デフォルトは 500)
        code := fiber.StatusInternalServerError
        if e, ok := err.(*fiber.Error); ok {
            code = e.Code
        }

        // エラーの内容をログに出力
        fmt.Printf("Error occurred: code=%d, message=%v\n", code, err.Error())

        // エラーレスポンスを返す
        return c.Status(code).JSON(fiber.Map{
            "error": "An error occurred",
        })
    },
})

// 正常なルート
app.Get("/", func(c *fiber.Ctx) error {
    return c.SendString("Hello, World!")
})

// エラーを強制発生させるルート
app.Get("/error", func(c *fiber.Ctx) error {
    return fiber.NewError(fiber.StatusBadRequest, "Bad Request Example")
})

app.Listen(":3000")

正常なリクエスト

curl http://localhost:3000/

# レスポンス
Hello, World!

エラーを発生させるリクエスト

curl http://localhost:3000/error

レスポンス

{
   "error": "An error occurred"
}

ターミナルのログ

Error occurred: code=400, message=Bad Request Example

テスト

Go の標準テストパッケージ (testing) と httptest パッケージを使えば、Fiber のルートやミドルウェアの動作をテストできます。

以下に、カスタムエラーハンドラーとルート /error をテストする例を示します。

サンプルコード(main.go

package main

import (
    "fmt"
    "net/http"

    "github.com/gofiber/fiber/v2"
)

func NewServer() *fiber.App {
    app := fiber.New(fiber.Config{
        // カスタムエラーハンドラー
        ErrorHandler: func(c *fiber.Ctx, err error) error {
            code := fiber.StatusInternalServerError
            if e, ok := err.(*fiber.Error); ok {
                code = e.Code
            }
            // エラーレスポンスを記録
            fmt.Printf("Error occurred: code=%d, message=%s\n", code, err.Error())
            return c.Status(code).JSON(fiber.Map{"error": err.Error()})
        },
    })

    // 通常ルート
    app.Get("/", func(c *fiber.Ctx) error {
        return c.SendString("Hello, World!")
    })

    // エラーを発生させるルート
    app.Get("/error", func(c *fiber.Ctx) error {
        return fiber.NewError(fiber.StatusBadRequest, "Bad Request Example")
    })

    return app
}

func main() {
    app := NewServer()
    app.Listen(":3000")
}

テストコード(main_test.go

NewServer 関数を使ってサーバーインスタンスを作成し、テストを実行します。

package main

import (
    "io"
    "net/http"
    "net/http/httptest"
    "strings"
    "testing"

    "github.com/stretchr/testify/assert"
)

func TestHelloWorld(t *testing.T) {
    app := NewServer()

    req := httptest.NewRequest(http.MethodGet, "/", nil)
    resp, _ := app.Test(req)

    assert.Equal(t, http.StatusOK, resp.StatusCode)

    // レスポンスボディの内容を読み取る
    body := new(strings.Builder)
    _, _ = io.Copy(body, resp.Body)
    assert.Equal(t, "Hello, World!", body.String())
}

func TestErrorRoute(t *testing.T) {
    app := NewServer()

    req := httptest.NewRequest(http.MethodGet, "/error", nil)
    resp, _ := app.Test(req)

    assert.Equal(t, http.StatusBadRequest, resp.StatusCode)

    // レスポンスボディの内容を読み取る
    body := new(strings.Builder)
    _, _ = io.Copy(body, resp.Body)
    assert.JSONEq(t, `{"error": "Bad Request Example"}`, body.String())
}

func TestNotFoundRoute(t *testing.T) {
    app := NewServer()

    req := httptest.NewRequest(http.MethodGet, "/notfound", nil)
    resp, _ := app.Test(req)

    assert.Equal(t, http.StatusNotFound, resp.StatusCode)

    // レスポンスボディの内容を読み取る
    body := new(strings.Builder)
    _, _ = io.Copy(body, resp.Body)
    assert.JSONEq(t, `{"error": "Cannot GET /notfound"}`, body.String())
}

必要なパッケージのインストール

テストに必要な testify パッケージをインストールします。

go get github.com/stretchr/testify
go mod tidy

テストの実行

以下のコマンドでテストを実行します。

go test -v

実行結果

=== RUN   TestHelloWorld
--- PASS: TestHelloWorld (0.00s)
=== RUN   TestErrorRoute
Error occurred: code=400, message=Bad Request Example
--- PASS: TestErrorRoute (0.00s)
=== RUN   TestNotFoundRoute
Error occurred: code=404, message=Cannot GET /notfound
--- PASS: TestNotFoundRoute (0.00s)
PASS
ok      myapp   0.266s

まとめ

  • Fiber は Go で高速かつ軽量な Web フレームワーク
  • Fasthttp 上に構築され、高パフォーマンスを実現
  • シンプルで直感的な API により初心者にも学びやすい設計
  • 公式の豊富なミドルウェアで柔軟な機能拡張が可能
  • 基本構造は Express.js に似たシンプルなコードで記述可能
  • ルーティング、ミドルウェア、エラーハンドリングなどの機能が充実
  • Go の標準テストパッケージで簡単にテスト可能


[Next] Step 5-4: Gorilla

[Prev] Step 5-2: Echo

Author

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級