Writing a CSV in Go

Ido Shveki
3 min readAug 10, 2019

Hi you.. I bet you want to generate all this data into a CSV don’t you?

The need for generating a CSV and downloading it from the server is quite often when working with data and having the products’ clients demand for a certain report.

Lets’ see how easy it is —

  • In this article I assume using with “encoding/csv” package and working with an http response :)
  • I also added way to test your http handler and handle errors on the request level.

Hope you like it, please contact for any feedback (good or bad)

  1. Setting the response headers

We first need to tell the browser (or any client that’s receiving the request) what’s the type of the response they’re getting and in what form

// assuming var w http.ResponseWriter
....
w.Header().Set("Content-Disposition", "attachment; filename=export.csv")
w.Header().Set("Content-Type", "text/csv")
w.Header().Set("Transfer-Encoding", "chunked")

In other words:

Hi, I’m sending you an attachment in the response, it is of type CSV and is named “export.csv”. It’s going to be divided into chunks so no worries if you don’t get it all at once, just keep waiting and don’t you close that response in the meantime”

2. Fetching you data

Now is the time to get the data from dB or any place you wish. Two issues (I can think of) you might encounter while trying to get the data are:

a. Getting the request context, in order to be able to talk to db etc. it’s as simple as:

// assuming var r http.Request
ctx := r.Context()

b. Getting get params sent in the URL

param := r.URL.Query().Get("param")
// if param was not sent -> param == ""
//ORparam, ok := r.URL.Query()["param"]
// if param was not sent → ok == false

3. Writing to the CSV

Assuming I want to generate a file with the following data.

| First Name  | Last Name | Age   | Email                   |
|-------------|-----------|-------|-------------------------|
| Frodo | Baggins | 33 | fbaggins@fellowship.com |
|-------------|-----------|-------|-------------------------|
| Gandalf | The White | 11,000| thegray@fellowship.com |

I’ll just have to have each row in a slice, and that’s about it.

writer := csv.NewWriter(w)
err := writer.Write([]string{"First Name", "Last Name", "Age", "Email"})
// err check
err = writer.Write([]string{"Frodo", "Baggins", "33", "fbaggins@fellowship.com"})
// err check
err = writer.Write([]string{"Gandalf", "The White", "11,000", "thegray@fellowship.com"})
// err check
writer.Flush()

and that’s about it in terms of creating the file! but…

Some important nits I’de like to add

4. Error handling

Again, super intuitive and easy, but you’ll just need to write the error to the response, so that you can deal with it on the client:

// assuming var w http.ResponseWriterif err != nil {
http.Error(w, "cannot write to file", http.StatusInternalServerError)
return
}

Or any of the relevant status code, which can be found in details here: https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml

5. Test

The most important one that we all tend to forget and hoping no one will notice.

(I’ll have another detailed article dedicated for that, for now I’ll just write the essence):

  • We’ll be using httptest package.
  • “exportExample” in this example is the function you created before, or any function that receives a http.ResponseWriter and a *http.Request (what we want to test)
func exportExample(w http.ResponseWriter, r *http.Request) {...}func TestExport(t *testing.T) {
server := httptest.NewServer(exportExample)
defer server.Close()
eq, err := http.NewRequest(http.MethodGet, server.URL, nil/*body of request*/)
require.NoError(t, err)
// Setting headers if needed
req.Header.Set("key", "value")
res, err := http.DefaultClient.Do(req)
require.NoError(t, err)
defer res.Body.Close()
reader := csv.NewReader(res.Body)
// Looping over the file row by row
for {
ln, err := reader.Read()
if err == io.EOF {
break
}
require.NoError(t, err, "error reading row")
// test logic
}

That’s it.

Hope I helped you implement a CSV generator in Go, and if so, in a high quality!

Thanks for sticking till here, lmk what you think with either claps or any feedback, really expecting this (good and bad).

--

--