Build RESTful API using Go Gorilla Mux [In-Depth Tutorial]


Written By - Antony Shikubu
Advertisement

Introduction to Golang Gorilla Mux

Building RESTFul APIs in Go is easy considering the packages that can be used to build these API. Go has several API packages namely Go Fiber, Go Gin and Gorilla Mux being the most commonly used. There are already other articles for developing RESTFul API using Go Fiber and Go Gin as part of the building RESTFul API series. In this article, our focus will be building RESTFul API using the famous Gorilla Mux package.

The name “mux” stands for HTTP request Multiplex. It matches incoming requests against a list of registered routes in your application. When a match has been found, a handler function is called to handler the incoming request. The handler function is responsible for the business logic such as interacting with the database.

Gorilla Mux Features

  1. The incoming HTTP requests can be matched based on the URL host, path, path prefix, schema, header and query values, HTTP methods or using custom matchers.
  2. The registered  URLs can be built or “reversed” hence maintaining references to resources.
  3. URL host, paths and query values can have variables with an optional regular expression.
  4. Mux implements the http.Handler interface so it is compatible with standard http.ServeMux

 

Objectives

The main focus in this article is to learn how to perform the famous CRUD operations using Mux for routing and matching HTTP requests into our application. We will use PostgreSQL db as our datastore for storing albums.

In this application we will ;

  1. Create an album
  2. Read an album with id
  3. Read all albums in our database
  4. Update an albums with a given id
  5. Delete an album

 

Prerequisites

  1. Go runtime
  2. Familiarity with Go function and methods, data structures  etc
  3. Code editor , I prefer VS code but any IDE is fine
  4. Installed and working PostgreSQL DB with PGAdmin
  5. Installed and working Postman for testing out API endpoints

 

API design

It is always good practice to properly design your API endpoint URLs. The endpoint should be clear and concise.

  1. POST a album : /api/v1/albums
  2. GET a album : /api/v1/albums/{id}
  3. GET albums : /api/v1/albums
  4. PUT album : /api/v1/albums/{id}
  5. DELETE album /api/v1/albums/{id}

 

Application structure in my Environment

We will build a simple application with minimal destruction. These API will be built in a single go file, the main.go file. We will also have a .env file to store our database configurations.

Build RESTful API using Go Gorilla Mux [In-Depth Tutorial]

Using the terminal, navigate to your workspace and issue the below commands.

Advertisement

Create root folder

$ mkdir go-mux-api && cd go-mux-api

Create main.go and .env file

$ touch main.go .env

With that cleared out, next thing to do is initialize a module and install dependencies.

Initialize go module

$ go mod init example.com/go-mux-api

Install dependencies

$ go get -u github.com/gorilla/mux
$ go get github.com/joho/godotenv
$ go get github.com/jinzhu/gorm

After issuing the above commands, two new files will be added in your application namely go.mod and go.sum. They will be used for dependency management in our application.

Before starting off , we need to import dependencies into our main.go file. We also need to setup our database , environment variables then start wring out CRUD functions

 

Setting up environmental variables

The .env file is responsible for storing the database configuration variables. This is so that we do not hard code this sensitive information in our code. When our runs, it will look for the .env file and load the environmental variables into the operating system. We will use github.com/joho/godotenv package to read these environmental variables into our application. These environmental variables might not be the same for you but the database port and the database host will be. Add environmental variables that match your database configurations.

.env file

DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_NAME=postgres
DB_PASSWORD=topsecret

 

Setting up the database

In this section , we will set up the database connection to our application based on the environmental variables. We will also define our Album schema using a struct. Please add the below code in your main.go file.

package main
 
import (
   "encoding/json"
   "fmt"
   "log"
   "net/http"
   "os"
 
   "github.com/gorilla/mux"
   "github.com/jinzhu/gorm"
   _ "github.com/jinzhu/gorm/dialects/postgres"
   "github.com/joho/godotenv"
)
 
var (
   db  *gorm.DB
   err error
)
 
// Model
type Album struct {
   gorm.Model
   Title  string `json:"title"`
   Author string `json:"author"`
}
 
func init() {
   var (
       host     = getEnvVariable("DB_HOST")
       port     = getEnvVariable("DB_PORT")
       user     = getEnvVariable("DB_USER")
       dbname   = getEnvVariable("DB_NAME")
       password = getEnvVariable("DB_PASSWORD")
   )
 
   conn := fmt.Sprintf("host=%s port=%s user=%s dbname=%s password=%s sslmode=disable",
       host,
       port,
       user,
       dbname,
       password,
   )
 
   db, err = gorm.Open("postgres", conn)
db.AutoMigrate(Album{})
 
   if err != nil {
       log.Fatal(err)
   }
}
 
func getEnvVariable(key string) string {
   err := godotenv.Load(".env")
   if err != nil {
       log.Fatal("Error loading .env file", err)
   }
   return os.Getenv(key)
}

The code in this main.go file starts with importing the dependencies. The “encoding/json”  will be used to encode and decode our album to JSON and vice versa.  The fmt and log packages will be used to log messages coming from the server.  The os package will give access to the environmental variables.The rest of the import will be used to build our API except for github.com/jinzhu/gorm/dialects/postgres which will be used to tell the database/sql what database we will be using and for this tutorial we will use postgres.

After importing the dependencies, we declare a db and err variable that will be assigned actual values when the application boots up.

Album struct {}: We then define our database schema using a go struct type Album struct{} . This struct defines the fields that are required for each album.  Each album will have a title and author field. The gorm.Model will include an album id by default whenever a new album is created.

After defining our data schema , we set up our database in the init() function. Whenever our application boots up, the init() function will be called first before anything else thus setting up the database before anything else. When setting up the database, we need to pass the supplied environmental variables to the connection string using the string conn := fmt.Sprintf("host=%s port=%s user=%s dbname=%s password=%s sslmode=disable" ….

We then assign the db and err variables to thegorm.Open()using the statement db, err = gorm.Open("postgres", conn). We will use the db variable to interact directly with the database.

If an error occurs while connecting to the database, we log the message to the terminal and stop the application from running.

 

Create CRUD operation Handlers

In this section we define the CRUD functions. Each handler takes two arguments from the http package. The first argument is the response writer that creates a response after a request. The response writer is responsible for setting response headers as part of modeling the response. The second  is the request coming into the handler. It contains the URL parameters and body of the request.

Create album

func PostAlbum(w http.ResponseWriter, r *http.Request) {
   w.Header().Set("Content-Type", "application/json")
   var album Album
   json.NewDecoder(r.Body).Decode(&album)
   db.Create(&album)
   json.NewEncoder(w).Encode(album)
}

The create album handler function is responsible for taking the data from the client , parsing the data and storing the data into the database. We start off by setting the response headers to json using the statement  w.Header().Set("Content-Type", "application/json"). We then declare an empty albums struct var album Albumthat will be assigned the data for an album. Using the json package we decode the request body into the declared album struct using the statement json.NewDecoder(r.Body).Decode(&album). After a successful decoding we write the new album into the database using the statement db.Create(&album). At the end we return the created album to the client using the statement json.NewEncoder(w).Encode(album).

 

Read album

func GetAlbum(w http.ResponseWriter, r *http.Request) {
   var album Album
   id := mux.Vars(r)["id"]
   db.First(&album, id)
   if album.ID == 0 {
       json.NewEncoder(w).Encode("album not found!")
       return
   }
   w.Header().Set("Content-Type", "application/json")
   json.NewEncoder(w).Encode(album)
}

To read an album we get the id of the album using id := mux.Vars(r)[“id”] statement.We search for the album with the given id using the statement db.First(&album, id) and return an error to the client id the album is not found. Else we return the found album.

 

Read All albums

func GetAlbums(w http.ResponseWriter, r *http.Request) {
   var albums []*Album
   db.Find(&albums)
   w.Header().Set("Content-Type", "application/json")
   w.WriteHeader(http.StatusOK)
   json.NewEncoder(w).Encode(albums)
}

This handler function returns all the albums in the database. The statement db.Find(&album) reads all the albums from the album table in the database and returns them to the client.

 

Update album

func UpdateAlbum(w http.ResponseWriter, r *http.Request) {
   var album Album
   id := mux.Vars(r)["id"]
   db.First(&album, id)
   if album.ID == 0 {
       json.NewEncoder(w).Encode("album not found!")
       return
   }
   json.NewDecoder(r.Body).Decode(&album)
   db.Save(&album)
   w.Header().Set("Content-Type", "application/json")
   json.NewEncoder(w).Encode(album)
}

To update an album, we first of all get the id of the album to edit from the URL parameters  and use this id to get the album to edit. If the album is not found we return an error message to the client else we proceed to get the new data using request using the r.Body statement in the json.NewDecoder(r.Body).Decode(&album). After a successful decoding , we save the new album in the database using the statement db.Save(&album) and return the updated album to the client.

 

Delete albums

func DeleteAlbum(w http.ResponseWriter, r *http.Request) {
   var album Album
   id := mux.Vars(r)["id"]
   db.First(&album, id)
   if album.ID == 0 {
       json.NewEncoder(w).Encode("album not found!")
       return
   }
   db.Delete(&album, id)
   w.Header().Set("Content-Type", "application/json")
   json.NewEncoder(w).Encode("album deleted successfully")
}

This handle takes an id of the album to delete and deletes it from the database if the id exists else returns an error to the client. In order to delete an album, we call the db.Delete() function from the database and pass it the album and id to delete.

 

Route handling and running the server

func main() {
   r := mux.NewRouter()
   r.HandleFunc("/", Home).Methods("GET")
   r.HandleFunc("/api/v1/albums", PostAlbum).Methods("POST")
   r.HandleFunc("/api/v1/albums", GetAlbums).Methods("GET")
   r.HandleFunc("/api/v1/albums/{id}", GetAlbum).Methods("GET")
   r.HandleFunc("/api/v1/albums/{id}", UpdateAlbum).Methods("PUT")
   r.HandleFunc("/api/v1/albums/{id}", DeleteAlbum).Methods("DELETE")
   fmt.Println("Listening and serving on http://localhost:5000")
   log.Fatal(http.ListenAndServe(":5000", r))
}

In this section, we use the Mux routing and matching capabilities to handle different HTTP request types. One thing to note is we start off by declaring a router using  r := mux.NewRouter(). This statement returns a router that gives us access to the HandleFunc() method. The HandleFunc() method takes the URL string and matches it with the incoming request. If a match has been found, the respective handle function is called. Each HandleFunc() method chains the Methods() method. The Methods method takes a string which defines the HTTP method such as POST, GET, PUT and DELETE.

To expose these endpoints, we spin the server that listens on port 5000. Please note that we are using the http package to serve requests using the statement http.ListenAndServe(). The ListenAndServe() method takes two arguments, the port as string and the router “r” to serve .

 

Test The API endpoints

To test out CRUD operations, we are going to make use of the Postman application, please load it up and perform the below operations.

Post album

Build RESTful API using Go Gorilla Mux [In-Depth Tutorial]

 

Get an album

Build RESTful API using Go Gorilla Mux [In-Depth Tutorial]

 

Get all albums

Build RESTful API using Go Gorilla Mux [In-Depth Tutorial]

 

Update album

Build RESTful API using Go Gorilla Mux [In-Depth Tutorial]

 

Delete album

Build RESTful API using Go Gorilla Mux [In-Depth Tutorial]

 

Summary

Gorilla Mux is one of the most popular Go API frameworks around. This article illustrates how easy it is to create and run a Mux API that fetches data from a postgreSQL database and efficiently performs CRUD RESTFul operations.

 

References

https://pkg.go.dev/github.com/gorilla/mux
https://zetcode.com/golang/gorilla-mux
gorilla/mux: A powerful HTTP router and URL

 

Categories GO

Can't find what you're searching for? Let us assist you.

Enter your query below, and we'll provide instant results tailored to your needs.

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can either use the comments section or contact me form.

Thank You for your support!!

Leave a Comment