Echo
高性能でミニマルなGo Webフレームワーク。より多くの組み込み機能を提供し、大規模アプリケーションに適している。
GitHub概要
labstack/echo
High performance, minimalist Go web framework
スター31,267
ウォッチ530
フォーク2,276
作成日:2015年3月1日
言語:Go
ライセンス:MIT License
トピックス
echogohttp2httpslabstack-echoletsencryptmicro-frameworkmicroservicemiddlewaresslwebweb-frameworkwebsocket
スター履歴
データ取得日時: 2025/7/16 08:43
フレームワーク
Echo
概要
Echoは、Go言語で書かれた高性能でミニマリストなWebフレームワークです。RESTful APIとWebアプリケーションの構築に特化しています。
詳細
Echo(エコー)は2015年にLabStack社によって開発された、シンプルさとパフォーマンスを重視したGoのWebフレームワークです。最適化されたHTTPルーター、豊富なミドルウェアエコシステム、そして直感的なAPIにより、開発者が高性能なWebアプリケーションを迅速に構築できるよう設計されています。ツリーベースのルーティングアルゴリズムを採用し、効率的なルートマッチングを実現しています。ミドルウェアシステムは階層化されており、グローバル、グループ、ルートレベルでの適用が可能です。JSON、XML、フォームデータの自動バインディング、集中型エラーハンドリング、HTTP/2サポート、Let's Encryptによる自動TLS機能を標準搭載しています。軽量でありながら企業レベルの機能を提供し、マイクロサービスアーキテクチャやAPIファーストな開発に最適です。
メリット・デメリット
メリット
- 高いパフォーマンス: 最適化されたルーターと軽量設計による高速レスポンス
- シンプルな学習コーブ: 直感的なAPIと分かりやすいドキュメント
- 豊富なミドルウェア: ロギング、認証、CORS、圧縮など標準搭載
- 柔軟なルーティング: パラメータ、ワイルドカード、グループ化対応
- 自動バインディング: JSONやフォームデータの自動変換機能
- HTTP/2対応: モダンなHTTPプロトコルサポート
- コミュニティ活発: アクティブな開発とコミュニティサポート
デメリット
- 機能の制約: フルスタックフレームワークに比べて機能が限定的
- Go言語依存: Go特有の学習コストと開発環境の制約
- ORM非搭載: データベース操作は別途ライブラリが必要
- テンプレート制限: 組み込みテンプレートエンジンの機能が基本的
- エコシステム: フレームワーク固有のツール群が比較的少ない
主要リンク
書き方の例
Hello World
package main
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
// Echoインスタンスを作成
e := echo.New()
// ミドルウェア
e.Use(middleware.Logger())
e.Use(middleware.Recover())
// ルート
e.GET("/", hello)
// サーバー起動
e.Logger.Fatal(e.Start(":1323"))
}
// ハンドラー関数
func hello(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
}
ルーティングとパラメータ
package main
import (
"net/http"
"github.com/labstack/echo/v4"
)
func main() {
e := echo.New()
// 基本ルート
e.GET("/", home)
e.POST("/users", createUser)
e.GET("/users/:id", getUser)
e.PUT("/users/:id", updateUser)
e.DELETE("/users/:id", deleteUser)
// ワイルドカードルート
e.GET("/files/*", getFile)
// クエリパラメータ
e.GET("/search", search)
e.Logger.Fatal(e.Start(":1323"))
}
func home(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{
"message": "Welcome to Echo API",
})
}
func getUser(c echo.Context) error {
// パスパラメータの取得
id := c.Param("id")
return c.JSON(http.StatusOK, map[string]string{
"id": id,
"name": "ユーザー " + id,
})
}
func search(c echo.Context) error {
// クエリパラメータの取得
query := c.QueryParam("q")
page := c.QueryParam("page")
if query == "" {
return c.JSON(http.StatusBadRequest, map[string]string{
"error": "検索クエリが必要です",
})
}
return c.JSON(http.StatusOK, map[string]interface{}{
"query": query,
"page": page,
"results": []string{"結果1", "結果2", "結果3"},
})
}
func getFile(c echo.Context) error {
// ワイルドカードパラメータの取得
filepath := c.Param("*")
return c.JSON(http.StatusOK, map[string]string{
"filepath": filepath,
})
}
JSONデータバインディングとバリデーション
package main
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/go-playground/validator/v10"
)
type User struct {
ID int `json:"id"`
Name string `json:"name" validate:"required,min=2,max=50"`
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"min=0,max=120"`
}
type CustomValidator struct {
validator *validator.Validate
}
func (cv *CustomValidator) Validate(i interface{}) error {
return cv.validator.Struct(i)
}
func main() {
e := echo.New()
// カスタムバリデーターを設定
e.Validator = &CustomValidator{validator: validator.New()}
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.POST("/users", createUser)
e.GET("/users/:id", getUser)
e.Logger.Fatal(e.Start(":1323"))
}
func createUser(c echo.Context) error {
user := new(User)
// JSONデータをバインド
if err := c.Bind(user); err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{
"error": "Invalid JSON format",
})
}
// バリデーション
if err := c.Validate(user); err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{
"error": err.Error(),
})
}
// ユーザー作成処理(実際のDBへの保存など)
user.ID = 123 // 仮のID
return c.JSON(http.StatusCreated, user)
}
func getUser(c echo.Context) error {
// 仮のユーザーデータ
user := User{
ID: 1,
Name: "山田太郎",
Email: "[email protected]",
Age: 30,
}
return c.JSON(http.StatusOK, user)
}
ミドルウェアの使用
package main
import (
"net/http"
"time"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
// グローバルミドルウェア
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Use(middleware.CORS())
// レート制限
e.Use(middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(20)))
// セキュアヘッダー
e.Use(middleware.Secure())
// Gzip圧縮
e.Use(middleware.Gzip())
// リクエストタイムアウト
e.Use(middleware.TimeoutWithConfig(middleware.TimeoutConfig{
Timeout: 30 * time.Second,
}))
// パブリックルート
e.GET("/", publicHandler)
e.POST("/register", registerHandler)
// 認証が必要なAPIグループ
api := e.Group("/api")
api.Use(middleware.BasicAuth(func(username, password string, c echo.Context) (bool, error) {
if username == "admin" && password == "secret" {
return true, nil
}
return false, nil
}))
api.GET("/users", listUsers)
api.POST("/users", createUser)
// カスタムミドルウェア
e.Use(customLogger)
e.Logger.Fatal(e.Start(":1323"))
}
func customLogger(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
start := time.Now()
// 次のハンドラーを実行
err := next(c)
// ログ出力
c.Logger().Infof("Method: %s, URI: %s, Status: %d, Latency: %v",
c.Request().Method,
c.Request().RequestURI,
c.Response().Status,
time.Since(start),
)
return err
}
}
func publicHandler(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{
"message": "パブリックエンドポイント",
})
}
func registerHandler(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{
"message": "ユーザー登録",
})
}
func listUsers(c echo.Context) error {
return c.JSON(http.StatusOK, []map[string]interface{}{
{"id": 1, "name": "ユーザー1"},
{"id": 2, "name": "ユーザー2"},
})
}
エラーハンドリング
package main
import (
"errors"
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
type HTTPError struct {
Code int `json:"code"`
Message string `json:"message"`
}
func main() {
e := echo.New()
// カスタムエラーハンドラー
e.HTTPErrorHandler = customHTTPErrorHandler
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.GET("/", successHandler)
e.GET("/error", errorHandler)
e.GET("/panic", panicHandler)
e.GET("/users/:id", getUserWithError)
e.Logger.Fatal(e.Start(":1323"))
}
func customHTTPErrorHandler(err error, c echo.Context) {
var code int
var message string
if he, ok := err.(*echo.HTTPError); ok {
code = he.Code
message = he.Message.(string)
} else {
code = http.StatusInternalServerError
message = "Internal Server Error"
}
// エラーログ出力
c.Logger().Error(err)
// JSONエラーレスポンス
if !c.Response().Committed {
if c.Request().Method == http.MethodHead {
err = c.NoContent(code)
} else {
err = c.JSON(code, HTTPError{
Code: code,
Message: message,
})
}
if err != nil {
c.Logger().Error(err)
}
}
}
func successHandler(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{
"message": "成功",
})
}
func errorHandler(c echo.Context) error {
return echo.NewHTTPError(http.StatusBadRequest, "カスタムエラーメッセージ")
}
func panicHandler(c echo.Context) error {
panic("パニックが発生しました")
}
func getUserWithError(c echo.Context) error {
id := c.Param("id")
// ビジネスロジックエラー
if id == "999" {
return echo.NewHTTPError(http.StatusNotFound, "ユーザーが見つかりません")
}
// システムエラー
if id == "500" {
return errors.New("データベース接続エラー")
}
return c.JSON(http.StatusOK, map[string]string{
"id": id,
"name": "ユーザー " + id,
})
}
ファイルアップロードと静的ファイル配信
package main
import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
// 静的ファイル配信
e.Static("/static", "assets")
// ファイルアップロード
e.POST("/upload", uploadFile)
e.POST("/upload/multiple", uploadMultipleFiles)
// ファイルダウンロード
e.GET("/download/:filename", downloadFile)
e.Logger.Fatal(e.Start(":1323"))
}
func uploadFile(c echo.Context) error {
// フォームからファイルを取得
file, err := c.FormFile("file")
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "ファイルの取得に失敗しました")
}
// ファイルサイズチェック(10MB制限)
if file.Size > 10<<20 {
return echo.NewHTTPError(http.StatusBadRequest, "ファイルサイズが大きすぎます(最大10MB)")
}
// ファイルを開く
src, err := file.Open()
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "ファイルの読み込みに失敗しました")
}
defer src.Close()
// アップロードディレクトリが存在しない場合は作成
uploadDir := "uploads"
if _, err := os.Stat(uploadDir); os.IsNotExist(err) {
err = os.Mkdir(uploadDir, 0755)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "アップロードディレクトリの作成に失敗しました")
}
}
// 保存先ファイルを作成
dst, err := os.Create(filepath.Join(uploadDir, file.Filename))
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "ファイルの作成に失敗しました")
}
defer dst.Close()
// ファイルをコピー
if _, err = io.Copy(dst, src); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "ファイルの保存に失敗しました")
}
return c.JSON(http.StatusOK, map[string]interface{}{
"message": "ファイルアップロード成功",
"filename": file.Filename,
"size": file.Size,
})
}
func uploadMultipleFiles(c echo.Context) error {
// マルチパートフォームを解析
form, err := c.MultipartForm()
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "マルチパートフォームの解析に失敗しました")
}
files := form.File["files"]
var uploadedFiles []string
for _, file := range files {
// ファイルサイズチェック
if file.Size > 10<<20 {
continue // 大きすぎるファイルはスキップ
}
src, err := file.Open()
if err != nil {
continue
}
dst, err := os.Create(filepath.Join("uploads", file.Filename))
if err != nil {
src.Close()
continue
}
if _, err = io.Copy(dst, src); err != nil {
src.Close()
dst.Close()
continue
}
src.Close()
dst.Close()
uploadedFiles = append(uploadedFiles, file.Filename)
}
return c.JSON(http.StatusOK, map[string]interface{}{
"message": "ファイルアップロード完了",
"uploaded_files": uploadedFiles,
"count": len(uploadedFiles),
})
}
func downloadFile(c echo.Context) error {
filename := c.Param("filename")
filePath := filepath.Join("uploads", filename)
// ファイルの存在チェック
if _, err := os.Stat(filePath); os.IsNotExist(err) {
return echo.NewHTTPError(http.StatusNotFound, "ファイルが見つかりません")
}
// Content-Dispositionヘッダーを設定してダウンロードを促す
c.Response().Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
return c.File(filePath)
}