REST API Handlers in Go
REST APIs are the backbone of modern backend systems.
Go is widely used to build fast, reliable, and scalable REST APIs using its
standard net/http package.
In this lesson, you will learn how to design, structure, and implement real REST API handlers using Go — the same way production systems do.
What Is a REST API?
REST (Representational State Transfer) is an architectural style that uses HTTP methods to perform operations on resources.
- GET – Read data
- POST – Create data
- PUT – Update data
- DELETE – Remove data
Each resource is identified by a URL.
Real-World Example: User Service
Imagine a backend service that manages users for an application. Each user has:
- ID
- Name
We will build REST handlers for this scenario.
Defining a Data Model
Start by defining a Go struct to represent a user.
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
In-Memory Data Store
For learning purposes, we will store users in memory.
var users = []User{
{ID: 1, Name: "Alice", Email: "alice@example.com"},
{ID: 2, Name: "Bob", Email: "bob@example.com"},
}
In real systems, this would be replaced by a database.
GET Handler – Fetch All Users
This endpoint returns all users as JSON.
func getUsers(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
}
POST Handler – Create a New User
This endpoint reads JSON from the request body and adds a new user.
func createUser(w http.ResponseWriter, r *http.Request) {
var newUser User
err := json.NewDecoder(r.Body).Decode(&newUser)
if err != nil {
http.Error(w, "Invalid input", http.StatusBadRequest)
return
}
newUser.ID = len(users) + 1
users = append(users, newUser)
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(newUser)
}
Routing REST Endpoints
Use http.HandleFunc to map endpoints.
http.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
getUsers(w, r)
} else if r.Method == http.MethodPost {
createUser(w, r)
} else {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
})
Handling Status Codes Properly
Status codes communicate the result of a request.
- 200 – OK
- 201 – Created
- 400 – Bad Request
- 404 – Not Found
- 500 – Server Error
Correct status codes improve API reliability and client integration.
GET User by ID
You can extract query parameters to fetch a specific user.
func getUserByID(w http.ResponseWriter, r *http.Request) {
idStr := r.URL.Query().Get("id")
id, _ := strconv.Atoi(idStr)
for _, user := range users {
if user.ID == id {
json.NewEncoder(w).Encode(user)
return
}
}
http.Error(w, "User not found", http.StatusNotFound)
}
Validating Input Data
Always validate incoming data to prevent invalid or unsafe requests.
For example, ensure that email and name fields are not empty before processing.
Separating Logic for Clean Code
In production systems:
- Handlers manage HTTP logic
- Services handle business logic
- Repositories manage database access
This separation makes APIs easier to maintain and scale.
Testing REST Handlers
Go provides excellent tools to test HTTP handlers.
Well-tested APIs reduce bugs and downtime in production.
Real-World Use Cases
- User management systems
- E-commerce backends
- Mobile and web app APIs
- Microservice communication
Practice Exercises
Exercise 1
Add a DELETE endpoint to remove a user by ID.
Exercise 2
Add input validation to reject empty names or invalid emails.
Key Takeaways
- REST APIs rely on HTTP methods
- Handlers map requests to logic
- JSON is the standard data format
- Clean structure improves scalability
What’s Next?
In the next lesson, you will work with Go modules in real projects and learn how APIs scale across multiple services.