A photo is not text but bytes: numbers from 0 to 255 that only an image viewer makes sense of. In Go, bytes are a []byte. A browser doesn't send a file as JSON but as multipart/form-data: the body is cut into parts, one per form field, and a file's part carries its file name and its bytes.
In a handler, r.FormFile finds the file sent in a form field. It gives three values: the file, a header whose .Filename is the name it had on the sender's computer, and an error. io.ReadAll reads the file's bytes. For example, a route that takes a customer's avatar from the field avatar:
app.HandleFunc("POST /customers/{id}/avatar", func(w http.ResponseWriter, r *http.Request) {
_, header, err := r.FormFile("avatar")
if err != nil {
writeJSON(w, 400, map[string]string{"error": "No avatar"})
return
}
writeJSON(w, 201, map[string]string{"name": header.Filename})
})
When the request has no such field, err is not nil. Go doesn't answer for you: your if answers 400 and returns. To keep the bytes, a table needs a BLOB column ("binary large object"). With product_id as the primary key, each product has one photo, and INSERT OR REPLACE lets a new photo take the old one's place instead of failing.
The tests send files in the field photo (each character of these files is one byte) and look in the table with GET /check/photos:
POST /products/1/photo (photo: mug.png, "a photo of a mug") → 201 {"filename": "mug.png", "product_id": 1, "size": 16}
POST /products/3/photo (photo: lamp.jpg, "a photo of a lamp") → 201 {"filename": "lamp.jpg", "product_id": 3, "size": 17}
POST /products/1/photo (photo: mug-new.png, "a better photo of the mug") → 201 {"filename": "mug-new.png", "product_id": 1, "size": 25}
POST /products/1/photo (form name=Mug, no file) → 400 {"error": "No photo in the request"}
GET /check/photos → 200 [{"filename": "mug-new.png", "product_id": 1, "size": 25}, {"filename": "lamp.jpg", "product_id": 3, "size": 17}]
Your task: in newApp, create a photos table, then add POST /products/{id}/photo: take the file from the field photo (answer 400 with an error when there is none), store its name and bytes for the product (a new photo replaces the old one), and answer 201 with the product id, the file name and the size in bytes.
Assignment
Follow the Coding Tutorial to create the photos table and the upload route.
Hint
Answer the size, len(data), not the bytes themselves: the client already has them. db.Exec saves the row at once, with no commit. Go won't compile a variable nobody uses yet, so expect "declared and not used" until the insert line uses header and data.
Free plan: 30 tutor messages. PRO is unlimited.
