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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Streaming rows through a cursor keeps memory flat regardless of how many records you export.
- 2Setting Content-Type and Content-Disposition headers before writing the body makes the browser treat the response as a file download.
- 3Gin's Stream callback returning false ends the response, so it doubles as both the loop condition and the error exit.
Related explainers
go
package main import ( "errors"
Parsing and validating CLI flags in Go
cli-parsing
validation
error-handling
Intermediate
8 steps
python
import csv import io from datetime import datetime
Streaming a CSV export in Flask
streaming
generators
csv
Intermediate
9 steps
go
package cache import ( "container/list"
Building a generic LRU cache in Go
lru-cache
generics
linked-list
Intermediate
8 steps
go
package model import ( "encoding/json"
Custom JSON marshaling in Go
json
serialization
interfaces
Intermediate
5 steps
python
import csv import io from datetime import date
Streaming a CSV export in FastAPI
streaming
async-generators
csv
Advanced
8 steps
go
package store import ( "database/sql"
Wrapping and inspecting errors in Go
error-handling
error-wrapping
sentinel-errors
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/streaming-a-csv-export-in-gin-explained-go-344d/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.