One route, one response, and the two lines that turn it into JSON. The whole server is a handler function and ListenAndServe.
A handler takes a place to write the response and the request that arrived. Register it on a path, hand the address to ListenAndServe, and you have a server.
The nil second argument means "use the routes I registered with HandleFunc", which is enough until you need more than one router.
func health(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "ok")
}
http.HandleFunc("/health", health)
log.Fatal(http.ListenAndServe(":8090", nil))Run it and the terminal blocks, since ListenAndServe only returns on an error. Send it a request from a second terminal.
$ curl localhost:8090/health
okAnything with struct tags becomes JSON through an encoder pointed at the response. Set the content type first, because Go guesses it from the bytes otherwise and guesses plain text.
type status struct {
Service string `json:"service"`
Tasks int `json:"tasks"`
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(status{Service: "tasks", Tasks: 3})A path answers every method until you say otherwise, so a POST to a read-only route succeeds by default. Check r.Method at the top of the handler and send back 405 when it is wrong.
if r.Method != http.MethodGet {
http.Error(w, "GET only", http.StatusMethodNotAllowed)
return
}A minimal program
This program serves one JSON endpoint and refuses anything that is not a GET.
package main
import (
"encoding/json"
"log"
"net/http"
)
type status struct {
Service string `json:"service"`
Tasks int `json:"tasks"`
}
func health(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "GET only", http.StatusMethodNotAllowed)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(status{Service: "tasks", Tasks: 3})
}
func main() {
http.HandleFunc("/health", health)
log.Println("listening on :8090")
log.Fatal(http.ListenAndServe(":8090", nil))
}Run it, then send it three requests: the one it wants, the same with headers shown, and one with the wrong method.
$ go run main.go
2026/09/01 21:46:38 listening on :8090
$ curl -i localhost:8090/health
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 30
{"service":"tasks","tasks":3}
$ curl -s -X POST localhost:8090/health
GET only