Gin

最も人気のあるGo Webフレームワーク。高速でシンプルなAPI開発に最適化されており、Martini風のAPIを提供。

GoフレームワークWeb開発HTTP軽量高性能ルーター

GitHub概要

gin-gonic/gin

Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.

スター83,163
ウォッチ1,358
フォーク8,257
作成日:2014年6月16日
言語:Go
ライセンス:MIT License

トピックス

frameworkgingomiddlewareperformancerouterserver

スター履歴

gin-gonic/gin Star History
データ取得日時: 2025/7/17 05:31

フレームワーク

Gin

概要

GinはGo言語で書かれた軽量で高性能なHTTPウェブフレームワークです。MartiniライクなAPIを持ちながら、最大40倍高速な処理を実現しています。

詳細

Gin(ジン)は2014年にGo言語向けのHTTPウェブフレームワークとして開発されました。httprouterをベースにしたゼロアロケーションルーターを採用し、メモリ割り当てを最小限に抑えることで、極めて高いパフォーマンスを実現しています。Martiniの使いやすさを保ちながら、大幅な性能向上を達成したフレームワークです。シンプルなAPIデザインと豊富な組み込み機能により、RESTful APIやマイクロサービスの開発に最適化されています。JSON/XMLバインディング、自動バリデーション、ミドルウェアシステム、ルートグルーピング、テンプレートレンダリングなどの機能を標準搭載しています。小さなフットプリントでありながら、エンタープライズレベルの機能を提供し、特にAPI開発とマイクロサービスアーキテクチャに優れた性能を発揮します。gin-contribプロジェクトにより、豊富なミドルウェアエコシステムも利用できます。

メリット・デメリット

メリット

  • 極めて高いパフォーマンス: ゼロアロケーションルーターによる超高速処理
  • 軽量設計: 小さなフットプリントと最小限のメモリ使用量
  • シンプルなAPI: 直感的で分かりやすいMartiniライクなAPI
  • 豊富な組み込み機能: バインディング、バリデーション、レンダリングを標準搭載
  • 強力なミドルウェア: 階層的なミドルウェアシステムとgin-contribエコシステム
  • クラッシュ耐性: 組み込みリカバリーによるサーバー保護
  • 活発なコミュニティ: アクティブな開発とコミュニティサポート

デメリット

  • Go言語依存: Go特有の学習コストと開発環境の制約
  • 機能の簡潔性: フルスタックフレームワークに比べて機能が限定的
  • コンベンション制約: 高速化のための規約により柔軟性が制限される場合
  • デバッグ情報: 高速化優先のため詳細なデバッグ情報が制限される
  • テンプレート制限: 基本的なテンプレート機能のみ提供

主要リンク

書き方の例

Hello World

package main

import (
	"net/http"
	"github.com/gin-gonic/gin"
)

func main() {
	// Ginインスタンスを作成(デフォルトはLogger + Recoveryミドルウェア付き)
	r := gin.Default()

	// GET /hello ルート
	r.GET("/hello", func(c *gin.Context) {
		c.String(http.StatusOK, "Hello, World!")
	})

	// GET / ルート
	r.GET("/", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "Welcome to Gin!",
			"version": "v1.10.0",
		})
	})

	// サーバー起動(デフォルトポート:8080)
	r.Run()
}

ルーティングとパラメータ

package main

import (
	"net/http"
	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()

	// 基本ルート
	r.GET("/", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "Gin API Server",
		})
	})

	// パスパラメータ
	r.GET("/users/:id", func(c *gin.Context) {
		id := c.Param("id")
		c.JSON(http.StatusOK, gin.H{
			"user_id": id,
			"name":    "ユーザー " + id,
		})
	})

	// ワイルドカード
	r.GET("/files/*filepath", func(c *gin.Context) {
		filepath := c.Param("filepath")
		c.JSON(http.StatusOK, gin.H{
			"filepath": filepath,
			"message":  "ファイル: " + filepath,
		})
	})

	// クエリパラメータ
	r.GET("/search", func(c *gin.Context) {
		query := c.Query("q")
		page := c.DefaultQuery("page", "1")
		
		c.JSON(http.StatusOK, gin.H{
			"query":   query,
			"page":    page,
			"results": []string{"結果1", "結果2", "結果3"},
		})
	})

	// HTTPメソッド
	r.POST("/users", createUser)
	r.PUT("/users/:id", updateUser)
	r.DELETE("/users/:id", deleteUser)

	r.Run(":8080")
}

func createUser(c *gin.Context) {
	c.JSON(http.StatusCreated, gin.H{
		"message": "ユーザーを作成しました",
	})
}

func updateUser(c *gin.Context) {
	id := c.Param("id")
	c.JSON(http.StatusOK, gin.H{
		"message": "ユーザー " + id + " を更新しました",
	})
}

func deleteUser(c *gin.Context) {
	id := c.Param("id")
	c.JSON(http.StatusOK, gin.H{
		"message": "ユーザー " + id + " を削除しました",
	})
}

JSONバインディングとバリデーション

package main

import (
	"net/http"
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
)

type User struct {
	Name  string `json:"name" binding:"required,min=2,max=50"`
	Email string `json:"email" binding:"required,email"`
	Age   int    `json:"age" binding:"min=0,max=120"`
}

type LoginRequest struct {
	Email    string `json:"email" binding:"required,email"`
	Password string `json:"password" binding:"required,min=6"`
}

func main() {
	r := gin.Default()

	// JSONバインディング
	r.POST("/users", func(c *gin.Context) {
		var user User

		// JSONをバインド
		if err := c.ShouldBindJSON(&user); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
			return
		}

		// ユーザー処理(データベース保存など)
		// ...

		c.JSON(http.StatusCreated, gin.H{
			"message": "ユーザーが作成されました",
			"user":    user,
		})
	})

	// URIバインディング
	r.GET("/users/:id/:name", func(c *gin.Context) {
		var uri struct {
			ID   int    `uri:"id" binding:"required"`
			Name string `uri:"name" binding:"required"`
		}

		if err := c.ShouldBindUri(&uri); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
			return
		}

		c.JSON(http.StatusOK, gin.H{
			"id":   uri.ID,
			"name": uri.Name,
		})
	})

	// フォームバインディング
	r.POST("/login", func(c *gin.Context) {
		var login LoginRequest

		if err := c.ShouldBindWith(&login, binding.Form); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
			return
		}

		// 認証処理
		if login.Email == "[email protected]" && login.Password == "password123" {
			c.JSON(http.StatusOK, gin.H{
				"message": "ログイン成功",
				"token":   "jwt_token_here",
			})
		} else {
			c.JSON(http.StatusUnauthorized, gin.H{
				"error": "認証に失敗しました",
			})
		}
	})

	r.Run(":8080")
}

ミドルウェアの使用

package main

import (
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
)

func main() {
	// ミドルウェアなしのGinインスタンス
	r := gin.New()

	// グローバルミドルウェア
	r.Use(gin.Logger())
	r.Use(gin.Recovery())

	// カスタムミドルウェア
	r.Use(customLogger())
	r.Use(corsMiddleware())

	// 認証ミドルウェア
	r.Use("/api", authMiddleware())

	// パブリックルート
	r.GET("/", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "パブリックエンドポイント",
		})
	})

	// 保護されたAPIルート
	api := r.Group("/api")
	{
		api.GET("/users", func(c *gin.Context) {
			userID := c.GetString("userID")
			c.JSON(http.StatusOK, gin.H{
				"message": "ユーザー一覧",
				"user_id": userID,
			})
		})

		api.POST("/posts", func(c *gin.Context) {
			c.JSON(http.StatusCreated, gin.H{
				"message": "投稿を作成しました",
			})
		})
	}

	// 管理者専用ルート
	admin := r.Group("/admin")
	admin.Use(adminMiddleware())
	{
		admin.GET("/dashboard", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{
				"message": "管理者ダッシュボード",
			})
		})
	}

	r.Run(":8080")
}

func customLogger() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		path := c.Request.URL.Path

		// リクエスト処理
		c.Next()

		// ログ出力
		latency := time.Since(start)
		method := c.Request.Method
		statusCode := c.Writer.Status()

		fmt.Printf("[GIN] %s %s %d %v\n", method, path, statusCode, latency)
	}
}

func corsMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
		c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
		c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
		c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")

		if c.Request.Method == "OPTIONS" {
			c.AbortWithStatus(204)
			return
		}

		c.Next()
	}
}

func authMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		token := c.GetHeader("Authorization")

		if token == "" {
			c.JSON(http.StatusUnauthorized, gin.H{
				"error": "認証トークンが必要です",
			})
			c.Abort()
			return
		}

		// トークン検証(実際の実装では JWT など)
		if token != "Bearer valid_token" {
			c.JSON(http.StatusUnauthorized, gin.H{
				"error": "無効なトークンです",
			})
			c.Abort()
			return
		}

		// ユーザー情報をコンテキストに設定
		c.Set("userID", "123")
		c.Next()
	}
}

func adminMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		role := c.GetHeader("X-User-Role")

		if role != "admin" {
			c.JSON(http.StatusForbidden, gin.H{
				"error": "管理者権限が必要です",
			})
			c.Abort()
			return
		}

		c.Next()
	}
}

ルートグループ

package main

import (
	"net/http"
	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()

	// 基本ルート
	r.GET("/", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "Gin API Server",
		})
	})

	// APIバージョン1
	v1 := r.Group("/api/v1")
	{
		users := v1.Group("/users")
		{
			users.GET("/", getUsersV1)
			users.POST("/", createUserV1)
			users.GET("/:id", getUserV1)
			users.PUT("/:id", updateUserV1)
			users.DELETE("/:id", deleteUserV1)
		}

		posts := v1.Group("/posts")
		{
			posts.GET("/", getPostsV1)
			posts.POST("/", createPostV1)
			posts.GET("/:id", getPostV1)
		}
	}

	// APIバージョン2(新機能付き)
	v2 := r.Group("/api/v2")
	v2.Use(apiVersionMiddleware("v2"))
	{
		users := v2.Group("/users")
		{
			users.GET("/", getUsersV2)
			users.POST("/", createUserV2)
			users.GET("/:id", getUserV2)
			users.PUT("/:id", updateUserV2)
			users.DELETE("/:id", deleteUserV2)
			users.GET("/:id/profile", getUserProfileV2) // V2の新機能
		}
	}

	// 管理者API
	admin := r.Group("/admin")
	admin.Use(authMiddleware())
	{
		admin.GET("/stats", getStats)
		admin.GET("/logs", getLogs)
		
		users := admin.Group("/users")
		{
			users.GET("/", getAllUsersAdmin)
			users.POST("/:id/ban", banUser)
			users.DELETE("/:id", deleteUserAdmin)
		}
	}

	r.Run(":8080")
}

func apiVersionMiddleware(version string) gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Header("API-Version", version)
		c.Next()
	}
}

// V1 ハンドラー
func getUsersV1(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"version": "v1",
		"users": []gin.H{
			{"id": 1, "name": "ユーザー1"},
			{"id": 2, "name": "ユーザー2"},
		},
	})
}

func createUserV1(c *gin.Context) {
	c.JSON(http.StatusCreated, gin.H{
		"message": "ユーザーを作成しました(V1)",
	})
}

func getUserV1(c *gin.Context) {
	id := c.Param("id")
	c.JSON(http.StatusOK, gin.H{
		"version": "v1",
		"user": gin.H{
			"id":   id,
			"name": "ユーザー " + id,
		},
	})
}

func updateUserV1(c *gin.Context) {
	id := c.Param("id")
	c.JSON(http.StatusOK, gin.H{
		"message": "ユーザー " + id + " を更新しました(V1)",
	})
}

func deleteUserV1(c *gin.Context) {
	id := c.Param("id")
	c.JSON(http.StatusOK, gin.H{
		"message": "ユーザー " + id + " を削除しました(V1)",
	})
}

// V2 ハンドラー(拡張機能付き)
func getUsersV2(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"version": "v2",
		"users": []gin.H{
			{"id": 1, "name": "ユーザー1", "email": "[email protected]", "active": true},
			{"id": 2, "name": "ユーザー2", "email": "[email protected]", "active": false},
		},
	})
}

func createUserV2(c *gin.Context) {
	c.JSON(http.StatusCreated, gin.H{
		"message": "ユーザーを作成しました(V2 - 拡張機能付き)",
	})
}

func getUserV2(c *gin.Context) {
	id := c.Param("id")
	c.JSON(http.StatusOK, gin.H{
		"version": "v2",
		"user": gin.H{
			"id":     id,
			"name":   "ユーザー " + id,
			"email":  "user" + id + "@example.com",
			"active": true,
		},
	})
}

func updateUserV2(c *gin.Context) {
	id := c.Param("id")
	c.JSON(http.StatusOK, gin.H{
		"message": "ユーザー " + id + " を更新しました(V2)",
	})
}

func deleteUserV2(c *gin.Context) {
	id := c.Param("id")
	c.JSON(http.StatusOK, gin.H{
		"message": "ユーザー " + id + " を削除しました(V2)",
	})
}

func getUserProfileV2(c *gin.Context) {
	id := c.Param("id")
	c.JSON(http.StatusOK, gin.H{
		"user_id": id,
		"profile": gin.H{
			"bio":      "ユーザー " + id + " のプロフィール",
			"location": "東京, 日本",
			"website":  "https://example.com",
		},
	})
}

// その他のハンドラー
func getPostsV1(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"posts": []gin.H{
			{"id": 1, "title": "投稿1"},
			{"id": 2, "title": "投稿2"},
		},
	})
}

func createPostV1(c *gin.Context) {
	c.JSON(http.StatusCreated, gin.H{
		"message": "投稿を作成しました",
	})
}

func getPostV1(c *gin.Context) {
	id := c.Param("id")
	c.JSON(http.StatusOK, gin.H{
		"post": gin.H{
			"id":    id,
			"title": "投稿 " + id,
		},
	})
}

func getStats(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"stats": gin.H{
			"users": 150,
			"posts": 1200,
		},
	})
}

func getLogs(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"logs": []string{
			"ログエントリ1",
			"ログエントリ2",
		},
	})
}

func getAllUsersAdmin(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"message": "全ユーザー管理",
	})
}

func banUser(c *gin.Context) {
	id := c.Param("id")
	c.JSON(http.StatusOK, gin.H{
		"message": "ユーザー " + id + " をBANしました",
	})
}

func deleteUserAdmin(c *gin.Context) {
	id := c.Param("id")
	c.JSON(http.StatusOK, gin.H{
		"message": "ユーザー " + id + " を削除しました(管理者)",
	})
}

func authMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		token := c.GetHeader("Authorization")
		if token != "Bearer admin_token" {
			c.JSON(http.StatusUnauthorized, gin.H{
				"error": "管理者認証が必要です",
			})
			c.Abort()
			return
		}
		c.Next()
	}
}

エラーハンドリング

package main

import (
	"errors"
	"net/http"
	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()

	// カスタムエラーハンドリングミドルウェア
	r.Use(errorHandler())

	// 正常なルート
	r.GET("/", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "正常なレスポンス",
		})
	})

	// 400エラー
	r.GET("/bad-request", func(c *gin.Context) {
		c.Error(errors.New("不正なリクエストです"))
		c.JSON(http.StatusBadRequest, gin.H{
			"error": "Bad Request",
		})
	})

	// カスタムエラー
	r.GET("/custom-error", func(c *gin.Context) {
		err := errors.New("カスタムエラーが発生しました")
		c.Error(err)
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
			"error": err.Error(),
		})
	})

	// パニックテスト
	r.GET("/panic", func(c *gin.Context) {
		panic("パニックが発生しました")
	})

	// バリデーションエラー
	r.POST("/validate", func(c *gin.Context) {
		var input struct {
			Name  string `json:"name" binding:"required"`
			Email string `json:"email" binding:"required,email"`
		}

		if err := c.ShouldBindJSON(&input); err != nil {
			c.Error(err)
			c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
				"error":   "バリデーションエラー",
				"details": err.Error(),
			})
			return
		}

		c.JSON(http.StatusOK, gin.H{
			"message": "バリデーション成功",
			"data":    input,
		})
	})

	// データベースエラーシミュレーション
	r.GET("/db-error", func(c *gin.Context) {
		err := simulateDatabaseError()
		if err != nil {
			c.Error(err)
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
				"error": "データベースエラーが発生しました",
			})
			return
		}

		c.JSON(http.StatusOK, gin.H{
			"message": "データベース処理成功",
		})
	})

	r.Run(":8080")
}

func errorHandler() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Next()

		// エラーがある場合の処理
		for _, err := range c.Errors {
			// エラーログ出力
			fmt.Printf("Error: %s\n", err.Error())
			
			// エラータイプによる分岐
			switch err.Type {
			case gin.ErrorTypeBind:
				// バインディングエラー
				c.JSON(http.StatusBadRequest, gin.H{
					"error":     "リクエストの形式が正しくありません",
					"details":   err.Error(),
					"timestamp": time.Now().Format(time.RFC3339),
				})
			case gin.ErrorTypePublic:
				// パブリックエラー
				c.JSON(http.StatusInternalServerError, gin.H{
					"error":     "内部サーバーエラー",
					"timestamp": time.Now().Format(time.RFC3339),
				})
			default:
				// その他のエラー
				c.JSON(http.StatusInternalServerError, gin.H{
					"error":     "予期しないエラーが発生しました",
					"timestamp": time.Now().Format(time.RFC3339),
				})
			}
		}
	}
}

func simulateDatabaseError() error {
	// データベースエラーをシミュレート
	return errors.New("connection timeout")
}

ファイルアップロードとWebSocket

package main

import (
	"fmt"
	"net/http"
	"path/filepath"
	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
	CheckOrigin: func(r *http.Request) bool {
		return true // 本番環境では適切なオリジンチェックを実装
	},
}

func main() {
	r := gin.Default()

	// 静的ファイル配信
	r.Static("/static", "./static")

	// 単一ファイルアップロード
	r.POST("/upload", func(c *gin.Context) {
		file, err := c.FormFile("file")
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": "ファイルの取得に失敗しました",
			})
			return
		}

		// ファイルサイズチェック(10MB制限)
		if file.Size > 10<<20 {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": "ファイルサイズが大きすぎます(最大10MB)",
			})
			return
		}

		// ファイル保存
		filename := filepath.Base(file.Filename)
		if err := c.SaveUploadedFile(file, "./uploads/"+filename); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{
				"error": "ファイルの保存に失敗しました",
			})
			return
		}

		c.JSON(http.StatusOK, gin.H{
			"message":  "ファイルアップロード成功",
			"filename": filename,
			"size":     file.Size,
		})
	})

	// 複数ファイルアップロード
	r.POST("/upload/multiple", func(c *gin.Context) {
		form, _ := c.MultipartForm()
		files := form.File["files"]

		var uploadedFiles []string

		for _, file := range files {
			filename := filepath.Base(file.Filename)
			if err := c.SaveUploadedFile(file, "./uploads/"+filename); err == nil {
				uploadedFiles = append(uploadedFiles, filename)
			}
		}

		c.JSON(http.StatusOK, gin.H{
			"message":        "ファイルアップロード完了",
			"uploaded_files": uploadedFiles,
			"count":          len(uploadedFiles),
		})
	})

	// WebSocket エンドポイント
	r.GET("/ws", func(c *gin.Context) {
		conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
		if err != nil {
			fmt.Printf("WebSocket upgrade error: %v\n", err)
			return
		}
		defer conn.Close()

		fmt.Println("WebSocket接続確立")

		for {
			// メッセージ読み取り
			messageType, message, err := conn.ReadMessage()
			if err != nil {
				fmt.Printf("Read error: %v\n", err)
				break
			}

			fmt.Printf("受信: %s\n", message)

			// エコーバック
			if err := conn.WriteMessage(messageType, message); err != nil {
				fmt.Printf("Write error: %v\n", err)
				break
			}
		}
	})

	// ブロードキャスト用WebSocket
	r.GET("/ws/chat", func(c *gin.Context) {
		handleChatWebSocket(c)
	})

	r.Run(":8080")
}

// チャット用WebSocketハンドラー
func handleChatWebSocket(c *gin.Context) {
	conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
	if err != nil {
		return
	}
	defer conn.Close()

	// ユーザー情報
	userID := c.Query("user_id")
	if userID == "" {
		userID = "anonymous"
	}

	fmt.Printf("ユーザー %s がチャットに参加しました\n", userID)

	for {
		var msg struct {
			Type    string `json:"type"`
			Message string `json:"message"`
			UserID  string `json:"user_id"`
		}

		if err := conn.ReadJSON(&msg); err != nil {
			fmt.Printf("JSON read error: %v\n", err)
			break
		}

		msg.UserID = userID

		// メッセージをエコーバック(実際の実装では他のクライアントにブロードキャスト)
		if err := conn.WriteJSON(msg); err != nil {
			fmt.Printf("JSON write error: %v\n", err)
			break
		}

		fmt.Printf("チャットメッセージ: %s from %s\n", msg.Message, msg.UserID)
	}

	fmt.Printf("ユーザー %s がチャットから退出しました\n", userID)
}