Handling CSV in Go Gin
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
- API to fetch from CSV
Test this by creating a POST request and sending a CSV file as a value to the key ‘file’
Hope this article has been helpful, thank you for reading.
Link to the repository: https://github.com/stha-nikhil/gin-csv