Build a RESTful API With Go and Gin: A Practical Guide

To build a RESTful API with Go and Gin, create a Go module, add the github.com/gin-gonic/gin package, define structs for your resources, register handlers with gin.Default() and methods such as router.GET and router.POST, and start the server with router.Run. Gin handles routing, JSON binding and middleware, so most of your effort goes into validation, errors and data access.

Key takeaways

  • Gin is a lightweight HTTP framework for Go with fast routing, middleware and JSON helpers.
  • The official Go tutorial uses Gin to build a small albums API, a good starting template.
  • Use ShouldBindJSON with binding tags to validate input and return clear 400 errors.
  • Group routes by version, add authentication middleware and replace in-memory data with a database before production.
  • Design status codes and error responses consistently so integrations can rely on them.

Why choose Go and Gin for an API?

Go compiles to a single binary, starts quickly and handles concurrent requests well, which makes it a common choice for internal services and integration APIs. Gin adds a router, middleware chain and request helpers on top of Go's standard net/http package without hiding it. Teams that already use Go's standard library can adopt Gin incrementally.

Alternatives exist: the standard library alone (Go 1.22 added method and wildcard patterns to its router), Echo, Chi and Fiber. Gin's advantages are maturity, documentation and a large ecosystem of middleware.

How do you set up the project?

mkdir items-api && cd items-api
go mod init example.com/items-api
go get github.com/gin-gonic/gin

Create main.go with a resource type, an in-memory store and handlers:

package main

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

type Item struct {
    ID    string  `json:"id" binding:"required"`
    Name  string  `json:"name" binding:"required"`
    Price float64 `json:"price" binding:"gte=0"`
}

var items = []Item{{ID: "1", Name: "Badge reader", Price: 49.5}}

func listItems(c *gin.Context) {
    c.JSON(http.StatusOK, items)
}

func getItem(c *gin.Context) {
    id := c.Param("id")
    for _, it := range items {
        if it.ID == id {
            c.JSON(http.StatusOK, it)
            return
        }
    }
    c.JSON(http.StatusNotFound, gin.H{"error": "item not found"})
}

func createItem(c *gin.Context) {
    var in Item
    if err := c.ShouldBindJSON(&in); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    items = append(items, in)
    c.JSON(http.StatusCreated, in)
}

func main() {
    r := gin.Default()
    v1 := r.Group("/api/v1")
    v1.GET("/items", listItems)
    v1.GET("/items/:id", getItem)
    v1.POST("/items", createItem)
    r.Run(":8080")
}

Run it with go run . and test with curl http://localhost:8080/api/v1/items. The official Go tutorial uses c.BindJSON and c.IndentedJSON; this example uses ShouldBindJSON so your code controls the error response, and compact JSON for smaller payloads.

REST design rules that matter in practice

OperationMethod and pathSuccess codeCommon errors
ListGET /items200401, 403
Read oneGET /items/:id200404
CreatePOST /items201400, 409
ReplacePUT /items/:id200400, 404
Partial updatePATCH /items/:id200400, 404
DeleteDELETE /items/:id204404
  • Use plural nouns for resources and keep verbs in the HTTP method.
  • Version from day one (/api/v1) so you can change contracts later.
  • Return one error shape everywhere, for example {"error": "message", "code": "ITEM_NOT_FOUND"}.
  • Paginate list endpoints with limit and cursor or offset parameters.

These conventions matter most when other systems integrate with you. Enterprise platforms take different approaches to integration, as our comparisons of Workday EIB, connectors and Studio and Infor's API and ION options show.

Getting the API ready for production

Persistence

Replace the slice with a database using database/sql or a library of your choice. The in-memory slice above is not safe for concurrent writes and loses data on restart.

Authentication and authorization

Add middleware that validates tokens (for example, OAuth 2.0 bearer tokens from your identity provider) and attach it to route groups with v1.Use(authMiddleware). Check permissions per action, not only at login.

Security and reliability

  • Serve over TLS, usually behind a load balancer or reverse proxy.
  • Set server read and write timeouts by using http.Server with Gin as the handler.
  • Limit request body size and validate every input.
  • Configure trusted proxies so client IPs are not spoofed.
  • Run in release mode (gin.SetMode(gin.ReleaseMode)) and use structured logging.
  • Store secrets in environment variables or a secrets manager, never in code.

Our guide on achieving data security in the cloud covers the hosting side of these controls.

Testing

Use Go's net/http/httptest package to send requests to the router in unit tests and assert on status codes and JSON bodies.

Frequently Asked Questions

What is the difference between BindJSON and ShouldBindJSON in Gin?

BindJSON automatically aborts the request with a 400 status if binding fails, leaving you little control over the response body. ShouldBindJSON returns the error to your handler and lets you decide the response. Most production APIs use ShouldBindJSON so they can return a consistent error format with helpful messages.

Is Gin still needed now that Go's standard router supports methods?

Not always. Since Go 1.22, net/http routing supports HTTP methods and path wildcards, which covers many simple services. Gin still adds convenient JSON binding, validation tags, route groups and a mature middleware ecosystem. Choose the standard library for minimal dependencies and Gin when those conveniences save real time.

How do I validate request fields in Gin?

Add binding tags to struct fields, such as binding:"required" or binding:"gte=0", and bind with ShouldBindJSON. Gin uses the go-playground validator library to check them. For rules that span several fields or need a database lookup, add explicit checks in your handler after binding succeeds.

Next steps

Work through the official Go and Gin tutorial, then extend the example above with a database, authentication middleware and httptest-based tests. Write an OpenAPI description of your endpoints before others integrate, so the contract is documented from the start.

Last reviewed: October 2026.

Sources

Comments

Popular Posts

Why Workday Onboarding Breaks Down for Frontline Employees

How to Improve the Customer Experience (CX)

How Do I Log In and Sign In to Workday HCM

UKG Union Contract and Overtime Rules for Multi-Site Food Plants

Infor HCM Software Engineer Jobs: Salary Factors and Skills

How to Search in Workday: Find Reqs, Reports, People and Tasks

ERP Solution Guide: How to Choose the Best ERP for You

The End of Remote Work: Why Companies Want You Back

The Future of Employee Healthcare Key Concerns and Strategies for 2024

How Digital Marketing Helps Your Business Grow: A Practical Plan