Go CSV download API
今回はGo製のAPIでCSVをダウンロードする方法を見ていきたいと思います。
普段はGraphQLについて発信していますが、今回はRestで実装し、"encoding/csv"パッケージなどメイン機能部分のみ解説していきます。
ルーター
Restなのでまずはルーティングを作ります。 echoのFWを使っています。
func csvRouter(g *echo.Group, ctrl *controller.Controller) { constroller := controller.NewAnalyticsController(ctrl) g.GET("/analytics", func(e echo.Context) error { return constroller.Analytics(e) }) }
controller
次はコントローラーです。 dbを持たせた構造体とコンストラクタを定義します。
package controller import ( "github.com/jinzhu/gorm" ) type Controller struct { db *gorm.DB } func NewController(db *gorm.DB) *Controller { return &Controller{ db: db, } }
AnalyticsController
いよいよCSV出力の実装です。 全体のコードを見たあとに一つ一つ解説していきます。
package controller import ( "bytes" "encoding/csv" "fmt" "net/http" "strconv" "github.com/hoge/backend/src/domain" "github.com/labstack/echo" ) type AnalyticsController struct { controller *Controller } func NewAnalyticsController(controller *Controller) *AnalyticsController { return &AnalyticsController{ controller: controller, } } func (a *AnalyticsController) Analytics(c echo.Context) error { var tasks []domain.Task if err := a.controller.db.Find(&tasks).Error; err != nil { fmt.Println("error") return err } fmt.Println(tasks) csvBytes, err := convertCSV(tasks) if err != nil { fmt.Println("convertError:", err) return err } return newCSVResponse(c, http.StatusOK, csvBytes) } func newCSVResponse(c echo.Context, status int, data []byte) error { c.Response().Writer.Header().Set("Content-Disposition", "attachment; filename=tasks.csv") c.Response().Writer.Header().Set("Content-Type", "text/csv") return c.Blob(status, "text/csv", data) } func convertCSV(tasks []domain.Task) ([]byte, error) { b := new(bytes.Buffer) w := csv.NewWriter(b) var header = []string{ "id", "userID", "title", "note", "completed", "createdAt", "updatedAt", } w.Write(header) for _, task := range tasks { var col = []string{ strconv.Itoa(task.ID), strconv.Itoa(task.UserID), task.Title, task.Note, strconv.Itoa(task.Completed), task.CreatedAt.String(), task.UpdatedAt.String(), } w.Write(col) } w.Flush() if err := w.Error(); err != nil { return nil, err } return b.Bytes(), nil }
以上がAPIの実装部分となります。 エラー時の処理が適当ですがご了承ください。
Analytics()
まずはAnalyticsメソッドです。 事前に定義していたドメインエンティティのTaskを全て取得します。 ORMはgormです。
ちなみにTaskは以下のような構造体になっています。
type Task struct { ID int UserID int Title string Note string Completed int CreatedAt time.Time UpdatedAt time.Time }
convertCSV()
convertCSV()メソッドはCSVで出力したい形にtasksを変換するメソッドです。 今回はシンプルに以下のような形で出力します。
コードの中身を確認していきます。 "encoding/csv"パッケージにはcsv用の Writer構造体が用意されているので、bytes.Bufferから生成します。
b := new(bytes.Buffer) w := csv.NewWriter(b)
次にcsvのヘッダーに当たる配列を作成します。 csvはstring配列の組み合わです。
var header = []string{ "id", "userID", "title", "note", "completed", "createdAt", "updatedAt", } w.Write(header)
次は表の中身となる部分を作ります。 ヘッダーと同じくstring配列なので各フィールドをstringに変換し一件ずつバッファリングします。
for _, task := range tasks { var col = []string{ strconv.Itoa(task.ID), strconv.Itoa(task.UserID), task.Title, task.Note, strconv.Itoa(task.Completed), task.CreatedAt.String(), task.UpdatedAt.String(), } w.Write(col) }
最後にバッファリングされたデータを書き込み、エラーハンドリングをして バイト配列でreturnします。
w.Flush() if err := w.Error(); err != nil { return nil, err } return b.Bytes(), nil
バイト列を生成できたのであとはHttpレスポンスを返すだけです。
newCSVResponse()
今回の実装はechoに依存しています。
ヘッダーにはダウロードファイルである事を示す "Content-Disposition", "attachment; filename=tasks.csv"
Content-Disposition - HTTP | MDN
csvファイルである事を示す "Content-Type", "text/csv"
を付与しています。
最後にechoのBlob()メソッドでhttpレスポンスを送信します。
Response | Echo - High performance, minimalist Go web framework
func newCSVResponse(c echo.Context, status int, data []byte) error { c.Response().Writer.Header().Set("Content-Disposition", "attachment; filename=tasks.csv") c.Response().Writer.Header().Set("Content-Type", "text/csv") return c.Blob(status, "text/csv", data) }
これで実装が完了しました。
最後にリクエストを送ってみます。
curl head http://localhost:8080/api/v1/analytics --output tasks.csv
tasks.csvがダウンロードできれば完了です。
CSV以外にもGoの io
パッケージ、bytes.Bufferではさまざまな形式の入出力が可能です。
こちらの記事に大変詳しく書かれていました。
Goメモ-153 (Goでのファイル I/O のやり方まとめ) - いろいろ備忘録日記
今回は以上です。
最後に、 このブログではweb開発について発信していくのでまたご覧頂けると嬉しいです。 最後までお読み頂きありがとうございました。