Start learning free

LESSON 128 OF 173 · BACKEND: SPEED

Only the Columns You Need

Practice

The product list shows each product's id, name and price; the product's own page, GET /products/{id}, shows the rest. Yet SELECT * reads every column of every product on every visit: today a category and a stock, tomorrow perhaps a long description, all copied from the database for nothing. And a new column would break the scan, because rows.Scan needs exactly one place for each column.

Name the columns you need. rows.Scan takes one place per column the query asked for, in order, and the JSON answer is made from the struct's fields. So a smaller query goes with a smaller struct. For example, a handler that only needs names and stock:

type StockLine struct {
	Name  string `json:"name"`
	Stock int    `json:"stock"`
}

app.HandleFunc("GET /stock", func(w http.ResponseWriter, r *http.Request) {
	rows := runQuery("SELECT name, stock FROM products")
	defer rows.Close()
	result := []StockLine{}
	for rows.Next() {
		var s StockLine
		rows.Scan(&s.Name, &s.Stock)
		result = append(result, s)
	}
	writeJSON(w, 200, result)
})

When a query joins two tables, write each column with its table's name in front, like orders.status, so it's clear which table it comes from. Columns used only in WHERE or ON don't need to be in the SELECT: the database uses them, the handler never reads them, and the scan has nothing to skip.

Your code must give these answers:

GET /products            →  200  [{"id": 1, "name": "Mug", "price": 12}, {"id": 2, "name": "Notebook", "price": 7}, {"id": 3, "name": "Lamp", "price": 29}, …, {"id": 8, "name": "Gift card", "price": 50}]
GET /products/8          →  200  {"category": null, "id": 8, "name": "Gift card", "price": 50, "stock": 999}
GET /customers/3/orders  →  200  [{"id": 4, "items": [{"product_id": 5, "quantity": 1}, {"product_id": 1, "quantity": 1}], "ordered": "2026-04-18", "status": "shipped"}, {"id": 7, "items": [{"product_id": 1, "quantity": 4}, {"product_id": 2, "quantity": 2}], "ordered": "2026-05-30", "status": "paid"}]
GET /customers/5/orders  →  200  []
GET /debug/queries       →  200  {"queries": 4}

Your task: in the GET /products handler, ask only for id, name and price, and scan them into a ProductSummary, the given struct with just those three fields; the slice and the cache hold ProductSummary values too. In the orders query, replace SELECT * with the five columns the handler reads, each with its table's name in front, and scan just those five.


Assignment
Follow the Coding Tutorial to make the product list and the orders query ask only for the columns they use.


Hint
The orders handler reads id, ordered and status from orders, and product_id and quantity from order_items. Separate the names with commas and keep the space at the end of the piece. The new scan has five places, in the same order, and no &skip.


STEP 1 OF 7
    Ctrl + Enter to check