Echo - Golang learning step 5-2

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

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

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

contents

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

開発環境

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

参考 URL

Echo

「Echo」は、Go 言語で作られた高速で柔軟な Web フレームワークです。簡潔な構造とパフォーマンスの良さが特徴で、API 開発や小規模から大規模な Web アプリケーションまで幅広く利用されています。以下では、Echo の基本的な特徴と使い方を初学者向けに解説します。

Echo の特徴

  1. 高速なパフォーマンス
    • Echo は軽量設計で、Go の効率的な HTTP サーバー機能を活かして高速に動作します。
  2. 簡潔なコード
    • シンプルで直感的な構造を持つため、初心者でもコードをすっきり書くことができます。
  3. 柔軟なミドルウェア
    • ログ記録やリクエストの検証、エラーハンドリングといった機能をミドルウェアとして簡単に追加できます。
  4. JSON の扱いが簡単
    • API 開発に便利な JSON サポートが充実しています。
  5. 豊富な機能
    • 静的ファイルの提供、ルーティング、グループ化、ミドルウェア、テンプレートエンジンのサポートなど。

開発環境のセットアップ

  1. プロジェクトの作成
mkdir myapp && cd myapp
go mod init myapp
  1. Echo のインストール
go get -u github.com/labstack/echo/v4

Echo の基本構造

Echo アプリケーションは次のような構造で記述します。

package main

import (
  "net/http"
  "github.com/labstack/echo/v4"
)

func main() {
  // Echo のインスタンスを作成
  e := echo.New()

  // ルートにハンドラーを登録
  e.GET("/", func(c echo.Context) error {
    return c.String(http.StatusOK, "Hello, Echo!")
  })

  // サーバーを起動
  e.Logger.Fatal(e.Start(":8080"))
}
  1. インスタンスの作成
    • e := echo.New() で Echo アプリケーションのインスタンスを作成します。
  2. ルーティング
    • e.GET は HTTP メソッド(ここでは GET リクエスト)とパス、ハンドラーを登録します。 ハンドラーはリクエストに対してどのように応答するかを定義します。
  3. サーバーの起動
    • e.Start(":8080") でポート番号 8080 を指定してサーバーを起動します。

基本的な使い方

以下は、シンプルな JSON レスポンスを返す API の例です。プロジェクトルートに main.go を作成し、以下を実装します。

package main

import (
  "net/http"
  "github.com/labstack/echo/v4"
)

type User struct {
  ID   int    `json:"id"`
  Name string `json:"name"`
}

func main() {
  e := echo.New()

  // JSON を返すハンドラー
  e.GET("/user", func(c echo.Context) error {
    user := User{ID: 1, Name: "John Doe"}
    return c.JSON(http.StatusOK, user)
  })

  e.Logger.Fatal(e.Start(":8080"))
}
  • c.JSON を使うと、構造体を簡単に JSON に変換してレスポンスとして返せます。
  • JSON を使った API は Web アプリケーションやモバイルアプリのバックエンドとして広く利用されています。

ターミナルで以下で実行し起動します。

go run main.go

ブラウザから http://localhost:8080/user にアクセスすると、以下の JSON レスポンスが表示されます。

{
  "id": 1,
  "name": "John Doe"
}

ルーティング

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

動的パラメータ

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

e.GET("/user/:id", func(c echo.Context) error {
  id := c.Param("id") // 動的パラメータを取得
  return c.JSON(http.StatusOK, map[string]string{
    "user_id": id,
  })
})

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

{
  "user_id": "123"
}

クエリパラメータ

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

e.GET("/search", func(c echo.Context) error {
  keyword := c.QueryParam("keyword") // クエリパラメータを取得
  return c.JSON(http.StatusOK, map[string]string{
    "search": keyword,
  })
})

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

{
  "search": "golang"
}

HTTP メソッド

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

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

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

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

レスポンスは以下になります。

{
  "id": 1,
  "name": "John Doe",
  "age": 30
}

グループルーティング

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

api := e.Group("/api")
api.GET("/users", func(c echo.Context) error {
  return c.JSON(http.StatusOK, map[string]string{"message": "User list"})
})
api.GET("/users/:id", func(c echo.Context) error {
  id := c.Param("id")
  return c.JSON(http.StatusOK, map[string]string{"message": "User details for " + id})
})

/api/users にアクセスすると、次のレスポンスが返されます:

{
  "message": "User list"
}

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

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

ミドルウェア

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

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

ミドルウェアの基本構造

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

func requestTimingMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
    start := time.Now() // リクエストの開始時間を記録

    // 次のハンドラやミドルウェアを実行
    err := next(c)

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

    return err
  }
}

ミドルウェアの適用方法

1. 全体に適用する

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

e := echo.New()

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

e.GET("/hello", func(c echo.Context) error {
    return c.String(http.StatusOK, "Hello, World!")
})

e.Logger.Fatal(e.Start(":8080"))

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

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

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

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

3. グループに適用する

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

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

api.GET("/users", func(c echo.Context) error {
    return c.JSON(http.StatusOK, map[string]string{"message": "User list"})
})
api.GET("/users/:id", func(c echo.Context) error {
    id := c.Param("id")
    return c.JSON(http.StatusOK, map[string]string{"message": "User details for " + id})
})
  • /api/users, /api/users/123 にアクセスすると、ログに処理時間が記録されます。
  • /hello など、グループ外のルートには影響しません。
2024/12/03 22:41:20 Path: /api/users | Method: GET | Duration: 94.333µs
2024/12/03 22:41:24 Path: /api/users/:id | Method: GET | Duration: 39.583µs

エラーハンドリング

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

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

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

例: JSON バインディングとエラーチェック

e.POST("/login", func(c echo.Context) error {
  var req struct {
    Username string `json:"username"`
    Password string `json:"password"`
  }

  // JSON データのバインディングとエラーチェック
  if err := c.Bind(&req); err != nil {
    return echo.NewHTTPError(http.StatusBadRequest, "Invalid JSON payload")
  }

  // 必須フィールドのチェック
  if req.Username == "" || req.Password == "" {
    return echo.NewHTTPError(http.StatusBadRequest, "Username and password are required")
  }

  // 成功レスポンス
  return c.JSON(http.StatusOK, map[string]string{"status": "logged in"})
})

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

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

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

{
  "status": "logged in"
}

送信する JSON が不正な場合でも送信します。

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

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

{
  "message": "Invalid JSON payload"
}

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

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

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

e := echo.New()

// カスタムエラーハンドラー
e.HTTPErrorHandler = func(err error, c echo.Context) {
    // エラーの内容をターミナルに出力
    if he, ok := err.(*echo.HTTPError); ok {
        log.Printf("Error occurred: code=%d, message=%v", he.Code, he.Message)
    } else {
        log.Printf("Unexpected error: %v", err)
    }

    // エラーレスポンスの設定
    c.JSON(http.StatusInternalServerError, map[string]string{"error": "An error occurred"})
}

// ルートの定義
e.GET("/", func(c echo.Context) error {
    return c.String(http.StatusOK, "Hello, World!")
})

// エラーを強制発生させるルート
e.GET("/error", func(c echo.Context) error {
    return echo.NewHTTPError(http.StatusBadRequest, "Bad Request Example")
})

e.Logger.Fatal(e.Start(":8080"))

正常なリクエスト

curl http://localhost:8080/

# レスポンス
Hello, World!

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

curl http://localhost:8080/error


# レスポンス
{
  "error": "An error occurred"
}

# ターミナルのログ
Error occurred: code=400, message=Bad Request Example

テスト

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

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

サンプルコード(main.go

package main

import (
  "log"
  "net/http"

  "github.com/labstack/echo/v4"
)

func NewServer() *echo.Echo {
  e := echo.New()

  // カスタムエラーハンドラー
  e.HTTPErrorHandler = func(err error, c echo.Context) {
    if he, ok := err.(*echo.HTTPError); ok {
      log.Printf("Error occurred: code=%d, message=%v", he.Code, he.Message)
      c.JSON(he.Code, map[string]string{"error": he.Message.(string)})
    } else {
      log.Printf("Unexpected error: %v", err)
      c.JSON(http.StatusInternalServerError, map[string]string{"error": "Internal Server Error"})
    }
  }

  // 通常ルート
  e.GET("/", func(c echo.Context) error {
    return c.String(http.StatusOK, "Hello, World!")
  })

  // エラーを発生させるルート
  e.GET("/error", func(c echo.Context) error {
    return echo.NewHTTPError(http.StatusBadRequest, "Bad Request Example")
  })

  return e
}

func main() {
  e := NewServer()
  e.Logger.Fatal(e.Start(":8080"))
}

テストコード(main_test.go

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

package main

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

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

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

  req := httptest.NewRequest(http.MethodGet, "/", nil)
  rec := httptest.NewRecorder()

  e.ServeHTTP(rec, req)

  assert.Equal(t, http.StatusOK, rec.Code)
  assert.Equal(t, "Hello, World!", strings.TrimSpace(rec.Body.String()))
}

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

  req := httptest.NewRequest(http.MethodGet, "/error", nil)
  rec := httptest.NewRecorder()

  e.ServeHTTP(rec, req)

  assert.Equal(t, http.StatusBadRequest, rec.Code)
  assert.JSONEq(t, `{"error": "Bad Request Example"}`, rec.Body.String())
}

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

  req := httptest.NewRequest(http.MethodGet, "/notfound", nil)
  rec := httptest.NewRecorder()

  e.ServeHTTP(rec, req)

  assert.Equal(t, http.StatusNotFound, rec.Code)
  assert.JSONEq(t, `{"error": "Not Found"}`, rec.Body.String())
}

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

# testifyパッケージのインストール
go get github.com/stretchr/testify

# 再度依存関係を解決
go mod tidy

テストを実行できます。

go test -v

=== RUN   TestHelloWorld
--- PASS: TestHelloWorld (0.00s)
=== RUN   TestErrorRoute
2024/12/03 23:04:22 Error occurred: code=400, message=Bad Request Example
--- PASS: TestErrorRoute (0.00s)
=== RUN   TestNotFoundRoute
2024/12/03 23:04:22 Error occurred: code=404, message=Not Found
--- PASS: TestNotFoundRoute (0.00s)
PASS
ok      myapp   0.338s

まとめ

  • Echo は Go 言語で構築された高速かつ軽量な Web フレームワーク
  • API 開発や小規模から大規模なアプリケーション開発に適用可能
  • シンプルで直感的なコード構造が初心者に優しい
  • JSON サポートや柔軟なミドルウェア機能が充実
  • 豊富なルーティングオプションを提供
  • エラーハンドリングやカスタムミドルウェアの実装が容易
  • Go 標準のテストパッケージでテストが可能


[Prev] Step 5-3: Fiber

[Prev] Step 5-1: Gin

Author

rito

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