06Jan, 2021
Language blog :
English
Share blog : 
06 January, 2021
English

Building a finance tracking REST API using Go with TDD - Part 1

By

3 mins read
Building a finance tracking REST API using Go with TDD - Part 1

Building a simple REST API using Go always could be challenging from designing the architecture to implement it 'cause there's no standard way to do it and developers (software architectures) are free to do whatever they want which comes with a cost. 

After almost 6 years of building services and REST APIs using RubyOnRails, I realized that developers have a passion for abstraction but that's not always a good choice. For example; If you look at almost every RubyOnRails projects there's too much abstraction and implicit things going on which makes the learning curve far more of a curve, starting pretty shallow.

Today I'm going to show you a different perspective of designing REST APIs with Go which like a good parent instead of allowing us to do anything teaches to practice, patience, and perseverance.

Let's get started, at first every Go project must be created in $GOPATH. My $GOPATH is at, ~/go/src/ ~/go/ . But we're going to create the project under ~/go/src/github.com/budget-api be able to download the project using go get.

So we're going to run these commands, in CLI.

cd ~/go/src/github.com/azbshiri
mkdir budget-api
touch main.go

(If you don't have github.com/username, you would create it using mkdir -p github.com/username after changing the directory to ~/go/src/)

Okay, we're going to use PostgreSQL as our database and Gorilla toolkit to help us build a RESTful API. For now, we just need github.com/go-pg/pg to be able to connect to PostgreSQL and github.com/gorilla/mux for routing.

go get github.com/go-pg/pg
go get github.com/gorilla/mux

Alright now that we have the prerequisites, we should think about how we're going to use them and design the architecture.

I'm going to use a struct as a container for controller/handler actions called server to be able to access database, router and etc. So I create a new file called server.go and put the declaration in it.

// server.go
package main
type server struct {    db *pg.DB    mux *mux.Router}

And create a factory function to initialize a new server (that would help in testing which I'll show in the future)

// server.go
package main
type server struct {    db *pg.DB    mux *mux.Router}
func newServer(db *pg.DB, mux *mux.Router) *server {    s := server{db, mux}    s.routes() // register handlers    return &s}

We need a routes.go to declare paths for APIs

// routes.go
func (s *server) routes() {    // register handlers here}

Now we have a file hierarchy like the below

.
├── main.go
├── rotues.go
└── server.go

To be able to run the server we should implement http.Handler interface so we add a new pointer function to server called ServeHTTP and serve registered handlers in the router (mux)

// server.go
package main
import (    "net/http"    "github.com/go-pg/pg"    "github.com/gorilla/mux")
type server struct {    db     *pg.DB    mux    *mux.Router}
func newServer(db *pg.DB, mux *mux.Router) *server {    server := server{db, mux}    server.routes() // register handlers    return &server}
func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {    s.mux.ServeHTTP(w, r)}

Now we can update the main.go file to run the server

package main
import (    "net/http"    "github.com/go-pg/pg"    "github.com/gorilla/mux")
func main() {    db := pg.Connect(&pg.Options{        User:     "alireza",        Password: "alireza",    })    mux := mux.NewRouter()    server := newServer(db, mux)    http.ListenAndServe(":8080", server)}

At this point, we don't have any registered handlers which would lead to an API so we have just a useless running server but we have the standard, to begin with which is a great thing to speed up the development process.

Due to I'm a TDD guru, so let's just begin with a test to write our first API. As you know we're going to build a finance tracking REST API so we're going to have Budget business model and call the API endpoint /budgets.

// budgets_test.go
package main
import (    "net/http"    "os"    "testing"    "github.com/azbshiri/common/test"    "github.com/go-pg/pg"    "github.com/gorilla/mux"    "github.com/stretchr/testify/assert")
var testServer *server
func TestMain(m *testing.M) {    testServer = newServer(        pg.Connect(&pg.Options{            User:     "alireza",            Password: "alireza",            Database: "alireza",        }),        mux.NewRouter(),    )    os.Exit(m.Run())}
func TestGetBudgets(t *testing.T) {    res, err := test.DoRequest(testServer, "GET", "/budgets", nil)    assert.NoError(t, err)    assert.Equal(t, res.Code, http.StatusOK)}

To write the above test we used two libraries github.com/stretchr/testify/assert and github.com/azbshiri/common/test the first one is for making assertion easier and the second one is a little bit complicated which I built myself to reduce duplication of initializing of a http.Request and a httptest.ResponseRecorder and pass them to the existing router to be able to capture the response (That's normal way of writing integration tests in Go for further information you can check out https://golang.org/src/net/http/httptest/example_test.go).

Run the test

go test -v .=== RUN   TestGetBudgets--- FAIL: TestGetBudgets (0.00s)        budgets_test.go:42:                        Error Trace:    budgets_test.go:42                        Error:          Not equal:                                        expected: 404                                        actual  : 200                        Test:           TestGetBudgets
FAIL
exit status 1
FAIL    github.com/azbshiri/budget-api  0.018s
shell returned 1

Phew! The plan was to cover everything in a single post but honestly this is getting longer than anticipated so kindly permit me to break this into a series of posts.

Once again, thanks for reading and don’t forget to leave your questions and/or suggestions as comments below.

Written by
Senna Labs
Senna Labs

Subscribe to follow product news, latest in technology, solutions, and updates

- More than 120,000 people/day visit to read our blogs

Other articles for you

01
March, 2024
JS class syntax
1 March, 2024
JS class syntax
เชื่อว่าหลายๆคนที่เขียน javascript กันมา คงต้องเคยสงสัยกันบ้าง ว่า class ที่อยู่ใน js เนี่ย มันคืออะไร แล้วมันมีหน้าที่ต่างกับการประกาศ function อย่างไร? เรามารู้จักกับ class ให้มากขึ้นกันดีกว่า class เปรียบเสมือนกับ blueprint หรือแบบพิมพ์เขียว ที่สามารถนำไปสร้างเป็นสิ่งของ( object ) ตาม blueprint หรือแบบพิมพ์เขียว( class ) นั้นๆได้ โดยภายใน class

By

4 mins read
Thai
01
March, 2024
15 สิ่งที่ทุกธุรกิจต้องรู้เกี่ยวกับ 5G
1 March, 2024
15 สิ่งที่ทุกธุรกิจต้องรู้เกี่ยวกับ 5G
ผู้ให้บริการเครือข่ายในสหรัฐฯ ได้เปิดตัว 5G ในหลายรูปแบบ และเช่นเดียวกับผู้ให้บริการเครือข่ายในยุโรปหลายราย แต่… 5G มันคืออะไร และทำไมเราต้องให้ความสนใจ บทความนี้ได้รวบรวม 15 สิ่งที่ทุกธุรกิจต้องรู้เกี่ยวกับ 5G เพราะเราปฏิเสธไม่ได้เลยว่ามันกำลังจะถูกใช้งานอย่างกว้างขวางขึ้น 1. 5G หรือ Fifth-Generation คือยุคใหม่ของเทคโนโลยีเครือข่ายไร้สายที่จะมาแทนที่ระบบ 4G ที่เราใช้อยู่ในปัจจุบัน ซึ่งมันไม่ได้ถูกจำกัดแค่มือถือเท่านั้น แต่รวมถึงอุปกรณ์ทุกชนิดที่เชื่อมต่ออินเตอร์เน็ตได้ 2. 5G คือการพัฒนา 3 ส่วนที่สำคัญที่จะนำมาสู่การเชื่อมต่ออุปกรณ์ไร้สายต่างๆ ขยายช่องสัญญาณขนาดใหญ่ขึ้นเพื่อเพิ่มความเร็วในการเชื่อมต่อ การตอบสนองที่รวดเร็วขึ้นในระยะเวลาที่น้อยลง ความสามารถในการเชื่อมต่ออุปกรณ์มากกว่า 1 ในเวลาเดียวกัน 3. สัญญาณ 5G นั้นแตกต่างจากระบบ

By

4 mins read
Thai
01
March, 2024
จัดการ Array ด้วย Javascript (Clone Deep)
1 March, 2024
จัดการ Array ด้วย Javascript (Clone Deep)
ในปัจจุบันนี้ ปฏิเสธไม่ได้เลยว่าภาษาที่ถูกใช้ในการเขียนเว็บต่าง ๆ นั้น คงหนีไม่พ้นภาษา Javascript ซึ่งเป็นภาษาที่ถูกนำไปพัฒนาเป็น framework หรือ library ต่าง ๆ มากมาย ผู้พัฒนาหลายคนก็มีรูปแบบการเขียนภาษา Javascript ที่แตกต่างกัน เราเลยมีแนวทางการเขียนที่หลากหลาย มาแบ่งปันเพื่อน ๆ เกี่ยวกับการจัดการ Array ด้วยภาษา Javascript กัน เรามาดูตัวอย่างกันเลยดีกว่า โดยปกติแล้วการ copy ค่าจาก value type ธรรมดา สามารถเขียนได้ดังนี้

By

4 mins read
Thai

Let’s build digital products that are
simply awesome !

We will get back to you within 24 hours!Go to contact us
Please tell us your ideas.
- Senna Labsmake it happy
Contact ball
Contact us bg 2
Contact us bg 4
Contact us bg 1
Ball leftBall rightBall leftBall right
Sennalabs gray logo28/11 Soi Ruamrudee, Lumphini, Pathumwan, Bangkok 10330+66 62 389 4599hello@sennalabs.com© 2022 Senna Labs Co., Ltd.All rights reserved.