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 ‘.’