Golang can build useful web APIs with only the standard library. The net/http package provides servers, handlers, requests, responses, headers, status codes, and routing basics.
A handler is a function that receives http.ResponseWriter and *http.Request. The request contains method, URL, headers, query parameters, and body. The response writer sends headers, status, and body data back to the client.
Many production teams use routers or frameworks, but learning the standard library first helps you understand what those tools are doing under the hood.
Add one worked example that compares the normal path with the boundary case for golang web api: Build a REST Endpoint with net/http.
golang web api Build a REST Endpoint with net http should be studied as a practical Golang lesson, not as a label. Start by naming the input, the rule that changes the input, and the result a learner should be able to predict after reading the page.
package main
import (
"encoding/json"
"log"
"net/http"
)
type Tutorial struct {
ID int `json:"id"`
Title string `json:"title"`
}
func tutorialsHandler(w http.ResponseWriter, r *http.Request) {
tutorials := []Tutorial{
{ID: 1, Title: "Golang Syntax"},
{ID: 2, Title: "Golang Web API"},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(tutorials)
}
func main() {
http.HandleFunc("/tutorials", tutorialsHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
A handler should write exactly one response for each request path. Set headers first, then set the status code, then write the response body.
If you call json.NewEncoder(w).Encode(...) without calling WriteHeader, Golang sends 200 OK automatically when the body is written. For non-200 responses, call WriteHeader before writing the body.
| Step | Example |
|---|---|
| Set header | w.Header().Set("Content-Type", "application/json") |
| Set status | w.WriteHeader(http.StatusCreated) |
| Write body | json.NewEncoder(w).Encode(response) |
Real APIs should validate the HTTP method. A list endpoint usually allows GET, a create endpoint usually allows POST, and unsupported methods should return 405 Method Not Allowed.
Status codes are part of the API contract. Use them consistently so clients can handle success and failure without reading fragile text messages.
| Status | Common Meaning |
|---|---|
| 200 OK | Request succeeded and response body is returned. |
| 201 Created | A new resource was created. |
| 400 Bad Request | Input is invalid or malformed. |
| 404 Not Found | Requested resource does not exist. |
| 405 Method Not Allowed | Endpoint exists, but the method is unsupported. |
| 500 Internal Server Error | Unexpected server failure. |
func tutorialsHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode([]string{"Golang", "TypeScript"})
}
Use query parameters for optional filters, pagination, search terms, and sorting options. They are available through r.URL.Query().
Query values arrive as strings, so parse and validate them before use. Do not trust a query parameter just because it exists.
func listTutorialsHandler(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
pageValue := query.Get("page")
page := 1
if pageValue != "" {
parsed, err := strconv.Atoi(pageValue)
if err != nil || parsed < 1 {
http.Error(w, "invalid page", http.StatusBadRequest)
return
}
page = parsed
}
json.NewEncoder(w).Encode(map[string]int{"page": page})
}
For endpoints that accept JSON, decode the request body into a struct. After decoding, validate required fields and business rules. Valid JSON syntax does not mean the request is valid for your application.
Use struct tags such as json:"title" to control field names. Keep request structs separate from database models when validation or public API shape differs from storage shape.
type CreateTutorialRequest struct {
Title string `json:"title"`
}
func createTutorialHandler(w http.ResponseWriter, r *http.Request) {
var input CreateTutorialRequest
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
http.Error(w, "invalid json", http.StatusBadRequest)
return
}
if input.Title == "" {
http.Error(w, "title is required", http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusCreated)
}
For APIs, JSON error responses are often easier for clients to consume than plain text. A small helper keeps error formatting consistent across handlers.
Keep public error messages safe. Do not expose database errors, stack traces, secrets, or internal infrastructure details to API clients.
type ErrorResponse struct {
Error string `json:"error"`
}
func writeJSONError(w http.ResponseWriter, status int, message string) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(ErrorResponse{Error: message})
}
A clean handler should focus on HTTP details: read input, validate simple request shape, call a service, and write a response. Business rules should live in functions or services that are easy to test without a web server.
This separation becomes important as the API grows. It keeps routing, JSON, validation, database calls, and business rules from turning into one giant handler function.
| Layer | Responsibility |
|---|---|
| Handler | HTTP method, request parsing, status codes, response format |
| Service | Business rules and use-case orchestration |
| Repository | Database or external storage access |
| Model | Data structures used by the app |
Middleware wraps a handler with shared behavior. Common middleware handles logging, panic recovery, authentication, request IDs, CORS, timeouts, and metrics.
In the standard library, middleware is usually a function that accepts an http.Handler and returns another http.Handler.
func logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println(r.Method, r.URL.Path)
next.ServeHTTP(w, r)
})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/tutorials", tutorialsHandler)
log.Fatal(http.ListenAndServe(":8080", logging(mux)))
}
The standard library includes net/http/httptest, which lets you test handlers without opening a real network port. This makes API tests fast and predictable.
Test success cases, invalid methods, bad JSON, missing fields, query validation, and service errors. Good API tests verify both the status code and the response body.
1. Define the input for golang web api Build a REST Endpoint with net http.
2. Apply the rule from the lesson.
3. Compare the actual result with the expected result.
4. Record the fix if the result differs.
1. Try empty, missing, duplicate, or invalid data.
2. Identify where golang web api Build a REST Endpoint with net http changes behavior.
3. Explain the safest correction.
4. Retest the normal path.
Memorizing golang web api Build a REST Endpoint with net http without the situation where it is useful.
Connect golang web api Build a REST Endpoint with net http to a concrete Golang task.
Testing golang web api Build a REST Endpoint with net http only with the perfect input.
Include empty, missing, duplicate, incompatible, or failed cases when relevant.
Changing code before reading the visible symptom or error message.
Inspect the output, state, configuration, or stack trace connected to golang web api Build a REST Endpoint with net http.
Memorizing golang web api Build a REST Endpoint with net http without the situation where it is useful.
Connect golang web api Build a REST Endpoint with net http to a concrete Golang task.
The common mistake is memorizing syntax without understanding when the behavior changes or fails.
Remember the problem it solves in Golang, then attach the syntax or steps to that problem.
You can predict the result of a small example, explain a failure case, and choose it over a nearby alternative for a clear reason.
They often copy the syntax but skip the state, input, dependency, selector, route, type, or configuration that controls the behavior.
Explore 500+ free tutorials across 20+ languages and frameworks.