Handling CSV in Go Gin

Nikhil Shrestha
readytowork, Inc.
Published in
5 min readApr 18, 2023

In this article, we will look into how we can work with CSV in Gin, a web framework built in Go. Go has a library, encoding/csv which allows working with the CSV.

Project Structure

CSV (Project folder)
-> api (folder)
- create_csv.go
- get_csv.go
main.go

Creating CSV

Firstly, we will be working in the create_csv.go file in our API folder. In this file, we will be writing a function to create CSV.

Importing the required libraries:

import (
"bytes"
"encoding/csv"
"github.com/gin-gonic/gin"
"log"
)

Let’s look at the function.

func CreateCSV(c *gin.Context) {
records := [][]string{
{"name", "age", "nationality"},
{"Nick Shrestha", "22", "Nepali"},
{"Jon Green", "25", "American"},
}

csvBuffer := new(bytes.Buffer)
writer := csv.NewWriter(csvBuffer)
writer.WriteAll(records)

_, err := c.Writer.Write(csvBuffer.Bytes())
if err != nil {
log.Fatalln("Error in writing with context: ", err.Error())
}
}

Breaking down the function:

records := [][]string{
{"name", "age", "nationality"},
{"Nick Shrestha", "22", "Nepali"},
{"Jon Green", "25", "American"},
}

Here in the function, we have created a two-dimensional slice of strings, defined as, where each slice represents the data to be stored in each row of the CSV file.

csvBuffer := new(bytes.Buffer)

Next, we have created a new bytes.Buffer, which facilitates the reading or writing operations from/to the given data. In this function, it will allow us to write the given slice, records, in the CSV file.

writer := csv.NewWriter(csvBuffer)

Then, we have created a new writer to write into our CSV file.

writer.WriteAll(records)

We have used WriteAll to write each slice in the records to the writer and call the flush internally.

c.Writer.Write(csvBuffer.Bytes())

Finally, we have used the Writer form the context to write the CSV file in bytes.

Getting CSV

Next, we will be working in the get_csv.go file in our API folder.

In this file, we will be writing a function to get a CSV file.

Importing the required libraries:

import (
"encoding/csv"
"github.com/gin-gonic/gin"
"log"
"mime/multipart"
"net/http"
)

Remaining code:

type FileUpload struct {
CSVFile *multipart.FileHeader `form:"file"`
}

type User struct {
Name string
Age string
Address string
}

func GetCSV(c *gin.Context) {
var csvfile FileUpload
if err := c.ShouldBind(&csvfile); err != nil {
log.Fatalln("Error in JSON binding: ", err)

}

if csvfile.CSVFile == nil {
log.Fatalln("File is missing")
}

file, err := csvfile.CSVFile.Open()
if err != nil {
log.Fatalln("Error in opening file")
}
defer file.Close()

records, err := csv.NewReader(file).ReadAll()
if err != nil {
log.Fatalln("Error in reading file")
}

var users []User
for _, record := range records {
user := User{
Name: record[0],
Age: record[1],
Address: record[2],
}
users = append(users, user)
}

c.IndentedJSON(http.StatusOK, users)
}

Let’s breakdown the code:

type FileUpload struct {
File *multipart.FileHeader `form:"file"`
}

We have created a struct, FileUpload, with a field CSVFile of type multipart.FileHeader.

type User struct {
Name string
Age string
Address string
}

We have a struct, named User, which we will use to convert data from CSV into a slice.

Now, let’s shift our focus to GetCSV function:

func GetCSV(c *gin.Context) {
var csvfile FileUpload
if err := c.ShouldBind(&csvfile); err != nil {
log.Fatalln("Error in JSON binding: ", err)
}

if csvfile.CSVFile == nil {
log.Fatalln("File is missing")
}

file, err := csvfile.CSVFile.Open()
if err != nil {
log.Fatalln("Error in opening file")
}
defer file.Close()

records, err := csv.NewReader(file).ReadAll()
if err != nil {
log.Fatalln("Error in reading file")
}

var users []User
for _, record := range records {
user := User{
Name: record[0],
Age: record[1],
Address: record[2],
}
users = append(users, user)
}

c.IndentedJSON(http.StatusOK, users)
}

Breaking down the function:

   var csvfile FileUpload
if err := c.ShouldBind(&csvfile); err != nil {
log.Fatalln("Error in JSON binding: ", err)
}

if csvfile.CSVFile == nil {
log.Fatalln("File is missing")
}

file, err := csvfile.CSVFile.Open()
if err != nil {
log.Fatalln("Error in opening file")
}
defer file.Close()

Firstly, we have initialized csvFile of the FileUpload struct type and then, used it to serialize the file from form-data. After binding, we checked if the request contains the file or not before proceeding to open the File from csvFile struct to get the FileHeader’s associated file.

records, err := csv.NewReader(file).ReadAll()
if err != nil {
log.Fatalln("Error in reading file")
}

After getting the file, we used the ReadAll function to read all the records of the file, with each record being a slice of fields.

var users []User
for _, record := range records {
user := User{
Name: record[0],
Age: record[1],
Address: record[2],
}
users = append(users, user)
}
c.IndentedJSON(http.StatusOK, users)

We have initialized users of []User type. Then, using the for loop, we have stored records to users which is at the end returned as the response.

Main

Finally, in the main file, we will be using our main function for routing purposes.

Importing required libraries

import (
"github.com/gin-gonic/gin"
"github.com/stha-nikhil/gin-csv/api"
)

main()

func main() {
router := gin.Default()
router.GET("/csv-create", api.CreateCSV)
router.POST("/csv-fetch", api.GetCSV)
router.Run()
}

Results

Run the program with the following command:

$ go run main.go

You can test this via Postman or another similar platform. The program will respond in this manner:

  • API to create CSV

Test this by creating a GET request

A successful response from API to create CSV
  • API to fetch from CSV

Test this by creating a POST request and sending a CSV file as a value to the key ‘file’

A successful response from API to fetch CSV

Hope this article has been helpful, thank you for reading.

Link to the repository: https://github.com/stha-nikhil/gin-csv

--

--