go 46 lines · 8 steps

Streaming a CSV export in Gin

Stream database rows straight to an HTTP response as CSV without buffering the whole result set in memory.

Explained by highlit
1func (h *TransactionHandler) ExportCSV(c *gin.Context) {
2 ctx := c.Request.Context()
3 filters := parseTransactionFilters(c)
4 
5 rows, err := h.repo.StreamTransactions(ctx, filters)
6 if err != nil {
7 c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "failed to query transactions"})
8 return
9 }
10 defer rows.Close()
11 
12 filename := fmt.Sprintf("transactions-%s.csv", time.Now().Format("2006-01-02"))
13 c.Header("Content-Type", "text/csv")
14 c.Header("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename))
15 
16 w := csv.NewWriter(c.Writer)
17 _ = w.Write([]string{"id", "account", "amount", "currency", "status", "created_at"})
18 w.Flush()
19 
20 c.Stream(func(out io.Writer) bool {
21 if !rows.Next() {
22 return false
23 }
24 
25 var t Transaction
26 if err := rows.Scan(&t.ID, &t.Account, &t.Amount, &t.Currency, &t.Status, &t.CreatedAt); err != nil {
27 c.Error(err)
28 return false
29 }
30 
31 record := []string{
32 t.ID,
33 t.Account,
34 strconv.FormatInt(t.Amount, 10),
35 t.Currency,
36 t.Status,
37 t.CreatedAt.UTC().Format(time.RFC3339),
38 }
39 if err := w.Write(record); err != nil {
40 c.Error(err)
41 return false
42 }
43 w.Flush()
44 return w.Error() == nil
45 })
46}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Streaming rows through a cursor keeps memory flat regardless of how many records you export.
  2. 2Setting Content-Type and Content-Disposition headers before writing the body makes the browser treat the response as a file download.
  3. 3Gin's Stream callback returning false ends the response, so it doubles as both the loop condition and the error exit.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Streaming a CSV export in Gin — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code