INDEPENDENT NOTES ON CODE & THE WEBGO · HTML · CSS
small systems
Go / 3 minute read

A small HTTP server, without a framework

Start with the standard library. Add a route, return JSON, and give every request a sensible time limit.

Updated October 30, 2026Small Systems

Start with one clear endpoint

A small service does not need much scaffolding. The net/http package gives you a request, a response writer, and a router. Begin with a health endpoint so that you can test the whole path from a browser to your process.

go
package main

import (
    "encoding/json"
    "log"
    "net/http"
    "time"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        if err := json.NewEncoder(w).Encode(map[string]string{
            "status": "ok",
        }); err != nil {
            log.Print(err)
        }
    })

    server := &http.Server{
        Addr:              ":8080",
        Handler:           mux,
        ReadHeaderTimeout: 5 * time.Second,
        WriteTimeout:      10 * time.Second,
        IdleTimeout:       60 * time.Second,
    }
    log.Fatal(server.ListenAndServe())
}

Run the complete example

Save the file as main.go in a new directory. Initialize a module, start the process, and ask for the health endpoint. The method-aware routing pattern in this example requires Go 1.22 or newer.

shell
go mod init example.com/tiny-server
go run .
curl -i http://localhost:8080/health

Choose timeouts for the actual workload

ReadHeaderTimeout bounds the time spent reading request headers. WriteTimeout bounds writes for ordinary request handling, and IdleTimeout controls idle keep-alive connections. These values are a starting point for a small JSON service; a streaming response or a large file upload needs different limits.

Decide when to send an error

Set response headers before writing the body. A successful body write implicitly sends status 200, so an error discovered afterward cannot reliably become a 500 response. For more complicated JSON handlers, encode into a buffer first, then set headers and write the result. Add graceful shutdown when the service needs to finish requests during deployment.

Keep reading in the original documentation:
net/http package reference ↗