Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Build a RESTful API in Go and MongoDB

DZone's Guide to

Build a RESTful API in Go and MongoDB

Learn the steps for building your own RESTful API using Go and MongoDB, and a demonstration of how to navigate and use it, in this tutorial.

· Integration Zone ·
Free Resource

Learn how integrating security into DevOps to deliver "DevSecOps" requires changing mindsets, processes and technology.

In this post, I will illustrate how you can build your own RESTful API in Go and MongoDB. All the code used in this demo can be found on my GitHub.

1 — API Specification

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

2 — Fetching Dependencies

Before we begin, we need to get the packages we need to set up 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

3 — 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 AllMoviesEndPoint(w http.ResponseWriter, r * http.Request) {
    fmt.Fprintln(w, "not implemented yet !")
}

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

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

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

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

func main() {
    r: = mux.NewRouter()
    r.HandleFunc("/movies", AllMoviesEndPoint).Methods("GET")
    r.HandleFunc("/movies", CreateMovieEndPoint).Methods("POST")
    r.HandleFunc("/movies", UpdateMovieEndPoint).Methods("PUT")
    r.HandleFunc("/movies", DeleteMovieEndPoint).Methods("DELETE")
    r.HandleFunc("/movies/{id}", FindMovieEndpoint).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 DELETEwhere 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/movies, you should see:

4 — Model

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

type Movie struct {
    ID bson.ObjectId `bson:"_id" json:"id"`
    Name string `bson:"name" json:"name"`
    CoverImage string `bson:"cover_image" json:"cover_image"`
    Description string `bson:"description" json:"description"`
}

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

5 — Data Access Object

5.1 — Establish Connection

package dao

import (
"log"

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

type MoviesDAO struct {
Server   string
Database string
}

var db *mgo.Database

const (
COLLECTION = "movies"
)

func (m *MoviesDAO) 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.

5.2 — Database Queries

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

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

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

func(m * MoviesDAO) Insert(movie Movie) error {
    err: = db.C(COLLECTION).Insert( & movie)
    return err
}

func(m * MoviesDAO) Delete(movie Movie) error {
    err: = db.C(COLLECTION).Remove( & movie)
    return err
}

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

6 — Set Up API Endpoints

6.1 — Create a Movie

Update the CreateMovieEndpoint method as follows:

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

It decodes the request body into a movie object, assigns it an ID, and uses the DAOInsert method to create a movie in the database.

Let’s test it out:

  • With cURL:

curl -sSX POST -d ‘{“name”:”dunkirk”,”cover_image”:”https://image.tmdb.org/t/p/w640/cUqEgoP6kj8ykfNjJx3Tl5zHCcN.jpg", “description”:”world war 2 movie”}’ http://localhost:3000/movies | jq ‘.’

6.2 — List of Movies

The code below is self-explanatory:

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

It uses the FindAll method of DAO to fetch a list of movies from the database.

Let’s test it out:

  • With cURL:

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

6.3 — Find a Movie

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

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


Let’s test it out:

  • With cURL:

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

6.4 — Update an Existing Movie

Update the UpdateMovieEndPoint method as follows:

func UpdateMovieEndPoint(w http.ResponseWriter, r * http.Request) {
    defer r.Body.Close()
    var movie Movie
    if err: = json.NewDecoder(r.Body).Decode( & movie);
    err != nil {
        respondWithError(w, http.StatusBadRequest, "Invalid request payload")
        return
    }
    if err: = dao.Update(movie);
    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”:”dunkirk”,”cover_image”:”https://image.tmdb.org/t/p/w640/cUqEgoP6kj8ykfNjJx3Tl5zHCcN.jpg", “description”:”world war 2 movie”}’ http://localhost:3000/movies | jq ‘.’

6.5 — Delete an Existing Movie

Update the DeleteMovieEndPoint method as follows:

func DeleteMovieEndPoint(w http.ResponseWriter, r * http.Request) {
    defer r.Body.Close()
    var movie Movie
    if err: = json.NewDecoder(r.Body).Decode( & movie);
    err != nil {
        respondWithError(w, http.StatusBadRequest, "Invalid request payload")
        return
    }
    if err: = dao.Delete(movie);
    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”:”dunkirk”,”cover_image”:”https://image.tmdb.org/t/p/w640/cUqEgoP6kj8ykfNjJx3Tl5zHCcN.jpg", “description”:”world war 2 movie”}’ http://localhost:3000/movies | jq ‘.’

Taking this further? On my upcoming posts, I will show you how to:

  • Write Unit Tests in Go for each Endpoint
  • Build a UI in Angular 4
  • Setup a CI/CD with CircleCI
  • Deploy the stack on AWS, and much more…

So stay tuned!

Learn how enterprises are using tools to automate security in their DevOps toolchain with these DevSecOps Reference Architectures.

Topics:
golang ,rest api ,mongodb ,api ,integration

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}