API Fundamentals: REST, HTTP, and the Web
Welcome to Part APIs. This section builds production-ready HTTP APIs and backends in Go — from net/http fundamentals through frameworks, databases, Docker, cloud deployment, and production architecture. Every chapter pairs concepts with working Go code.
By now you already understand HTTP at the protocol level (Part 2, Chapter 10), you have written TCP and UDP servers in Go, and you are comfortable with goroutines and channels. This chapter is the bridge: a concise refresher on APIs and REST, followed immediately by Go code.
What is an API?
An API (Application Programming Interface) is a contract between two programs: a defined set of endpoints, request shapes, and response shapes that one program exposes and another consumes. It abstracts implementation so the client does not need to know how the server stores data, which database it uses, or what language it is written in — only the contract matters.
- Endpoint: A specific URL and method combination (e.g.
GET /users/42). - Request: The client's structured call — method, headers, and optionally a body.
- Response: The server's structured reply — status code, headers, and optionally a body.
- Contract: The rules for what can be asked and what will be returned. Good contracts are documented, versioned, and backwards-compatible.
An API is a promise. The server promises to understand a specific set of requests and the client promises to send only what the contract allows.
REST Principles
REST (Representational State Transfer) is an architectural style for designing APIs around resources, each identified by a URL, manipulated through standard HTTP methods.
- Statelessness: Each request carries all the information the server needs to process it. The server does not remember previous requests — every call is self-contained.
- Resource Representation: Data is modeled as resources (
/users,/orders,/products/42). The URL identifies what you want; the HTTP method identifies what to do with it. - Uniform Interface: Standard HTTP methods (GET, POST, PUT, DELETE, PATCH) mean any HTTP client can interact with any REST API without learning a custom protocol.
- Client-Server Separation: The client and server evolve independently as long as the contract holds.
HTTP Methods to CRUD
| Method | CRUD | Meaning | Safe? | Idempotent? |
|---|---|---|---|---|
| GET | Read | Retrieve a resource | Yes | Yes |
| POST | Create | Create a new resource | No | No |
| PUT | Update | Replace a resource entirely | No | Yes |
| PATCH | Update | Partially update a resource | No | No |
| DELETE | Delete | Remove a resource | Yes | No |
Safe means the request does not modify server state. Idempotent means calling it N times has the same effect as calling it once.
Example: E-Commerce API
GET /products— list productsGET /products/42— retrieve product 42POST /cart— add an item to the cartPUT /cart/42— replace cart item 42 entirelyPATCH /cart/42— update the quantity of cart item 42DELETE /cart/42— remove cart item 42
HTTP Status Codes
Every response carries a three-digit status code. Learn the ranges, not individual codes:
| Range | Meaning | Examples |
|---|---|---|
| 2xx | Success | 200 OK, 201 Created, 204 No Content |
| 3xx | Redirection | 301 Moved Permanently, 304 Not Modified |
| 4xx | Client error | 400 Bad Request, 401 Unauthorized, 404 Not Found |
| 5xx | Server error | 500 Internal Server Error, 503 Service Unavailable |
Go in Action: A Minimal REST API
Enough review. Here is a complete, runnable Go program — an HTTP server that exposes two endpoints (GET /health and GET /users/{id}) and returns JSON. This is the starting shape every API in this section builds on.
package main
import (
"encoding/json"
"log"
"net/http"
"strconv"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
var users = map[int]User{
1: {ID: 1, Name: "Alice"},
2: {ID: 2, Name: "Bob"},
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /health", healthHandler)
mux.HandleFunc("GET /users/{id}", userHandler)
log.Println("Listening on :8080")
log.Fatal(http.ListenAndServe(":8080", mux))
}
func healthHandler(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
}
func userHandler(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{
"error": "invalid user ID",
})
return
}
user, ok := users[id]
if !ok {
writeJSON(w, http.StatusNotFound, map[string]string{
"error": "user not found",
})
return
}
writeJSON(w, http.StatusOK, user)
}
func writeJSON(w http.ResponseWriter, status int, v any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(v)
}
Key points:
http.NewServeMux()with method-pattern routing (GET /users/{id}) — available since Go 1.22, no third-party router needed.r.PathValue("id")extracts the path parameter.writeJSONsets the content type, writes the status code, and encodes to JSON — a helper you will reuse across every API in this section.json.NewEncoder(w).Encode(v)streams directly to the response writer instead of building a full[]bytebuffer in memory.- The in-memory
usersmap is fine for this example; later chapters replace it with SQL databases, mutex-guarded stores, and real persistence.
Testing It
Run the server:
go run main.go
In another terminal:
curl localhost:8080/health
# {"status":"ok"}
curl localhost:8080/users/1
# {"id":1,"name":"Alice"}
curl localhost:8080/users/99
# {"error":"user not found"}
What Makes This RESTful
- Resources are identified by URLs (
/users/1). - Standard HTTP methods express intent (GET retrieves, Chapter 4 adds POST/PUT/DELETE).
- Responses include appropriate status codes (200, 404, 400).
- The
Content-Type: application/jsonheader tells the client how to parse the body. - Each request is stateless — the server remembers nothing between calls.
Frequently Asked Questions
Is REST the same thing as an API, or the same thing as JSON?
No. API is the general concept of a contract between two programs. REST is one architectural style for designing that contract. JSON is one data format a REST API commonly speaks. You can build an API that is not RESTful, and a RESTful API that returns XML instead of JSON — both are still "an API."
If REST is stateless, how does a site keep me logged in between requests?
The server does not remember your previous request — statelessness means each request is self-contained. What happens is the client resends proof of identity with every request, typically a token in the Authorization header. From the server's perspective, every request is independent even though the experience feels continuous to you.
Why use PUT and DELETE when POST could include an action field?
Because the method itself communicates intent before anyone reads the body or documentation. A GET is safe to retry and cache by default. A DELETE is unambiguous from a server log line. Proxies, browsers, and load balancers can make decisions based on the method alone — none of that works if every action is disguised as an identical POST.
Do I need to pick HTTP/2 or HTTP/3 myself?
Rarely. Which version is used is negotiated automatically between client and server. Go's standard library handles this transparently — the same http.ListenAndServe call works with HTTP/1.1 and HTTP/2. What matters is understanding what each version provides (HTTP/2's multiplexing, HTTP/3's QUIC-based transport) so you know why a client might behave differently across environments.
Is the users map in the example safe for concurrent access when multiple requests arrive at once?
No — this example is deliberately single-threaded to keep the code focused on HTTP fundamentals. A real server handling concurrent requests needs synchronization: a sync.RWMutex guarding the map, a sync.Map, or a database-backed store. Chapter 4 covers mutex-guarded in-memory stores; Chapters 36-37 cover SQL databases with built-in concurrency.
Where This Goes From Here
Chapter 2 dives into URL design, query parameters, and routing patterns. Chapter 3 covers JSON, XML, and serialization in depth. Chapter 4 builds the full CRUD API you will extend across this entire section — with proper error handling, middleware, and tests. The minimal server above is the seed: by the end of Part APIs you will have built, containerized, deployed, and secured a production-grade backend in Go.