FAQ
General Questions
Section titled “General Questions”How do I enable live reload during development?
Section titled “How do I enable live reload during development?”Use Air for automatic live reloading during development. Air watches your files and rebuilds/restarts your application when changes are detected.
Installation:
go install github.com/air-verse/air@latestSetup:
Create a .air.toml configuration file in your project root:
air initThen run air in your project directory instead of go run:
airAir will watch your .go files and automatically rebuild/restart your Gin application on changes. See the Air documentation for configuration options.
How do I handle CORS in Gin?
Section titled “How do I handle CORS in Gin?”Use the official gin-contrib/cors middleware:
package main
import ( "time"
"github.com/gin-contrib/cors" "github.com/gin-gonic/gin")
func main() { r := gin.Default()
// Default CORS configuration r.Use(cors.Default())
// Or customize CORS settings r.Use(cors.New(cors.Config{ AllowOrigins: []string{"https://example.com"}, AllowMethods: []string{"GET", "POST", "PUT", "DELETE"}, AllowHeaders: []string{"Origin", "Content-Type", "Authorization"}, ExposeHeaders: []string{"Content-Length"}, AllowCredentials: true, MaxAge: 12 * time.Hour, }))
r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{"message": "pong"}) })
r.Run()}For a complete security overview, see Security best practices.
How do I serve static files?
Section titled “How do I serve static files?”Use Static() or StaticFS() to serve static files:
func main() { r := gin.Default()
// Serve files from ./assets directory at /assets/* r.Static("/assets", "./assets")
// Serve a single file r.StaticFile("/favicon.ico", "./resources/favicon.ico")
// Serve from embedded filesystem (Go 1.16+) r.StaticFS("/public", http.FS(embedFS))
r.Run()}See Serving data from file for more details.
How do I handle file uploads?
Section titled “How do I handle file uploads?”Use FormFile() for single files or MultipartForm() for multiple files:
// Single file uploadr.POST("/upload", func(c *gin.Context) { file, _ := c.FormFile("file") c.SaveUploadedFile(file, "./uploads/"+file.Filename) c.String(200, "File %s uploaded successfully", file.Filename)})
// Multiple files uploadr.POST("/upload-multiple", func(c *gin.Context) { form, _ := c.MultipartForm() files := form.File["files"]
for _, file := range files { c.SaveUploadedFile(file, "./uploads/"+file.Filename) } c.String(200, "%d files uploaded", len(files))})See the Upload file documentation for more details.
How do I implement authentication with JWT?
Section titled “How do I implement authentication with JWT?”Use gin-contrib/jwt or implement custom middleware. Here’s a minimal example:
package main
import ( "net/http" "time"
"github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5")
var jwtSecret = []byte("your-secret-key")
type Claims struct { Username string `json:"username"` jwt.RegisteredClaims}
func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { tokenString := c.GetHeader("Authorization") if tokenString == "" { c.JSON(http.StatusUnauthorized, gin.H{"error": "Missing authorization token"}) c.Abort() return }
// Remove "Bearer " prefix if present if len(tokenString) > 7 && tokenString[:7] == "Bearer " { tokenString = tokenString[7:] }
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { return jwtSecret, nil })
if err != nil || !token.Valid { c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"}) c.Abort() return }
if claims, ok := token.Claims.(*Claims); ok { c.Set("username", claims.Username) c.Next() } }}For session-based authentication, see Session management.
How do I set up request logging?
Section titled “How do I set up request logging?”Gin includes a default logger middleware via gin.Default(). For structured JSON logging in production, see Structured logging.
For basic log customization:
r := gin.New()r.Use(gin.LoggerWithConfig(gin.LoggerConfig{ SkipPaths: []string{"/healthz"},}))r.Use(gin.Recovery())See the Logging section for all options including custom formats, file output, and skipping query strings.
How do I handle graceful shutdown?
Section titled “How do I handle graceful shutdown?”See Graceful restart or stop for a complete guide with code examples.
Why am I getting “404 Not Found” instead of “405 Method Not Allowed”?
Section titled “Why am I getting “404 Not Found” instead of “405 Method Not Allowed”?”By default, Gin returns 404 for routes that don’t support the requested HTTP method. Set HandleMethodNotAllowed = true to return 405 instead:
r := gin.Default()r.HandleMethodNotAllowed = true
r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{"message": "pong"})})
r.Run()$ curl -X POST localhost:8080/ping
HTTP/1.1 405 Method Not AllowedAllow: GETHow do I bind query parameters and POST data together?
Section titled “How do I bind query parameters and POST data together?”Use ShouldBind() which automatically selects the binding based on content type:
type User struct { Name string `form:"name" json:"name"` Email string `form:"email" json:"email"` Page int `form:"page"`}
r.POST("/user", func(c *gin.Context) { var user User if err := c.ShouldBind(&user); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } c.JSON(200, user)})See the Binding section for all binding options.
How do I validate request data?
Section titled “How do I validate request data?”Gin uses go-playground/validator for validation. Add validation tags to your structs:
type User struct { Name string `json:"name" binding:"required,min=3,max=50"` Email string `json:"email" binding:"required,email"` Age int `json:"age" binding:"gte=0,lte=130"`}
r.POST("/user", func(c *gin.Context) { var user User if err := c.ShouldBindJSON(&user); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } c.JSON(200, gin.H{"message": "User is valid"})})See Binding and validation for custom validators and advanced usage.
How do I run Gin in production mode?
Section titled “How do I run Gin in production mode?”Set the GIN_MODE environment variable to release:
export GIN_MODE=release# orGIN_MODE=release ./your-appOr set it programmatically:
gin.SetMode(gin.ReleaseMode)Release mode disables debug logging and improves performance.
How do I handle database connections with Gin?
Section titled “How do I handle database connections with Gin?”See Database integration for a complete guide covering database/sql, GORM, connection pooling, and dependency injection patterns.
How do I test Gin handlers?
Section titled “How do I test Gin handlers?”Use net/http/httptest to test your routes:
func TestPingRoute(t *testing.T) { router := gin.Default() router.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{"message": "pong"}) })
w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/ping", nil) router.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code) assert.Contains(t, w.Body.String(), "pong")}See the Testing documentation for more examples.
Performance Questions
Section titled “Performance Questions”How do I optimize Gin for high traffic?
Section titled “How do I optimize Gin for high traffic?”- Use Release Mode: Set
GIN_MODE=release - Disable unnecessary middleware: Only use what you need
- Use
gin.New()instead ofgin.Default()for manual middleware control - Connection pooling: Configure database connection pools (see Database integration)
- Caching: Implement caching for frequently accessed data
- Load balancing: Use reverse proxy (nginx, HAProxy)
- Profiling: Use Go’s pprof to identify bottlenecks
- Monitoring: Set up metrics and monitoring to track performance
Is Gin production-ready?
Section titled “Is Gin production-ready?”Yes. Gin is used in production by many companies and has been battle-tested at scale. See Users for examples of projects using Gin in production.
Troubleshooting
Section titled “Troubleshooting”Why are my route parameters not working?
Section titled “Why are my route parameters not working?”Ensure route parameters use : syntax and are properly extracted:
// Correctr.GET("/user/:id", func(c *gin.Context) { id := c.Param("id") c.String(200, "User ID: %s", id)})
// Not: /user/{id} or /user/<id>See Parameters in path for details.
Why is my middleware not executing?
Section titled “Why is my middleware not executing?”Middleware must be registered before routes or route groups:
// Correct orderr := gin.New()r.Use(MyMiddleware()) // Register middleware firstr.GET("/ping", handler) // Then routes
// For route groupsauth := r.Group("/admin")auth.Use(AuthMiddleware()) // Middleware for this group{ auth.GET("/dashboard", handler)}See Using middleware for details.
Why is request binding failing?
Section titled “Why is request binding failing?”Common reasons:
- Missing binding tags: Add
json:"field"orform:"field"tags - Content-Type mismatch: Ensure client sends correct Content-Type header
- Validation errors: Check validation tags and requirements
- Unexported fields: Only exported (capitalized) struct fields are bound
type User struct { Name string `json:"name" binding:"required"` // ✓ Correct Email string `json:"email"` // ✓ Correct age int `json:"age"` // ✗ Won't bind (unexported)}See Binding and validation for details.