Go by Example: Building REST APIs
Go 1.23
Build a RESTful API in Go without external frameworks. This example demonstrates how to handle different HTTP methods (GET, POST), route requests, and work with JSON data using the standard library, providing a deep understanding of HTTP mechanics.
Code
package main
import (
"encoding/json"
"net/http"
)
type Todo struct {
ID string `json:"id"`
Title string `json:"title"`
Done bool `json:"done"`
}
var todos = []Todo{
{ID: "1", Title: "Learn Go", Done: false},
{ID: "2", Title: "Build API", Done: false},
}
func getTodos(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(todos)
}
func createTodo(w http.ResponseWriter, r *http.Request) {
var todo Todo
_ = json.NewDecoder(r.Body).Decode(&todo)
todos = append(todos, todo)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(todo)
}
func main() {
http.HandleFunc("/todos", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
getTodos(w, r)
case http.MethodPost:
createTodo(w, r)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
})
http.ListenAndServe(":8080", nil)
}Explanation
Building REST APIs in Go is straightforward with the standard library, especially with the routing enhancements in Go 1.22. A robust API typically involves routing, JSON handling, and proper status codes.
Key patterns for Go REST APIs:
- Streaming JSON: Use
json.NewEncoder(w).Encode(v)andjson.NewDecoder(r.Body).Decode(&v). This streams data directly to/from the network connection, which is more memory-efficient thanMarshal/Unmarshalfor large payloads. - Method Routing: Use the new
ServeMuxpatterns like"GET /todos/{id}"to handle different HTTP verbs cleanly without big switch statements. - Middleware: Wrap your handlers to add cross-cutting concerns like logging, CORS, and authentication (covered in the Middleware example).
Code Breakdown
9-11
Struct tags define how the Todo struct is serialized to JSON.
20
Setting the Content-Type header to application/json informs the client that the response body is JSON.
21
json.NewEncoder(w).Encode(todos) streams the JSON encoding of the 'todos' slice directly to the response writer.
26
json.NewDecoder(r.Body).Decode(&todo) reads the request body and unmarshals the JSON into the 'todo' struct.
34
We switch on r.Method to handle different HTTP verbs (GET vs POST) on the same endpoint.

