Building Microservice Applications with Go

Cihan Ozhan
5 min readNov 8, 2021

Building Microservice Applications with Go

I wrote this code in 2016 but re-shared it for newbies.

The technologies we use in this project are;

The purpose of this article;

  • Development microservice applications with Go
  • Learning the Goji with Go
  • Understanding the use of MongoDB with Go

Preparation;

  • Start MongoDB service

Lets start!

First, we prepare the model layer;

models/Page.go

package models

type Page struct {
Title string
Author string
Header string
PageDefinition string
Content string
URI string
}

models/Book.go

package models

type Book struct {
ISBN string `json:"isbn"`
Title string `json:"title"`
Authors []string `json:"authors"`
Price string `json:"price"`
}

Next, we are preparing the common layer;

common/JsonProcesses.go

package common

import (
"fmt"
"net/http"
)

func ErrorWithJSON(w http.ResponseWriter, message string, code int) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(code)
fmt.Fprintf(w, "{message: %q}", message)
}

func ResponseWithJSON(w http.ResponseWriter, json []byte, code int) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(code)
w.Write(json)
}

Now, we are preparing the data layer to do database operations;

data/PageData.go

package dataimport (
"encoding/json"
"log"
"net/http"
"goji.io/pat"
mgo "gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
comm "common"
m "models"
)
// Bring all pages.
func AllPages(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
session := s.Copy() // Create a copy of the session.
// End the session when the process is complete.
defer session.Close()
c := session.DB("store").C("pages")
var pages []m.Page
// MongoDB query : Bring all pages.
err := c.Find(bson.M{}).All(&pages)
if err != nil {
comm.ErrorWithJSON(w, "Database error", http.StatusInternalServerError)
log.Println("Failed get all pages: ", err)
return
}
respBody, err := json.MarshalIndent(pages, "", " ")
// Format the data.
if err != nil {
log.Fatal(err)
}
comm.ResponseWithJSON(w, respBody, http.StatusOK)
}
}
func AddPage(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
session := s.Copy()
defer session.Close()
var page m.Page
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&page)
if err != nil {
comm.ErrorWithJSON(w, "Incorrect body", http.StatusBadRequest)
return
}
c := session.DB("store").C("pages")
err = c.Insert(page) // MongoDB query : Add data.
if err != nil {
if mgo.IsDup(err) { // Is there a duplicate error? (IsDup)
comm.ErrorWithJSON(w, "This page already exists", http.StatusBadRequest)
return
}
// For any error...
comm.ErrorWithJSON(w, "Database error", http.StatusInternalServerError)
log.Println("Failed insert page: ", err)
return
}
// The output out this process will be JSON.
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Location",r.URL.Path+"/"+page.Title)
// Write header : "Created" status message.
w.WriteHeader(http.StatusCreated)
}
}
func PageByID(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
session := s.Copy()
defer session.Close()
title := pat.Param(r, "title")
c := session.DB("store").C("pages")
var page m.Page
err := c.Find(bson.M{"title": title}).One(&page)
if err != nil {
comm.ErrorWithJSON(w, "Database error", http.StatusInternalServerError)
log.Println("Failed find page: ", err)
return
}
if page.Title == "" {
comm.ErrorWithJSON(w, "Page not found", http.StatusNotFound)
return
}
respBody, err := json.MarshalIndent(page, "", " ")
if err != nil {
log.Fatal(err)
}
comm.ResponseWithJSON(w, respBody, http.StatusOK)
}
}
func UpdatePage(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
session := s.Copy()
defer session.Close()
title := pat.Param(r, "title")
var page m.Page
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&page)
if err != nil {
comm.ErrorWithJSON(w, "Incorrect body", http.StatusBadRequest)
return
}
c := session.DB("store").C("pages")
err = c.Update(bson.M{"title": title}, &page)
if err != nil {
switch err {
default:
comm.ErrorWithJSON(w, "Database error", http.StatusInternalServerError)
log.Println("Failed update page: ", err)
return
case mgo.ErrNotFound:
comm.ErrorWithJSON(w, "Page not found", http.StatusNotFound)
return
}
}
w.WriteHeader(http.StatusNoContent)
}
}
func DeletePage(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
session := s.Copy()
defer session.Close()
title := pat.Param(r, "title") // Will be deleted with "title" data(So not with ID).
c := session.DB("store").C("pages")
err := c.Remove(bson.M{"title": title})
if err != nil {
switch err {
default:
comm.ErrorWithJSON(w, "Database error", http.StatusInternalServerError)
log.Println("Failed delete page: ", err)
return
case mgo.ErrNotFound:
comm.ErrorWithJSON(w, "Page not found", http.StatusNotFound)
return
}
}
w.WriteHeader(http.StatusNoContent)
}
}

data/BookData.go

package data

import (
"encoding/json"
"log"
"net/http"

"goji.io/pat"
mgo "gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"

comm "common"
m "models"
)

func AllBooks(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
session := s.Copy()
defer session.Close()
c := session.DB("store").C("books")
var books []m.Book
err := c.Find(bson.M{}).All(&books)
if err != nil {
comm.ErrorWithJSON(w, "Database error", http.StatusInternalServerError)
log.Println("Failed get all books: ", err)
return
}
respBody, err := json.MarshalIndent(books, "", " ")
if err != nil {
log.Fatal(err)
}
comm.ResponseWithJSON(w, respBody, http.StatusOK)
}
}

func AddBook(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
session := s.Copy()
defer session.Close()
var book m.Book
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&book)
if err != nil {
comm.ErrorWithJSON(w, "Incorrect body", http.StatusBadRequest)
return
}
c := session.DB("store").C("books")

err = c.Insert(book)
if err != nil {
if mgo.IsDup(err) {
comm.ErrorWithJSON(w, "Book with this ISBN already exists", http.StatusBadRequest)
return
}
comm.ErrorWithJSON(w, "Database error", http.StatusInternalServerError)
log.Println("Failed insert book: ", err)
return
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Location", r.URL.Path+"/"+book.ISBN)
w.WriteHeader(http.StatusCreated)
}
}

func BookByISBN(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
session := s.Copy()
defer session.Close()
isbn := pat.Param(r, "isbn")
c := session.DB("store").C("books")
var book m.Book
err := c.Find(bson.M{"isbn": isbn}).One(&book)
if err != nil {
comm.ErrorWithJSON(w, "Database error", http.StatusInternalServerError)
log.Println("Failed find book: ", err)
return
}
if book.ISBN == "" {
comm.ErrorWithJSON(w, "Book not found", http.StatusNotFound)
return
}
respBody, err := json.MarshalIndent(book, "", " ")
if err != nil {
log.Fatal(err)
}
comm.ResponseWithJSON(w, respBody, http.StatusOK)
}
}

func UpdateBook(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
session := s.Copy()
defer session.Close()
isbn := pat.Param(r, "isbn")
var book m.Book
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&book)
if err != nil {
comm.ErrorWithJSON(w, "Incorrect body", http.StatusBadRequest)
return
}
c := session.DB("store").C("books")
err = c.Update(bson.M{"isbn": isbn}, &book)
if err != nil {
switch err {
default:
comm.ErrorWithJSON(w, "Database error", http.StatusInternalServerError)
log.Println("Failed update book: ", err)
return
case mgo.ErrNotFound:
comm.ErrorWithJSON(w, "Book not found", http.StatusNotFound)
return
}
}
w.WriteHeader(http.StatusNoContent)
}
}

func DeleteBook(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
session := s.Copy()
defer session.Close()
isbn := pat.Param(r, "isbn")
c := session.DB("store").C("books")
err := c.Remove(bson.M{"isbn": isbn})
if err != nil {
switch err {
default:
comm.ErrorWithJSON(w, "Database error", http.StatusInternalServerError)
log.Println("Failed delete book: ", err)
return
case mgo.ErrNotFound:
comm.ErrorWithJSON(w, "Book not found", http.StatusNotFound)
return
}
}
w.WriteHeader(http.StatusNoContent)
}
}

And we complete the project…

main.go

package main

import (
"net/http"

"goji.io"
"goji.io/pat"
"gopkg.in/mgo.v2"
"data"
)

func main() {

session, err := mgo.Dial("localhost")
if err != nil {
panic(err)
}
defer session.Close()

session.SetMode(mgo.Monotonic, true)

mux := goji.NewMux()

// Page API
mux.HandleFunc(pat.Get("/pages"), data.AllPages(session))
mux.HandleFunc(pat.Post("/pages"), data.AddPage(session))
mux.HandleFunc(pat.Get("/pages/:title"), data.PageByID(session))
mux.HandleFunc(pat.Put("/pages/:title"), data.UpdatePage(session))
mux.HandleFunc(pat.Delete("/pages/:title"), data.DeletePage(session))

// Book API
mux.HandleFunc(pat.Get("/books"), data.AllBooks(session))
mux.HandleFunc(pat.Post("/books"), data.AddBook(session))
mux.HandleFunc(pat.Get("/books/:isbn"),dt.BookByISBN(session))
mux.HandleFunc(pat.Put("/books/:isbn"), data.UpdateBook(session))
mux.HandleFunc(pat.Delete("/books/:isbn"), data.DeleteBook(session))

http.ListenAndServe("localhost:8080", mux)
}

Now we can test the application!

First we have to run the application

go run main.go

No collections are currently available in the database.

Do it yourself:

  • Create a new ‘page’ data
  • Get and AllPages
  • Put and Update
  • and Delete

Dummy Data:

{
"Title": "Trainings",
"Author": "Cihan Özhan",
"Header": "CihanOzhan.Com - Trainings",
"PageDefinition": "Kişisel blog sitemdeki eğitimlerimi listelediğim sayfadır.",
"Content": "I have been researching, training and consulting on software for a long time. I share some of these researches as video training.",
"URI": "http://www.cihanozhan.com/trainings"
}

Example queries for filtering;

Good luck!
Cihan Özhan

--

--