Skip to content

List and get handlers

Hub › Golang › Intermediate › List and get handlers

Goal

Add GET /items and GET /items/{id} using Go 1.22 http.ServeMux pattern routing. After this page the server returns items from SQLite.

Prerequisites

Go 1.22 ServeMux patterns

Go 1.22 added method and wildcard support to http.ServeMux. You can now write:

go
mux.Handle("GET /items", handler)
mux.Handle("GET /items/{id}", handler)

The leading GET restricts the route to GET requests only. {id} is a wildcard — its value is available inside the handler via r.PathValue("id"). No third-party router is needed.

The handlers

Add a new file handlers.go to your items/ module:

go
package main

import (
	"database/sql"
	"encoding/json"
	"net/http"
	"strconv"
)

func handleList(s *Store) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		items, err := s.List()
		if err != nil {
			http.Error(w, "internal error", http.StatusInternalServerError)
			return
		}
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(items)
	}
}

func handleGet(s *Store) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
		if err != nil {
			http.Error(w, "bad id", http.StatusBadRequest)
			return
		}
		it, err := s.Get(id)
		if err == sql.ErrNoRows {
			http.Error(w, "not found", http.StatusNotFound)
			return
		} else if err != nil {
			http.Error(w, "internal error", http.StatusInternalServerError)
			return
		}
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(it)
	}
}

Each handler is a constructor function — it takes a *Store and returns an http.HandlerFunc. The store is captured in a closure, so the handler always uses the same store instance.

handleGet distinguishes two error cases: sql.ErrNoRows means the ID does not exist (404), any other error is a server fault (500).

Wiring

Update main.go to create the mux and register both routes (you will replace main.go with the full version on page 07 — for now this skeleton is enough to test):

go
package main

import (
	"log"
	"net/http"
)

func main() {
	db, err := openDB("items.db")
	if err != nil {
		log.Fatal(err)
	}
	store := &Store{db: db}

	mux := http.NewServeMux()
	mux.Handle("GET /items", handleList(store))
	mux.Handle("GET /items/{id}", handleGet(store))

	log.Println("listening on :8080")
	log.Fatal(http.ListenAndServe(":8080", mux))
}

Checkpoint

bash
go run .

In a second terminal:

bash
curl http://localhost:8080/items

Expected output (empty database, so an empty array):

null

Note: json.NewEncoder.Encode encodes a nil slice as null. If you want [] for an empty list, initialise items as []Item{} inside List. Either is valid JSON — the tests on page 08 use the nil form.

bash
curl http://localhost:8080/items/1

Expected:

not found

Stop the server with Ctrl-C.

Next06 Create handler