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
| Operation | Method and path | Success code | Common errors |
|---|---|---|---|
| List | GET /items | 200 | 401, 403 |
| Read one | GET /items/:id | 200 | 404 |
| Create | POST /items | 201 | 400, 409 |
| Replace | PUT /items/:id | 200 | 400, 404 |
| Partial update | PATCH /items/:id | 200 | 400, 404 |
| Delete | DELETE /items/:id | 204 | 404 |
- 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
- Tutorial: Developing a RESTful API with Go and Gin, The Go Programming Language
- Gin Web Framework, Gin
- gin package documentation, Go Packages
Comments
Post a Comment