Building a REST API

A REST API exposes resources over HTTP, and Go's standard net/http — with the 1.22 method-and-wildcard routing in http.ServeMux — gives you everything you need to route requests, read path values, decode and encode JSON, and return the right status codes.

Learn Building a REST API in our free Go course — an interactive lesson with runnable examples, a practice exercise and a quick reference.

Part of the free Go course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

1️⃣ Routing by method and path value

http.NewServeMux is the standard router. Since Go 1.22 a pattern can name a method and use wildcards : GET /items/{id} matches only GETs and captures id , which you read with r.PathValue("id") . We exercise the handlers entirely with httptest so it runs with no port.

2️⃣ Creating resources: decode, validate, 201

A POST handler reads the JSON body with json.NewDecoder(r.Body).Decode(&in) , validates it, then writes a response. Set the status with w.WriteHeader(http.StatusCreated) before encoding the body — once you write bytes, the status is locked to whatever was sent.

3️⃣ Keeping handlers DRY

Every handler repeats the same three lines: set the content type, write the status, encode JSON. Factor that into a tiny writeJSON helper. Note 204 No Content for a successful DELETE — a body-less success — which never calls Encode at all.

🎯 Your Turn

Register a route and dispatch a request to it. Fill in the two blanks marked ___ , then run it.

❌ Calling w.WriteHeader after writing the body — the status is already 200; the second call logs "superfluous WriteHeader".

✅ Set headers and status before the first Write / Encode .

❌ Patterns without a method on Go < 1.22, then expecting method routing.

✅ Use Go 1.22+ and write "GET /path" ; the mux returns 405 for other methods automatically.

❌ Ignoring the Decode error — a malformed body leaves your struct half-filled.

✅ Check it and return 400 ; validate required fields too.

❌ Not setting Content-Type — clients may not treat the body as JSON.

✅ w.Header().Set("Content-Type", "application/json") before writing.

405 Method Not Allowed — the path matches but the method doesn't, and the 1.22 mux rejects it for you.

r.PathValue("id") — it returns the matched segment as a string ; convert with strconv.Atoi if you need a number.

Register GET /greet/&#123;lang&#125; , look up the language in the map, and write the greeting — or respond 404 for an unknown one. Then dispatch /greet/fr and /greet/de .

Practice quiz

Which http.ServeMux pattern matches only GET requests for a single item by id (Go 1.22+)?

  • "/items/{id}"
  • "GET /items/:id"
  • "GET /items/{id}"
  • "/items/(id)"

Answer: "GET /items/{id}". Since Go 1.22 a pattern can include the method and a named wildcard like {id}.

How do you read the {id} wildcard inside the handler?

  • r.PathValue("id")
  • r.URL.Query().Get("id")
  • r.FormValue("id")
  • mux.Param("id")

Answer: r.PathValue("id"). r.PathValue("id") returns the matched path segment as a string.

A request with the wrong HTTP method to a matched path returns what status automatically?

  • 400 Bad Request
  • 404 Not Found
  • 500 Internal Server Error
  • 405 Method Not Allowed

Answer: 405 Method Not Allowed. The 1.22 mux returns 405 when the path matches but the method does not.

When must you call w.WriteHeader(status)?

  • After the first Write/Encode
  • Before writing any body bytes
  • Only inside a defer
  • It is optional and ignored

Answer: Before writing any body bytes. Once you write bytes the status is locked to 200, so set it before the first write.

Which status code is conventional after a successful POST that creates a resource?

  • 201 Created
  • 200 OK
  • 202 Accepted
  • 204 No Content

Answer: 201 Created. 201 Created (http.StatusCreated) signals a new resource was created.

Which status fits a successful DELETE that returns no body?

  • 200 OK
  • 201 Created
  • 204 No Content
  • 404 Not Found

Answer: 204 No Content. 204 No Content means success with nothing to return in the body.

How do you decode a JSON request body straight from the stream?

  • json.Unmarshal(r.Body, &v)
  • json.NewDecoder(r.Body).Decode(&v)
  • json.Decode(r.Body, &v)
  • r.Body.JSON(&v)

Answer: json.NewDecoder(r.Body).Decode(&v). json.NewDecoder(r.Body).Decode(&v) reads JSON directly from the body stream.

How do you test a handler without binding a port?

  • http.ListenAndServe on :0
  • exec.Command("curl")
  • net.Dial to localhost
  • httptest.NewRecorder with httptest.NewRequest

Answer: httptest.NewRecorder with httptest.NewRequest. httptest.NewRecorder is an in-memory ResponseWriter you drive via ServeHTTP.

What does http.NewServeMux return?

  • A *http.Client
  • A request router (*http.ServeMux)
  • An http.ResponseWriter
  • A net.Listener

Answer: A request router (*http.ServeMux). http.NewServeMux creates a new request multiplexer (router).

Why set w.Header().Set("Content-Type", "application/json")?

  • It is required to compile
  • It selects the HTTP method
  • So clients treat the body as JSON
  • It enables gzip automatically

Answer: So clients treat the body as JSON. Setting the Content-Type tells clients the response body is JSON.