Gin
最も人気のあるGo Webフレームワーク。高速でシンプルなAPI開発に最適化されており、Martini風のAPIを提供。
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
スター履歴
データ取得日時: 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)
}