If you’ve been exploring client-side Go development, it’s time to dive into the server side. Designing a REST API is one of the core skills any Go developer should master. In this guide, we’ll cover everything from HTTP methods and routing to middleware, versioning, and building a full CRUD API. By the end, you’ll be ready to implement a robust REST API in Go.
Table of Contents
- 1. Designing Your REST API with Go
- 1.1 Understanding HTTP Methods and Status Codes
- 1.2 GET – Retrieving Resources
- 1.3 POST – Creating Resources
- 1.4 PUT & PATCH – Updating Resources
- 1.5 DELETE – Removing Resources
- 1.6 HTTP Status Codes
- 1.7 Routing in Go
- 1.8 Example listUsers Handler
- 1.9 Middleware
- 1.10 API Versioning
- 1.11 Building a CRUD API in Go
- 1.11.1 Resource Structure
- 1.11.2 Endpoints
- 1.11.3 Testing with cURL
- 1.12 Summary
- 2. Best Practices for REST API Design in Go
- 2.1 1. Consistent Error Handling
- 2.2 2. Logging
- 2.3 3. Secure Your API
- 2.4 4. Pagination, Filtering, and Sorting
- 2.5 5. Use Context for Request Lifecycle
- 2.6 6. Version Your API Early
- 2.7 7. Testing Your API
- 2.8 8. Use Middleware Wisely
- 2.9 9. Documentation is Key
- 2.10 10. Keep It Simple, Yet Flexible
We’ll cover:
- HTTP methods and status codes
- Routing and URL design
- Middleware for logging and authentication
- API versioning strategies
- Building a CRUD API with Go
Requirements: Go version 1.22+ and
cURL
installed.
Designing Your REST API with Go
Understanding HTTP Methods and Status Codes
REST APIs revolve around HTTP methods, also known as verbs. They define what action the client wants to perform on a resource. The most common methods in REST APIs are:
- GET – Retrieve data
- POST – Create a new resource
- PUT – Update an existing resource fully
- PATCH – Partially update an existing resource
- DELETE – Remove a resource
GET – Retrieving Resources
The GET method is used to fetch data from the server. It’s idempotent, meaning repeated requests should return the same result.
Example – Get a single user:
$ curl https://api.example.com/users/1
{
"id": 1,
"username": "john.doe",
"name": "John",
"surname": "Doe"
}
Example – Get a list of users:
$ curl https://api.example.com/users
[
{"id": 1, "username": "john.doe", "name": "John", "surname": "Doe"},
{"id": 2, "username": "jane.doe", "name": "Jane", "surname": "Doe"}
]
Query Parameters: You can filter, order, and limit resources:
$ curl https://api.example.com/users?filter=John&orderby=name&limit=10
POST – Creating Resources
The POST method creates new resources on the server.
$ curl -X POST https://api.example.com/users --data '{"username": "jesus.espino", "name": "Jesus", "surname": "Espino"}'
The server responds with the newly created resource including its ID.
Pro Tip: POST can also trigger actions, like sending notifications or login/logout actions.
PUT & PATCH – Updating Resources
- PUT – Full replacement of a resource:
$ curl -X PUT https://api.example.com/users/3 --data '{"username": "jesus.espino", "name": "Jesús", "surname": "Espino García"}'
- PATCH – Partial update (only modify certain fields):
$ curl -X PATCH https://api.example.com/users/3 -d '{"surname": "Other"}'
DELETE – Removing Resources
The DELETE method removes a resource:
$ curl -X DELETE https://api.example.com/users/3
It usually returns a 204 No Content status code.
HTTP Status Codes
Status codes tell the client the result of their request:
- 2XX – Success
- 200 OK, 201 Created, 204 No Content
- 3XX – Redirection
- 301 Moved Permanently, 302 Found, 304 Not Modified
- 4XX – Client errors
- 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found
- 5XX – Server errors
- 500 Internal Server Error, 501 Not Implemented, 503 Service Unavailable
For a complete list: MDN HTTP Status Codes
Routing in Go
Routing defines how URLs map to functions. In Go, you can use the standard library or third-party packages like gorilla/mux
or gin
.
Example using Go 1.22 standard library:
http.HandleFunc("GET /api/v1/users", listUsers)
http.HandleFunc("POST /api/v1/users", createUsers)
http.HandleFunc("GET /api/v1/users/{id}", getUser)
http.HandleFunc("PUT /api/v1/users/{id}", updateUser)
http.HandleFunc("PATCH /api/v1/users/{id}", partialUpdateUser)
http.HandleFunc("DELETE /api/v1/users/{id}", deleteUser)
Example listUsers
Handler
func listUsers(w http.ResponseWriter, r *http.Request) {
users := []User{
{ID: 1, Username: "john.doe", Name: "John", Surname: "Doe"},
{ID: 2, Username: "jane.doe", Name: "Jane", Surname: "Doe"},
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(users)
}
Middleware
Middleware functions wrap handlers to add common functionality, like logging or authentication.
func logRequest(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Printf("Before %s %s", r.Method, r.URL.Path)
next(w, r)
log.Printf("After %s %s", r.Method, r.URL.Path)
}
}
http.HandleFunc("GET /api/v1/users", logRequest(listUsers))
You can chain multiple middlewares using the decorator pattern.
API Versioning
Always version your API. Even if you think you’re doing it right, breaking changes happen.
Example:
/api/v1/users
– current version/api/v2/users
– new version with changes
Building a CRUD API in Go
Let’s create a shopping list API.
Resource Structure
type ShoppingList struct {
ID int `json:"id"`
Name string `json:"name"`
Items []string `json:"items"`
}
var allData []ShoppingList
Endpoints
- Create a list – POST /v1/lists
- List all lists – GET /v1/lists
- Retrieve a single list – GET /v1/lists/{id}
- Update a list – PUT /v1/lists/{id}
- Partial update – PATCH /v1/lists/{id}
- Delete a list – DELETE /v1/lists/{id}
- Add item to list – POST /v1/lists/{id}/push
Each handler decodes JSON, updates allData
, and returns the correct status code.
Testing with cURL
Create a list:
$ curl -X POST http://localhost:8888/v1/lists --data '{"id":1,"name":"my first shopping list","items":["eggs","milk"]}'
List all shopping lists:
$ curl http://localhost:8888/v1/lists
Update partially:
$ curl -X PATCH http://localhost:8888/v1/lists/1 --data '{"name":"updated shopping list"}'
Delete a list:
$ curl -X DELETE http://localhost:8888/v1/lists/1
Add an item to a list:
$ curl -X POST http://localhost:8888/v1/lists/1/push --data '{"item":"bread"}'
Summary
By now, you should understand:
- HTTP methods and when to use them
- Status codes for client-server communication
- Routing and handler functions in Go
- How to use middleware for cross-cutting concerns
- API versioning best practices
- Building a fully functional CRUD API
Best Practices for REST API Design in Go
Designing a working API is one thing—but building a robust, maintainable, and secure API is another. Here are some best practices to take your Go REST API to the next level.
1. Consistent Error Handling
Always respond with consistent error messages and appropriate status codes. This makes it easier for clients to understand what went wrong.
Example – error response format:
{
"error": "User not found",
"code": 404
}
Handler Example:
func respondWithError(w http.ResponseWriter, code int, message string) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
json.NewEncoder(w).Encode(map[string]string{"error": message})
}
Then in your handlers:
if listNotFound {
respondWithError(w, http.StatusNotFound, "List not found")
}
2. Logging
Logging is crucial for debugging and monitoring your API.
- Use structured logging (e.g.,
logrus
orzap
) instead of plain text logs. - Log request method, path, client IP, and any errors.
- Avoid logging sensitive data like passwords or tokens.
Example middleware:
func loggingMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Printf("Request: %s %s from %s", r.Method, r.URL.Path, r.RemoteAddr)
next(w, r)
}
}
3. Secure Your API
Even simple APIs should follow basic security principles:
- Authentication & Authorization: Use JWTs, OAuth2, or session-based auth.
- Input validation: Always validate incoming JSON or query parameters.
- Avoid exposing internal errors: Don’t leak stack traces to clients.
- HTTPS only: Encrypt traffic to prevent data leaks.
- Rate limiting: Protect against abuse with throttling.
4. Pagination, Filtering, and Sorting
When returning lists, avoid sending huge datasets. Use query parameters for:
- Pagination:
?page=2&limit=20
- Filtering:
?filter=name:John
- Sorting:
?sort=name:asc
This makes your API scalable and friendly for front-end clients.
5. Use Context for Request Lifecycle
Go’s context.Context
is vital for managing timeouts, cancellations, and deadlines.
func handleRequest(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
select {
case <-time.After(2 * time.Second):
fmt.Fprintln(w, "Processed")
case <-ctx.Done():
fmt.Fprintln(w, "Request cancelled")
}
}
6. Version Your API Early
Even if your API is simple now, versioning protects against breaking changes. Always start with /v1/
in your URLs. If you need new functionality, create /v2/
instead of changing /v1/
.
7. Testing Your API
Automated testing ensures your API behaves as expected.
- Unit tests: Test handlers and business logic independently.
- Integration tests: Test the full HTTP lifecycle.
- Tools:
httptest
in Go, Postman, orcurl
scripts.
Example:
func TestListUsers(t *testing.T) {
req, _ := http.NewRequest("GET", "/v1/users", nil)
rr := httptest.NewRecorder()
handler := http.HandlerFunc(listUsers)
handler.ServeHTTP(rr, req)
if status := rr.Code; status != http.StatusOK {
t.Errorf("expected %v got %v", http.StatusOK, status)
}
}
8. Use Middleware Wisely
Middleware isn’t just for logging:
- Authentication – validate JWTs or sessions
- Rate limiting – prevent abuse
- CORS headers – allow front-end apps to access the API
- Request/Response compression – reduce bandwidth
Chain multiple middlewares for clean, reusable code:
http.HandleFunc("/v1/users", loggingMiddleware(authMiddleware(listUsers)))
9. Documentation is Key
- Use OpenAPI / Swagger for automated docs.
- Keep examples up to date.
- Include status codes, request/response formats, and error messages.
Good docs = happy developers = fewer support tickets.
10. Keep It Simple, Yet Flexible
- Start small and refactor. Don’t over-engineer.
- Avoid monolithic handlers; break logic into smaller functions.
- Use interfaces to abstract storage (e.g., DB vs in-memory) for easier testing.
This extended section transforms your REST API guide from “basic tutorial” to a professional developer resource, giving readers the knowledge to build scalable, secure, and maintainable APIs in Go.
- NVIDIA to Invest $5 Billion in Rival Intel in Landmark AI Chip Collaboration
- Spotify Premium Lossless Audio: How to Enable Hi-Fi Streaming
- Apple Event September 2025: Everything Announced – iPhone 17, AirPods Pro 3, Apple Watch & More
- iPhone 17 Series Unveiled at Apple Event: 17, 17 Air, and 17 Pro Redefine Innovation
- A Guide to Factory Reset Google Pixel/Android with Family Link Account | Safely Remove Child’s Account
- How to Recover Permanently Deleted Files on Mac
- How to Remove Microsoft Store Ads Showing Up on Windows