Manage your friends with a microservice in Golang

Published 03-07-2018 00:00:00

In this tutorial I will illustrate how you can build your own RESTful API in Go and MongoDB.

API Specification

The REST API service will expose endpoints to manage a list of friends. The operations that our endpoints will allow are:

GET /friends  Get list of friends
GET /friends/:id Find a friends by id
POST /friends  Create new friend
PUT /firends Update a friend
DELETE /firends Delete a friend

Fetching Dependencies

Before we begin, we need to get the packages we need to setup the API:

go get github.com/BurntSushi/toml gopkg.in/mgo.v2 github.com/gorilla/mux
  • toml : Parse the configuration file (MongoDB server & credentials)
  • mux : Request router and dispatcher for matching incoming requests to their respective handler
  • mgo : MongoDB driver

API structure

Once the dependencies are installed, we create a file called “app.go“, with the following content:

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/gorilla/mux"
)

func AllFriendsEndPoint(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "not implemented yet !")
}

func FindFriendEndpoint(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "not implemented yet !")
}

func CreateFriendEndPoint(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "not implemented yet !")
}

func UpdateFriendEndPoint(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "not implemented yet !")
}

func DeleteFriendEndPoint(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "not implemented yet !")
}

func main() {
	r := mux.NewRouter()
	r.HandleFunc("/friends", AllFriendsEndPoint).Methods("GET")
	r.HandleFunc("/friends", CreateFriendEndPoint).Methods("POST")
	r.HandleFunc("/friends", UpdateFriendEndPoint).Methods("PUT")
	r.HandleFunc("/friends", DeleteFriendEndPoint).Methods("DELETE")
	r.HandleFunc("/friends/{id}", FindFriendEndpoint).Methods("GET")
	if err := http.ListenAndServe(":3000", r); err != nil {
		log.Fatal(err)
	}
}

The code above creates a controller for each endpoint, then expose an HTTP server on port 3000.

Note: We are using GET, POST, PUT, and DELETE where appropriate. We are also defining parameters that can be passed in

To run the server in local, type the following command:

go run app.go

If you point your browser to http://localhost:3000/friends, you should see:

Model

Now that we have a minimal application, it’s time to create a basic Friend model. In Go, we use struct keyword to create a model:

type Friend struct {
	ID          bson.ObjectId `bson:"_id" json:"id"`
	Name        string        `bson:"name" json:"name"`
	Picture  string        `bson:"friend_picture" json:"friend_picture"`
	Description string        `bson:"description" json:"description"`
}

Next, we will create the Data Access Object to manage database operations.

Data Access Object

Establish Connection

package dao

import (
	"log"

	"github.com/mlabouardy/Friends-restapi/models"
	mgo "gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

type FriendsDAO struct {
	Server   string
	Database string
}

var db *mgo.Database

const (
	COLLECTION = "Friends"
)

func (m *FriendsDAO) Connect() {
	session, err := mgo.Dial(m.Server)
	if err != nil {
		log.Fatal(err)
	}
	db = session.DB(m.Database)
}

The connect() method as its name implies, establish a connection to MongoDB database.

Database Queries

The implementation is relatively straighforward and just includes issuing right method using db.C(COLLECTION) object and returning the results. These methods can be implemented as follows:

func (m *FriendsDAO) FindAll() ([]Friend, error) {
	var Friends []Friend
	err := db.C(COLLECTION).Find(bson.M{}).All(&Friends)
	return Friends, err
}

func (m *FriendsDAO) FindById(id string) (Friend, error) {
	var Friend Friend
	err := db.C(COLLECTION).FindId(bson.ObjectIdHex(id)).One(&Friend)
	return Friend, err
}

func (m *FriendsDAO) Insert(Friend Friend) error {
	err := db.C(COLLECTION).Insert(&Friend)
	return err
}

func (m *FriendsDAO) Delete(Friend Friend) error {
	err := db.C(COLLECTION).Remove(&Friend)
	return err
}

func (m *FriendsDAO) Update(Friend Friend) error {
	err := db.C(COLLECTION).UpdateId(Friend.ID, &Friend)
	return err
}

Setup API Endpoints

Create a Friend

Update the CreateFriendEndpoint method as follows:

func CreateFriendEndPoint(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()
	var Friend Friend
	if err := json.NewDecoder(r.Body).Decode(&Friend); err != nil {
		respondWithError(w, http.StatusBadRequest, "Invalid request payload")
		return
	}
	Friend.ID = bson.NewObjectId()
	if err := dao.Insert(Friend); err != nil {
		respondWithError(w, http.StatusInternalServerError, err.Error())
		return
	}
	respondWithJson(w, http.StatusCreated, Friend)
}

It decodes the request body into a Friend object, assign it an ID, and uses the DAO Insert method to create a Friend in database.

Let’s test it out with cURL

curl -sSX POST -d ‘{“name”:JohnDoe,”friend_picture”:”https://i2.wp.com/thetempest.co/wp-content/uploads/2016/09/Friends-TV-show-on-NBC-canceled-no-season-11.jpg", “description”:”Friends”}’ http://localhost:3000/friends | jq ‘.’

#### List of Friends

The code below is self explanatory:

func AllFriendsEndPoint(w http.ResponseWriter, r *http.Request) {
	Friends, err := dao.FindAll()
	if err != nil {
		respondWithError(w, http.StatusInternalServerError, err.Error())
		return
	}
	respondWithJson(w, http.StatusOK, Friends)
}

It uses FindAll method of DAO to fetch list of Friends from database.

Let’s test it out with cURL:

curl -sSX GET http://localhost:3000/friends | jq ‘.’

#### Find a Friend

We will use the mux library to get the parameters that the users passed in with the request:

func FindFriendEndpoint(w http.ResponseWriter, r *http.Request) {
	params := mux.Vars(r)
	Friend, err := dao.FindById(params["id"])
	if err != nil {
		respondWithError(w, http.StatusBadRequest, "Invalid Friend ID")
		return
	}
	respondWithJson(w, http.StatusOK, Friend)
}

Let’s test it out with cURL:

curl -sSX GET http://localhost:3000/friends/599570faf0429b4494cfa5d4 | jq ‘.’

#### Update an existing Friend

Update the UpdateFriendEndPoint method as follows:

func UpdateFriendEndPoint(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()
	var Friend Friend
	if err := json.NewDecoder(r.Body).Decode(&Friend); err != nil {
		respondWithError(w, http.StatusBadRequest, "Invalid request payload")
		return
	}
	if err := dao.Update(Friend); err != nil {
		respondWithError(w, http.StatusInternalServerError, err.Error())
		return
	}
	respondWithJson(w, http.StatusOK, map[string]string{"result": "success"})
}

Let’s test it out with cURL:

curl -sSX PUT -d ‘{“name”:JohnDoe,”friend_picture”:”https://i2.wp.com/thetempest.co/wp-content/uploads/2016/09/Friends-TV-show-on-NBC-canceled-no-season-11.jpg", “description”:”Friends”}’ http://localhost:3000/friends | jq ‘.’

Delete an existing Friend

Update the DeleteFriendEndPoint method as follows:

func DeleteFriendEndPoint(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()
	var Friend Friend
	if err := json.NewDecoder(r.Body).Decode(&Friend); err != nil {
		respondWithError(w, http.StatusBadRequest, "Invalid request payload")
		return
	}
	if err := dao.Delete(Friend); err != nil {
		respondWithError(w, http.StatusInternalServerError, err.Error())
		return
	}
	respondWithJson(w, http.StatusOK, map[string]string{"result": "success"})
}

Let’s test it out with cURL:

curl -sSX DELETE -d ‘{“name”:JohnDoe,”friend_picture”:”https://i2.wp.com/thetempest.co/wp-content/uploads/2016/09/Friends-TV-show-on-NBC-canceled-no-season-11.jpg", “description”:”Friends”}’ http://localhost:3000/Friends | jq ‘.’