├── books.db ├── go.mod ├── go.sum ├── internal ├── book │ ├── book.go │ └── book_integration_test.go ├── database │ └── database.go └── transport │ └── transport.go └── main.go /books.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TutorialEdge/go-fiber-rest-api-tutorial/9732ecd4d4e6b8bb25400683da98b5560b5aae90/books.db -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/elliotforbes/go-fiber-tutorial 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/gofiber/fiber v1.9.2 7 | github.com/jinzhu/gorm v1.9.12 8 | github.com/stretchr/testify v1.6.1 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= 4 | github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= 5 | github.com/elliotforbes/go v0.0.0-20190112135226-012ead1e9319 h1:FyoHeEwtgqKKRi+VRtb96uuMieHvp89T5wtvJfzx2Vk= 6 | github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= 7 | github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= 8 | github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= 9 | github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 10 | github.com/gofiber/fiber v1.9.2 h1:r7ZkAALBZWnw85nNRXkdtemZHFXTC9lduAbGUkK4Xww= 11 | github.com/gofiber/fiber v1.9.2/go.mod h1:o2YQgwJW8+Z16x8MTos4nYn8PD1RJpzu9fojiGqjSjI= 12 | github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= 13 | github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= 14 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 15 | github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY= 16 | github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= 17 | github.com/jinzhu/gorm v1.9.12 h1:Drgk1clyWT9t9ERbzHza6Mj/8FY/CqMyVzOiHviMo6Q= 18 | github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs= 19 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 20 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 21 | github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M= 22 | github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 23 | github.com/klauspost/compress v1.10.4 h1:jFzIFaf586tquEB5EhzQG0HwGNSlgAJpG53G6Ss11wc= 24 | github.com/klauspost/compress v1.10.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= 25 | github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= 26 | github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 27 | github.com/mattn/go-sqlite3 v2.0.1+incompatible h1:xQ15muvnzGBHpIpdrNi1DA5x0+TcBZzsIDwmw9uTHzw= 28 | github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 29 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 30 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 31 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 32 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 33 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 34 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 35 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 36 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 37 | github.com/valyala/fasthttp v1.12.0 h1:TsB9qkSeiMXB40ELWWSRMjlsE+8IkqXHcs01y2d9aw0= 38 | github.com/valyala/fasthttp v1.12.0/go.mod h1:229t1eWu9UXTPmoUkbpN/fctKPBY4IJoFXQnxHGXy6E= 39 | github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= 40 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 41 | golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 42 | golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd h1:GGJVjV8waZKRHrgwvtH66z9ZGVurTD1MT0n1Bb+q4aM= 43 | golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 44 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 45 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 46 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 47 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 48 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 49 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 50 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 51 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 52 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 53 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 54 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 55 | -------------------------------------------------------------------------------- /internal/book/book.go: -------------------------------------------------------------------------------- 1 | package book 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elliotforbes/go-fiber-tutorial/internal/database" 7 | "github.com/gofiber/fiber" 8 | "github.com/jinzhu/gorm" 9 | ) 10 | 11 | type Book struct { 12 | gorm.Model 13 | ISIN int `json:"ISIN"` 14 | Title string `json:"title"` 15 | Author string `json:"author"` 16 | Rating int `json:"rating"` 17 | } 18 | 19 | func GetBooks(c *fiber.Ctx) { 20 | db := database.DBConn 21 | var books []Book 22 | db.Find(&books) 23 | c.JSON(books) 24 | } 25 | 26 | func GetBook(c *fiber.Ctx) { 27 | id := c.Params("id") 28 | db := database.DBConn 29 | var book Book 30 | db.Find(&book, id) 31 | c.JSON(book) 32 | } 33 | 34 | func NewBook(c *fiber.Ctx) { 35 | db := database.DBConn 36 | 37 | book := new(Book) 38 | if err := c.BodyParser(book); err != nil { 39 | c.Status(503).Send(err) 40 | return 41 | } 42 | 43 | db.Create(&book) 44 | c.JSON(book) 45 | } 46 | 47 | func DeleteBook(c *fiber.Ctx) { 48 | ISIN := c.Params("isin") 49 | db := database.DBConn 50 | 51 | fmt.Println(ISIN) 52 | 53 | var book Book 54 | db.Find(&book, "ISIN = ?", ISIN) 55 | if book.Title == "" { 56 | c.Status(500).Send("No book found with given ID") 57 | return 58 | } 59 | db.Delete(&book) 60 | c.Send("Book successfully deleted") 61 | } 62 | -------------------------------------------------------------------------------- /internal/book/book_integration_test.go: -------------------------------------------------------------------------------- 1 | // +build integration 2 | 3 | package book_test 4 | 5 | import ( 6 | "encoding/json" 7 | "fmt" 8 | "io/ioutil" 9 | "os" 10 | "strconv" 11 | "strings" 12 | "testing" 13 | 14 | "net/http" 15 | "net/http/httptest" 16 | 17 | "github.com/elliotforbes/go-fiber-tutorial/internal/book" 18 | "github.com/elliotforbes/go-fiber-tutorial/internal/database" 19 | "github.com/elliotforbes/go-fiber-tutorial/internal/transport" 20 | "github.com/gofiber/fiber" 21 | "github.com/jinzhu/gorm" 22 | _ "github.com/jinzhu/gorm/dialects/sqlite" 23 | "github.com/stretchr/testify/assert" 24 | "github.com/stretchr/testify/suite" 25 | ) 26 | 27 | type BookTestSuite struct { 28 | suite.Suite 29 | dbConn *gorm.DB 30 | app *fiber.App 31 | } 32 | 33 | func (suite *BookTestSuite) SetupSuite() { 34 | var err error 35 | suite.dbConn, err = gorm.Open("sqlite3", "books.db") 36 | if err != nil { 37 | panic("Failed to connect to database") 38 | } 39 | fmt.Println("Database connection successfully opened") 40 | 41 | suite.app = transport.Setup() 42 | database.InitDatabase() 43 | database.DBConn.AutoMigrate(&book.Book{}) 44 | } 45 | 46 | func (suite *BookTestSuite) TearDownSuite() { 47 | err := os.Remove("books.db") 48 | if err != nil { 49 | 50 | } 51 | } 52 | 53 | func (suite *BookTestSuite) TestCreateBook() { 54 | req := httptest.NewRequest( 55 | "POST", 56 | "/api/v1/book", 57 | strings.NewReader(`{"ISIN": 12345, "title":"Test Book", "author": "Elliot", "rating": 5}`), 58 | ) 59 | req.Header.Add("Content-Type", "application/json") 60 | req.Header.Add("Content-Length", strconv.FormatInt(req.ContentLength, 10)) 61 | 62 | res, err := suite.app.Test(req, -1) 63 | 64 | assert.NoError(suite.T(), err) 65 | assert.Equal(suite.T(), http.StatusOK, res.StatusCode) 66 | 67 | var bookTest book.Book 68 | database.DBConn.Where("title = ?", "Test Book").First(&bookTest) 69 | fmt.Println(bookTest) 70 | assert.Equal(suite.T(), bookTest.Title, "Test Book") 71 | } 72 | 73 | func (suite *BookTestSuite) TestReadBook() { 74 | req := httptest.NewRequest( 75 | "GET", 76 | "/api/v1/book/1", 77 | nil, 78 | ) 79 | res, err := suite.app.Test(req, -1) 80 | assert.NoError(suite.T(), err) 81 | assert.Equal(suite.T(), http.StatusOK, res.StatusCode) 82 | 83 | var testbook book.Book 84 | body, _ := ioutil.ReadAll(res.Body) 85 | json.Unmarshal(body, &testbook) 86 | 87 | assert.Equal(suite.T(), "Test Book", testbook.Title) 88 | } 89 | 90 | func (suite *BookTestSuite) TestDeleteBook() { 91 | req := httptest.NewRequest( 92 | "POST", 93 | "/api/v1/book", 94 | strings.NewReader(`{"ISIN": 45678, "title":"Test Book", "author": "Elliot", "rating": 5}`), 95 | ) 96 | req.Header.Add("Content-Type", "application/json") 97 | req.Header.Add("Content-Length", strconv.FormatInt(req.ContentLength, 10)) 98 | 99 | res, err := suite.app.Test(req, -1) 100 | 101 | assert.NoError(suite.T(), err) 102 | assert.Equal(suite.T(), http.StatusOK, res.StatusCode) 103 | 104 | req = httptest.NewRequest( 105 | "DELETE", 106 | "/api/v1/book/45678", 107 | nil, 108 | ) 109 | res, err = suite.app.Test(req, -1) 110 | assert.NoError(suite.T(), err) 111 | assert.Equal(suite.T(), http.StatusOK, res.StatusCode) 112 | 113 | body, _ := ioutil.ReadAll(res.Body) 114 | fmt.Println(string(body)) 115 | } 116 | 117 | func TestBookTestSuite(t *testing.T) { 118 | suite.Run(t, new(BookTestSuite)) 119 | } 120 | -------------------------------------------------------------------------------- /internal/database/database.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/jinzhu/gorm" 7 | _ "github.com/jinzhu/gorm/dialects/sqlite" 8 | ) 9 | 10 | var ( 11 | DBConn *gorm.DB 12 | ) 13 | 14 | // InitDatabase - 15 | func InitDatabase() { 16 | var err error 17 | DBConn, err = gorm.Open("sqlite3", "books.db") 18 | if err != nil { 19 | panic("Failed to connect to database") 20 | } 21 | fmt.Println("Database connection successfully opened") 22 | } 23 | -------------------------------------------------------------------------------- /internal/transport/transport.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | "github.com/elliotforbes/go-fiber-tutorial/internal/book" 5 | "github.com/gofiber/fiber" 6 | ) 7 | 8 | func helloWorld(c *fiber.Ctx) { 9 | c.Send("Hello, World!") 10 | } 11 | 12 | func setupRoutes(app *fiber.App) { 13 | app.Get("/", helloWorld) 14 | app.Get("/api/v1/book", book.GetBooks) 15 | app.Get("/api/v1/book/:isin", book.GetBook) 16 | app.Post("/api/v1/book", book.NewBook) 17 | app.Delete("/api/v1/book/:isin", book.DeleteBook) 18 | } 19 | 20 | // Setup - set's up our fiber app and the routes 21 | // returns a pointer to app 22 | func Setup() *fiber.App { 23 | app := fiber.New() 24 | setupRoutes(app) 25 | return app 26 | } 27 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/elliotforbes/go-fiber-tutorial/internal/database" 5 | "github.com/elliotforbes/go-fiber-tutorial/internal/transport" 6 | 7 | _ "github.com/jinzhu/gorm/dialects/sqlite" 8 | ) 9 | 10 | func main() { 11 | app := transport.Setup() 12 | database.InitDatabase() 13 | app.Listen(3000) 14 | } 15 | --------------------------------------------------------------------------------