Expert · Standard library essentials · topic 31 of 46 · 45 min
An HTTP server with net/http
Explanation
How a web request works
A client (a browser, a mobile app, curl) sends an HTTP request: a method such as GET or POST, a path such as /items/42, headers, and sometimes a body. The server sends back a response: a status code (200 OK, 404 Not Found, 500 Internal Server Error), headers, and a body, often JSON. In Go, each incoming request is handled in its own goroutine, so many users are served at once.
The smallest server
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("GET /hello", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Namaste from Go!")
})
log.Println("listening on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}- http.ResponseWriter w: where you write the response (it is an io.Writer, so fmt.Fprintln works).
- *http.Request r: everything about the request: method, URL, headers, body, context.
- A handler is any value with a ServeHTTP(w, r) method; http.HandlerFunc turns a plain function into one.
- nil as the second argument means "use the default ServeMux". In real apps create your own with http.NewServeMux().
Routing with Go 1.22 patterns
mux := http.NewServeMux()
mux.HandleFunc("GET /items", listItems)
mux.HandleFunc("GET /items/{id}", getItem)
mux.HandleFunc("POST /items", createItem)
mux.HandleFunc("DELETE /items/{id}", deleteItem)
mux.HandleFunc("GET /{$}", home) // exactly "/" and nothing else
mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.Dir("public"))))
func getItem(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id") // the {id} part, as a string
fmt.Fprintf(w, "item %s\n", id)
}| Pattern | Matches |
|---|---|
| "GET /items/{id}" | GET (and HEAD) /items/42, /items/abc; not /items/42/x |
| "/static/" | Anything starting with /static/ (a trailing slash means a whole subtree) |
| "/{$}" | Only the root path / |
| "/files/{path...}" | The rest of the path, including slashes, in one wildcard |
If the path matches but the method does not (a POST to "GET /items"), the mux answers 405 Method Not Allowed for you. If two patterns overlap, the more specific one wins; if neither is more specific, registering them panics at start-up, so conflicts are caught early. Before Go 1.22 people used third-party routers such as gorilla/mux or chi for this; many projects still use chi, which is fine.
Reading the request
q := r.URL.Query().Get("q") // ?q=dosa
page := r.URL.Query().Get("page") // "" if missing
auth := r.Header.Get("Authorization")
ct := r.Header.Get("Content-Type")
// r.Body is an io.ReadCloser: decode JSON from it (next topics)Writing the response: order matters
func createItem(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json") // 1. headers
w.WriteHeader(http.StatusCreated) // 2. status (201)
w.Write([]byte(`{"id":42}`)) // 3. body
}
func missing(w http.ResponseWriter, r *http.Request) {
http.Error(w, "item not found", http.StatusNotFound) // plain-text error
}Production: http.Server with timeouts
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 10 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
log.Fatal(srv.ListenAndServe())Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
An HTTP server with net/http · 1/7
Go's standard library includes a production-grade web server: net slash http.
Prefer a video by a teacher? Find free videos on “golang net/http server go 1.22 routing servemux pathvalue” on YouTube ↗
In real life: A canteen menu service
A college canteen shows its menu on a screen and in a small app. A Go service with three routes is enough: list the menu, get one item, and a health check that the hosting platform calls every few seconds. It is one binary, uses a few MB of memory, and handles every student's phone at lunch time.
package main
import (
"fmt"
"log"
"net/http"
"time"
)
var menu = map[string]int{"dosa": 40, "idli": 30, "chai": 15}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "ok")
})
mux.HandleFunc("GET /menu/{item}", func(w http.ResponseWriter, r *http.Request) {
item := r.PathValue("item")
price, ok := menu[item]
if !ok {
http.Error(w, "no such item", http.StatusNotFound)
return
}
fmt.Fprintf(w, "%s: Rs %d\n", item, price)
})
srv := &http.Server{Addr: ":8080", Handler: mux, ReadHeaderTimeout: 5 * time.Second}
log.Fatal(srv.ListenAndServe())
}Practice (0/4 done)
With mux.HandleFunc("GET /users/{id}", h), what does r.PathValue("id") return for GET /users/77?
A client sends POST /users/77 but only "GET /users/{id}" is registered. What does ServeMux reply?
Which line is the bug? 1: if item == "" { 2: http.Error(w, "missing item", http.StatusBadRequest) 3: } 4: fmt.Fprintf(w, "item: %s", item)
Build a server with an http.Server that has timeouts and three routes: GET /healthz returns "ok"; GET /greet/{name} returns "Namaste, NAME"; GET /square?n=5 returns 25, or 400 Bad Request if n is not a number. Test each route with curl -i.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Calling other APIs safely with the HTTP clientStandard library essentials · same module
- JSON with encoding/jsonStandard library essentials · same module
- Files, time and structured logsStandard library essentials · same module
- How web requests work (and why that matters)Ethical Hacking and Penetration Testing (legal labs) · Web application testing: Burp Suite and the basics
- HTTP requests, responses and methodsBackend Development · How the web and backends work
- fetch: talking to serversJavaScript Programming (language in depth) · Asynchronous JavaScript
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
How does net/http handle many requests at once?
Answer: Each request runs in its own goroutine
The server starts a goroutine per connection/request.
Which pattern matches only the root path "/"?
Answer: "/{$}"
"/" alone matches everything; "/{$}" matches exactly "/".
What status is sent if you call w.Write without w.WriteHeader?
Answer: 200 OK
The first Write sends an implicit 200 OK.
Why use http.Server with timeouts instead of http.ListenAndServe?
Answer: ListenAndServe has no timeouts, so slow clients can hold connections forever
Timeouts protect the server from slow or malicious clients.
HTTP is…Related
Answer: The text-based language browsers and servers use
HTTP is the request/response protocol of the web.
Which part of an HTTP request carries the JSON data you send?Related
Answer: The body
The body comes after the headers and the empty line, and holds the data.
A server replies 404 Not Found. What does the fetch promise do?Related
Answer: Fulfils with a Response whose ok is false
fetch only rejects on network failure.
Hundreds of 404 responses for paths like /backup.zip, /.git/config and /admin.php from one IP suggest…Related
Answer: Someone probing for hidden files
Automated scanners try many common hidden paths, producing many 404s.
From HTTP, HTTPS and TLS
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 3 more topics in “Standard library essentials” before its exam: Calling other APIs safely with the HTTP client, JSON with encoding/json, Files, time and structured logs.
- Module 7 exam: Standard library essentials · 14 questions · 25 min, without help. 4 topics of this module still to finish first.
- Then the next module, Building a REST API with a database. 46 topics left in the course before the final exam.