🌐 Detecting your location…
📢 Advertisement — Configure AdSense in Appearance → Customize → AdSense Settings

How to Build a REST API with Go and Gin in 2026: Complete Guide

⏱️3 min read  ·  552 words

Go with the Gin framework is one of the fastest, cleanest ways to build REST APIs in 2026 — compiled performance, simple syntax, and a mature ecosystem. This guide builds a complete API with routing, validation, database access, and middleware.

Why Go and Gin?

  • Performance: Compiled Go handles high concurrency with minimal resources
  • Simple deployment: A single static binary — no runtime to install
  • Gin’s speed: One of the fastest Go web frameworks, with a clean API
  • Great for microservices: Small binaries, fast startup, low memory

Setup

mkdir go-api && cd go-api
go mod init github.com/you/go-api
go get github.com/gin-gonic/gin
go get gorm.io/gorm gorm.io/driver/postgres

Basic Server and Routing

// main.go
package main

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

func main() {
    r := gin.Default()   // includes logger and recovery middleware

    r.GET("/health", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"status": "ok"})
    })

    // Route groups
    api := r.Group("/api/v1")
    {
        api.GET("/users", getUsers)
        api.GET("/users/:id", getUser)
        api.POST("/users", createUser)
        api.PUT("/users/:id", updateUser)
        api.DELETE("/users/:id", deleteUser)
    }

    r.Run(":8080")
}

Models and Database

// models.go
package main

import "gorm.io/gorm"

type User struct {
    ID    uint   `json:"id" gorm:"primaryKey"`
    Name  string `json:"name" binding:"required"`
    Email string `json:"email" binding:"required,email" gorm:"unique"`
}

var db *gorm.DB

func initDB() {
    dsn := "host=localhost user=postgres password=pass dbname=mydb port=5432"
    var err error
    db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
    if err != nil {
        panic("failed to connect database")
    }
    db.AutoMigrate(&User{})
}

Handlers with JSON Binding and Validation

// handlers.go
func getUsers(c *gin.Context) {
    var users []User
    db.Find(&users)
    c.JSON(http.StatusOK, users)
}

func getUser(c *gin.Context) {
    var user User
    if err := db.First(&user, c.Param("id")).Error; err != nil {
        c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
        return
    }
    c.JSON(http.StatusOK, user)
}

func createUser(c *gin.Context) {
    var user User
    // Binds JSON and runs validation tags (required, email)
    if err := c.ShouldBindJSON(&user); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    if err := db.Create(&user).Error; err != nil {
        c.JSON(http.StatusConflict, gin.H{"error": "email already exists"})
        return
    }
    c.JSON(http.StatusCreated, user)
}

Custom Middleware

// Authentication middleware
func authRequired() gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.GetHeader("Authorization")
        if token == "" {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "missing token"})
            c.Abort()   // stop the chain
            return
        }
        userID, err := verifyToken(token)
        if err != nil {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
            c.Abort()
            return
        }
        c.Set("userID", userID)  // pass to handlers
        c.Next()
    }
}

// Apply to a route group
protected := r.Group("/api/v1")
protected.Use(authRequired())

Structured Error Handling

// Consistent error responses
type APIError struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
}

func respondError(c *gin.Context, code int, msg string) {
    c.JSON(code, APIError{Code: code, Message: msg})
}

// Recovery middleware (Gin includes one, but you can customize)
r.Use(gin.CustomRecovery(func(c *gin.Context, recovered interface{}) {
    respondError(c, http.StatusInternalServerError, "internal server error")
}))

Graceful Shutdown

func main() {
    r := setupRouter()
    srv := &http.Server{Addr: ":8080", Handler: r}

    go func() {
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("listen: %s\n", err)
        }
    }()

    // Wait for interrupt signal
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit

    // Graceful shutdown with 5-second timeout
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    srv.Shutdown(ctx)
}

Frequently Asked Questions

Q: Gin vs Echo vs Fiber?
A: All are fast and mature. Gin is the most popular with the largest ecosystem. Echo is similar. Fiber has an Express-like API. For most projects, Gin's popularity and documentation make it the safe default.

Q: GORM or raw SQL / sqlx?
A: GORM is convenient for CRUD and rapid development. For complex queries or maximum control and performance, raw SQL with sqlx or pgx is preferred. Many teams use GORM for basics and drop to raw SQL where needed.

Q: How do I validate request data?
A: Gin uses struct tags with the validator library — binding:"required,email". ShouldBindJSON runs validation automatically and returns errors you can return to the client.

Q: How do I handle CORS in Gin?
A: Use the gin-contrib/cors middleware: r.Use(cors.Default()) for permissive dev, or configure specific origins, methods, and headers for production.

Q: How do I test Gin handlers?
A: Use httptest to create a test request and recorder, then assert on the response. Gin's router works with Go's standard testing tools for clean, fast handler tests.

Conclusion

Go with Gin delivers fast, clean REST APIs that compile to a single deployable binary. This guide covers the essentials — route groups, JSON binding with validation, GORM database access, custom middleware for auth, structured errors, and graceful shutdown. Go's performance and simple deployment make it excellent for microservices and high-concurrency APIs. Add rate limiting, structured logging, and OpenAPI docs before production, and you have a robust, scalable backend.

✍️ Leave a Comment

Your email address will not be published. Required fields are marked *