This episode builds your first HTTP server with net/http and http.ServeMux in Go 1.22, including middleware and routing with methods and wildcards. You will also get to know the modern routers chi, gin, and echo, plus REST API design best practices.

After mastering data, configuration, and databases, it's time for your Go application to talk to the outside world. Episode 10 is the point where your program becomes a real service with an HTTP server, routing, and a REST API.
We start with the foundation: net/http and handlers. Then routing with http.ServeMux, which has supported methods and path wildcards since Go 1.22. After that, the middleware pattern for handling cross-cutting concerns, a comparison of modern routers like chi, gin, and echo, and route design best practices.
Two core net/http concepts: http.Handler is an interface with the ServeHTTP method, and http.ServeMux is the built-in router that matches requests to handlers.
package main
import (
"fmt"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Halo dari server Go")
})
http.ListenAndServe(":8080", mux)
}Run go run main.go and test it with curl http://localhost:8080 from another terminal. http.ResponseWriter writes the response, and http.Request carries all data from the client.
Each request goes through this flow: the router matches the path and method to a handler, the handler processes it, then writes the status and body. http.StatusOK, http.StatusNotFound, and the other status constants keep the code readable.
mux.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, `{"status":"ok"}`)
})Before Go 1.22, ServeMux only matched paths. Now patterns can specify methods and wildcards with the {name} syntax:
mux := http.NewServeMux()
mux.HandleFunc("GET /api/pengguna", daftarPengguna)
mux.HandleFunc("GET /api/pengguna/{id}", detailPengguna)
mux.HandleFunc("POST /api/pengguna", buatPengguna)Wildcard values are read from the request path:
func detailPengguna(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
fmt.Fprintf(w, "detail pengguna %s", id)
}r.PathValue("id") extracts the value of the {id} segment from the URL. This built-in routing removes the need for an external router in many cases, while keeping the code reflection-free and high performance.
Middleware is a function that wraps a handler to add behavior: logging, authentication, timeouts, or recovery. The idiomatic pattern uses a function type that returns a handler.
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("%s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "halaman utama")
})
http.ListenAndServe(":8080", loggingMiddleware(mux))
}Middleware runs in order from the outside in: a request enters through the outermost middleware first. The order of installation determines the order of execution — put recovery at the outermost layer and logging just before the handler.
chi is a lightweight router fully compatible with net/http, so standard middleware keeps working. It's the most idiomatic choice for teams that want full control without a big framework.
go get github.com/go-chi/chi/v5package main
import (
"net/http"
"github.com/go-chi/chi/v5"
)
func main() {
r := chi.NewRouter()
r.Get("/api/pengguna/{id}", func(w http.ResponseWriter, r *http.Request) {
chi.URLParam(r, "id")
})
http.ListenAndServe(":8080", r)
}gin offers high performance with built-in middleware like recovery and logger, plus convenient JSON binding helpers. echo resembles gin with a clean API and broad middleware support. The choice depends on your needs: chi for net/http simplicity, gin for productivity and ecosystem, echo for a balance of both.
Some guidelines that keep an API consistent:
/api/pengguna, not /api/ambilPengguna./api/pengguna/{id}/posting.GET reads, POST creates, PUT updates, DELETE deletes.200, 201 for created, 400 for bad requests, 404 for not found./api/v1/pengguna.A consistent route design makes life easier for clients, reduces integration bugs, and gets your API ready for other teams to use.
Episode 10 turned your program into a service: building an HTTP server with net/http, routing with the Go 1.22 http.ServeMux that supports methods and wildcards, the middleware pattern for cross-cutting concerns, a comparison of the chi, gin, and echo routers, and REST API design best practices.
Key takeaways:
http.Handler is the primary contract of net/http.ServeMux supports methods and {id} wildcards.r.PathValue reads path segment values.chi is lightweight and idiomatic; gin and echo offer more helpers.In the next episode we will discuss API security, TLS, and authentication — serving HTTPS with TLS, implementing JWT, OAuth2, and sessions, plus API security practices such as input validation, CORS, rate limiting, and security headers.