A shop doesn't send all its products at once. It hands them out in pages: GET /products?page=2 asks for the second page, with 3 products per page unless the client sends another per_page. The database builds a page with LIMIT, how many rows to give, and OFFSET, how many rows to skip first.
An AI assistant wrote this handler, and a customer noticed that the Mug never shows up. (intParam is a given helper: it reads a whole number from the query string, or gives the default.)
app.HandleFunc("GET /products", func(w http.ResponseWriter, r *http.Request) {
page := intParam(r, "page", 1)
perPage := intParam(r, "per_page", 3)
offset := (page-1)*perPage + 1
rows, _ := db.Query("SELECT id, name FROM products ORDER BY id LIMIT ? OFFSET ?", perPage, offset)
defer rows.Close()
result := []Product{}
for rows.Next() {
var p Product
rows.Scan(&p.ID, &p.Name)
result = append(result, p)
}
writeJSON(w, 200, map[string]any{"page": page, "products": result})
})
An offset counts rows to skip, so it starts at 0, while pages are numbered from 1. With 10 comments per page, for example, page 1 skips 0 comments and page 3 skips 20. The AI added 1, so page 1 skips the Mug and every page starts one product late. That's an off-by-one error. When code counts, check the first case, the last case and the one just outside: here, page 1, the last page and page 0.
The shop has 8 products. Your rewrite must answer:
GET /products → 200 {"page": 1, "products": [{"id": 1, "name": "Mug"}, {"id": 2, "name": "Notebook"}, {"id": 3, "name": "Lamp"}]}
GET /products?page=2 → 200 {"page": 2, "products": [{"id": 4, "name": "Pen"}, {"id": 5, "name": "Plant"}, {"id": 6, "name": "Teapot"}]}
GET /products?page=3 → 200 {"page": 3, "products": [{"id": 7, "name": "Stapler"}, {"id": 8, "name": "Gift card"}]}
GET /products?page=4 → 200 {"page": 4, "products": []}
GET /products?page=2&per_page=4 → 200 {"page": 2, "products": [{"id": 5, "name": "Plant"}, …, {"id": 8, "name": "Gift card"}]}
GET /products?per_page=1 → 200 {"page": 1, "products": [{"id": 1, "name": "Mug"}]}
GET /products?page=8&per_page=1 → 200 {"page": 8, "products": [{"id": 8, "name": "Gift card"}]}
GET /products?page=0 → 400 {"error": "page and per_page must be 1 or more"}
GET /products?per_page=0 → 400 {"error": "page and per_page must be 1 or more"}
Your task: rewrite the GET /products handler. Read page and perPage with intParam (defaults 1 and 3), answer 400 with the error above when either is less than 1, skip exactly the products of the earlier pages, and answer the page number with its products in id order.
Assignment
Follow the Coding Tutorial to rewrite the GET /products handler with the right offset and a 400 for pages that can't exist.
Hint
Check page 1 by hand before you trust a formula: it must skip 0 rows, not 1. Keep ORDER BY id in the query, or pages could repeat or miss products. Go won't build a program with a variable nobody uses, so the app only runs again once the loop appends to result.
Free plan: 30 tutor messages. PRO is unlimited.
