An End to End Go REST api with MySql
An end to end Go REST api with MySql.
Go is a powerful programing language and its gaining popularity among developers comunity.
This article will serve as a good start to understand how to implement a REST api using Go
with Mysql
.
Github link for the project is available here
Proposed functionality
This api exposes endpoints for an Order
service, which includes basic CRUD operations as follows:
- [GET] /order/{id}
- [GET] /orders
- [POST] /order
- [PUT] /order/{id}
- [DELETE] /order/{id}
Database script
We will create a simple MySql table called orders
with following fields
1. id - int type auto incremented column also serve as a Primary Key
2. title - title of the order
3. status - status of the order
The Db script to create a table:
CREATE TABLE orders (
id int not null auto_increment,
title varchar(50) not null,
status bool,
constraint pk_example primary key (id)
);
Dependencies
- Gorilla Mux Routes mux
- MySql driver mysql
go get github.com/gorilla/mux github.com/go-sql-driver/mysql
Note: Install mysql to $GOPATH in case it gives any problem
Scaffolding
This is how your project structure should look like
┌── app.go
├── main.go
└── dbmodel.go
The app.go file will hold reference to our main libraries
app.go
type App struct {
Router *mux.Router
Db *sql.DB
}
Add two functions called Init and Start as follows:
app.go
func (a *App) Init(user,password,db string){
}
func (a *App) Start(route string) {
}
Init will initialize a Db connection and Start will begin execution of our application
The final app.go looks like this
package main
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
"github.com/gorilla/mux"
)
//template structure
type App struct {
Router *mux.Router
Db *sql.DB
}
//functions of App struct
func (a *App) Init(user,password,db string){
}
func (a *App) Start(route string) {
}
We will add three environment variables called DB_USER_NAME
,DB_PASSWORD
and DB_NAME
.
Now we will create a main.go file which is the entry point for our application:
main.go
package main
import "os"
func main() {
a := App()
a.Init(os.Getenv("DB_USER_NAME"),
os.Getenv("DB_PASSWORD"),
os.Getenv("DB_NAME"))
a.Start(":8000")
}
Lets now add a db model representing our table order. We will also add the basic CRUD methods for the order entity
dbmodel.go
package main
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
type order struct{
Id int `json:"id"`
Title string `json:"title"`
Status bool `json:"status"`
}
func (o *order) createOrder(repository *sql.DB) error{
}
func (o *order) getOrderById(repository *sql.DB) (order,error){
}
func (o *order) updateOrder(repository *sql.DB) error{
}
func (o *order) getOrder(repository *sql.DB) ([]order,error){
}
func (o *order) deleteOrder(repository *sql.DB) error{
}
Lets add some real code for the database access layer
dbmodel.go
package main
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
type order struct{
Id int `json:"id"`
Title string `json:"title"`
Status bool `json:"status"`
}
func (o *order) createOrder(repository *sql.DB) error{
insert,err := repository.Query("INSERT INTO orders(title,status) VALUES(?,?)",o.Title,o.Status)
if err!=nil{
return err
}
defer insert.Close()
return nil
}
func (o *order) updateOrder(repository *sql.DB) error{
_, err := repository.Exec("UPDATE orders SET title=?, status=? WHERE id=?",o.Title,o.Status,o.Id)
if err!=nil{
return err
}
return nil
}
func (o *order) getOrderById(repository *sql.DB) (order,error){
var obj order
err := repository.QueryRow("SELECT * FROM orders WHERE id=?",o.Id).Scan(&obj.Id,&obj.Title,&obj.Status)
if err!=nil{
return obj,err
}
return obj,nil
}
func getOrders(repository *sql.DB) ([]order,error){
orders := []order{}
rows,err := repository.Query("SELECT * from orders")
if err!=nil{
return orders,err
}
defer rows.Close()
for rows.Next() {
var temp order
err = rows.Scan(&temp.Id,&temp.Title,&temp.Status)
checkErr(err)
orders = append(orders, temp)
}
return orders, nil
}
func (o *order) deleteOrder(repository *sql.DB) error{
_, err := repository.Exec("DELETE FROM orders WHERE id=?",o.Id)
if err!=nil{
return err
}
return nil
}
Now lets add endpoints and handlers
Create Order
app.go
func (a *App) createOrder(w http.ResponseWriter, r *http.Request){
var o order
decoder := json.NewDecoder(r.Body)
if err:= decoder.Decode(&o); err!= nil{
logError(w, http.StatusBadRequest,"invalid json")
return
}
defer r.Body.Close()
if err := o.createOrder(a.Db); err!= nil{
logError(w, http.StatusInternalServerError,err.Error())
return
}
jsonResponse(w, http.StatusCreated, o)
}
Get Order
app.go
func (a *App) getOrder(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err !=nil{
logError(w,http.StatusBadRequest,err.Error())
return
}
o := order{Id:id}
obj,err:= o.getOrderById(a.Db)
if err != nil {
logError(w, http.StatusBadRequest, err.Error())
return
}
jsonResponse(w,http.StatusOK,obj)
}
Get Order(s)
app.go
func (a *App) getOrders(w http.ResponseWriter,r *http.Request) {
orders, err := getOrders(a.Db)
if err!=nil{
logError(w,http.StatusBadRequest,"Some error")
}
jsonResponse(w,http.StatusOK,orders)
}
Update Order
app.go
func (a *App) updateOrder(w http.ResponseWriter, r *http.Request){
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
logError(w, http.StatusBadRequest, "Invalid id")
return
}
var o order
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&o); err != nil {
logError(w, http.StatusBadRequest, "Invalid resquest")
return
}
defer r.Body.Close()
o.Id = id
if err := o.updateOrder(a.Db); err != nil {
logError(w, http.StatusInternalServerError, err.Error())
return
}
jsonResponse(w, http.StatusOK, o)
}
Delete Order
app.go
func (a *App) deleteOrder(w http.ResponseWriter, r *http.Request){
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
logError(w, http.StatusBadRequest, "Invalid id")
return
}
o := order{Id:id}
if err := o.deleteOrder(a.Db); err != nil {
logError(w, http.StatusInternalServerError, err.Error())
return
}
jsonResponse(w, http.StatusOK, map[string]string{"message": "deleted successfully"})
}
Some helper methods
These helper methods will be used to log errors and respond back to the caller
app.go
func logError(w http.ResponseWriter,code int,message string) {
jsonResponse(w, code, map[string]string{"error": message})
}
func jsonResponse(w http.ResponseWriter, code int, payload interface{}) {
response, _ := json.Marshal(payload)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
w.Write(response)
}
With this simply run the api and test it with tools like Postman/Insomnia
Thank You!