FAQ
General Questions
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?
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?
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?
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?
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?
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?
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”?
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?
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?
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?
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?
See Database integration for a complete guide covering database/sql, GORM, connection pooling, and dependency injection patterns.
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
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?
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
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?
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?
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.