Golang RESTful API using GORM and Gorilla Mux
REST stands for Representational State Transfer. The name Representational state transfer (REST) was coined by Roy Fielding from the University of California. It is a very streamlined and lightweight web service compared to SOAP or WSDL. Performance, scalability, simplicity, portability are the core principles behind the REST API.
The essence of the REST architecture comprises a client and a server. The REST API allows diverse systems to connect and send/receive data in the direct way. The server accepts the incoming messages, then replies to it, while the client creates the connection, then delivers messages to the server.
The RESTful client would be an HTTP client, and the RESTful server would be the HTTP server. Each and every REST API call has a relationship between an HTTP verb and the URL. The reserves (data or business logic) in the database in an application can be defined with an API endpoint in the REST.
Installing the GORM, Gorilla Mux and MySQL package is fairly simple. You just need to run below three commands using GIT Bash or Putty:
go get -u github.com/gorilla/mux
go get -u github.com/jinzhu/gorm
go get -u github.com/go-sql-driver/mysql
You will found 3 new folders inside the github.com folder.
Now, we are ready to go. Let's a create new api folder inside the github.com folder. Inside api folder we will create our RESTful API app.
Inside model create a file called model.go and add the following code:
package model
import (
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
)
type Employee struct {
gorm.Model
Name string `gorm:"unique" json:"name"`
City string `json:"city"`
Age int `json:"age"`
Status bool `json:"status"`
}
func (e *Employee) Disable() {
e.Status = false
}
func (p *Employee) Enable() {
p.Status = true
}
// DBMigrate will create and migrate the tables, and then make the some relationships if necessary
func DBMigrate(db *gorm.DB) *gorm.DB {
db.AutoMigrate(&Employee{})
return db
}
Create a file called common.go and add the following code:
package handler
import (
"encoding/json"
"net/http"
)
// respondJSON makes the response with payload as json format
func respondJSON(w http.ResponseWriter, status int, payload interface{}) {
response, err := json.Marshal(payload)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
w.Write([]byte(response))
}
// respondError makes the error response with payload as json format
func respondError(w http.ResponseWriter, code int, message string) {
respondJSON(w, code, map[string]string{"error": message})
}
Create a file called employees.go and add the following code:
package handler
import (
"encoding/json"
"net/http"
"github.com/api/app/model"
"github.com/gorilla/mux"
"github.com/jinzhu/gorm"
)
func GetAllEmployees(db *gorm.DB, w http.ResponseWriter, r *http.Request) {
employees := []model.Employee{}
db.Find(&employees)
respondJSON(w, http.StatusOK, employees)
}
func CreateEmployee(db *gorm.DB, w http.ResponseWriter, r *http.Request) {
employee := model.Employee{}
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&employee); err != nil {
respondError(w, http.StatusBadRequest, err.Error())
return
}
defer r.Body.Close()
if err := db.Save(&employee).Error; err != nil {
respondError(w, http.StatusInternalServerError, err.Error())
return
}
respondJSON(w, http.StatusCreated, employee)
}
func GetEmployee(db *gorm.DB, w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["name"]
employee := getEmployeeOr404(db, name, w, r)
if employee == nil {
return
}
respondJSON(w, http.StatusOK, employee)
}
func UpdateEmployee(db *gorm.DB, w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["name"]
employee := getEmployeeOr404(db, name, w, r)
if employee == nil {
return
}
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&employee); err != nil {
respondError(w, http.StatusBadRequest, err.Error())
return
}
defer r.Body.Close()
if err := db.Save(&employee).Error; err != nil {
respondError(w, http.StatusInternalServerError, err.Error())
return
}
respondJSON(w, http.StatusOK, employee)
}
func DeleteEmployee(db *gorm.DB, w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["name"]
employee := getEmployeeOr404(db, name, w, r)
if employee == nil {
return
}
if err := db.Delete(&employee).Error; err != nil {
respondError(w, http.StatusInternalServerError, err.Error())
return
}
respondJSON(w, http.StatusNoContent, nil)
}
func DisableEmployee(db *gorm.DB, w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["name"]
employee := getEmployeeOr404(db, name, w, r)
if employee == nil {
return
}
employee.Disable()
if err := db.Save(&employee).Error; err != nil {
respondError(w, http.StatusInternalServerError, err.Error())
return
}
respondJSON(w, http.StatusOK, employee)
}
func EnableEmployee(db *gorm.DB, w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["name"]
employee := getEmployeeOr404(db, name, w, r)
if employee == nil {
return
}
employee.Enable()
if err := db.Save(&employee).Error; err != nil {
respondError(w, http.StatusInternalServerError, err.Error())
return
}
respondJSON(w, http.StatusOK, employee)
}
// getEmployeeOr404 gets a employee instance if exists, or respond the 404 error otherwise
func getEmployeeOr404(db *gorm.DB, name string, w http.ResponseWriter, r *http.Request) *model.Employee {
employee := model.Employee{}
if err := db.First(&employee, model.Employee{Name: name}).Error; err != nil {
respondError(w, http.StatusNotFound, err.Error())
return nil
}
return &employee
}
Create a file called app.go and add the following code:
package app
import (
"fmt"
"log"
"net/http"
"github.com/api/app/handler"
"github.com/api/app/model"
"github.com/api/config"
"github.com/gorilla/mux"
"github.com/jinzhu/gorm"
)
// App has router and db instances
type App struct {
Router *mux.Router
DB *gorm.DB
}
// App initialize with predefined configuration
func (a *App) Initialize(config *config.Config) {
dbURI := fmt.Sprintf("%s:%s@/%s?charset=%s&parseTime=True",
config.DB.Username,
config.DB.Password,
config.DB.Name,
config.DB.Charset)
db, err := gorm.Open(config.DB.Dialect, dbURI)
if err != nil {
log.Fatal("Could not connect database")
}
a.DB = model.DBMigrate(db)
a.Router = mux.NewRouter()
a.setRouters()
}
// Set all required routers
func (a *App) setRouters() {
// Routing for handling the projects
a.Get("/employees", a.GetAllEmployees)
a.Post("/employees", a.CreateEmployee)
a.Get("/employees/{title}", a.GetEmployee)
a.Put("/employees/{title}", a.UpdateEmployee)
a.Delete("/employees/{title}", a.DeleteEmployee)
a.Put("/employees/{title}/disable", a.DisableEmployee)
a.Put("/employees/{title}/enable", a.EnableEmployee)
}
// Wrap the router for GET method
func (a *App) Get(path string, f func(w http.ResponseWriter, r *http.Request)) {
a.Router.HandleFunc(path, f).Methods("GET")
}
// Wrap the router for POST method
func (a *App) Post(path string, f func(w http.ResponseWriter, r *http.Request)) {
a.Router.HandleFunc(path, f).Methods("POST")
}
// Wrap the router for PUT method
func (a *App) Put(path string, f func(w http.ResponseWriter, r *http.Request)) {
a.Router.HandleFunc(path, f).Methods("PUT")
}
// Wrap the router for DELETE method
func (a *App) Delete(path string, f func(w http.ResponseWriter, r *http.Request)) {
a.Router.HandleFunc(path, f).Methods("DELETE")
}
// Handlers to manage Employee Data
func (a *App) GetAllEmployees(w http.ResponseWriter, r *http.Request) {
handler.GetAllEmployees(a.DB, w, r)
}
func (a *App) CreateEmployee(w http.ResponseWriter, r *http.Request) {
handler.CreateEmployee(a.DB, w, r)
}
func (a *App) GetEmployee(w http.ResponseWriter, r *http.Request) {
handler.GetEmployee(a.DB, w, r)
}
func (a *App) UpdateEmployee(w http.ResponseWriter, r *http.Request) {
handler.UpdateEmployee(a.DB, w, r)
}
func (a *App) DeleteEmployee(w http.ResponseWriter, r *http.Request) {
handler.DeleteEmployee(a.DB, w, r)
}
func (a *App) DisableEmployee(w http.ResponseWriter, r *http.Request) {
handler.DisableEmployee(a.DB, w, r)
}
func (a *App) EnableEmployee(w http.ResponseWriter, r *http.Request) {
handler.EnableEmployee(a.DB, w, r)
}
// Run the app on it's router
func (a *App) Run(host string) {
log.Fatal(http.ListenAndServe(host, a.Router))
}
Use your database connection credentials and create a file called config.go and add the following code:
package config
type Config struct {
DB *DBConfig
}
type DBConfig struct {
Dialect string
Username string
Password string
Name string
Charset string
}
func GetConfig() *Config {
return &Config{
DB: &DBConfig{
Dialect: "mysql",
Username: "root",
Password: "root",
Name: "employee",
Charset: "utf8",
},
}
}
Create a file called main.go and add the following code:
package main
import (
"github.com/api/app"
"github.com/api/config"
)
func main() {
config := config.GetConfig()
app := &app.App{}
app.Initialize(config)
app.Run(":3000")
}
Testing RESTful API methods
The GET method is a very common HTTP method in web applications used to retrieve a resource and should never be used to update any record. Typically, a body is never passed with a GET request to request a resource from our HTTP web servers.
Request
GET /employees HTTP/1.1
GET /employees/title HTTP/1.1
GET request type used in our scenario to request the data of all employees.
We used an HTML form to create a new resource via a non-idempotent action which we called POST method. The POST method to send data to the server either through an asynchronous call or to execute a controller.
Request
POST /employees/data HTTP/1.1
Content-Type: application/json
Content-Length: xxxx
{"name": "John", "age": 42, "city": "Tokyo"}
POST request type used in our scenario to create new employee record.
The PUT method is used to update a changeable resource and include the resource locator. Whenever we have to update a record that we have created earlier or want to create a record if it does not exist, using idempotent method calls which we refer as PUT method.
Request
PUT /employees/name HTTP/1.1
Content-Type: application/json
Content-Length: xxxx
{ "age": 30}
PUT request type used in our scenario to update employee age.
The DELETE method is always used to remove a record that is no longer required. In DELETE method we pass the ID of the resource as part of the path rather than in the body of the request.
Request
DELETE /employees/name HTTP/1.1
DELETE request type used in our scenario to remove employee record.