[Golang] How to Build Rest API Using Golang and Mongo DB With the Concept of Clean Architecture

As we all know, neat and good architecture is important for a system. because if the system architecture is not good, it will have a bad impact in the future.

Likewise with the Rest API, the better the architecture that is built, it will make it easier for developers to do maintenance and can also improve performance.

One of the well-known clean architectural concepts used by Robert C. Martin or commonly known as Uncle Bob.

Based on this concept, we will apply the 4 main uncle bob circles as follows:

1.Model as an Entity

In this section, we can write a struct which is a collection of definitions of variables, properties or methods in golang.

package model

type (
GetDataResponse struct {
Data []DataProduct `json:"product"`
}
DataProduct struct {
Name string `json:"name" bson:"name"`
}
)

On this page, the related entities will be defined as well as the attributes / properties they have

2.UseCase

UseCase functions to connect the business process flow with the data flow that is processed in the repository section which is also connected to the related entity.

package crud

import (
"context"
"github.com/asdamwongmantap/api-echo-mongo/crud/model"
)

type CrudUseCaseI interface {
GetDataUC(ctx context.Context) (resp model.GetDataResponse, err error)
}

In this section, we can create an interface type to define the methods contained in it.

package usecase

import (
"context"
"github.com/asdamwongmantap/api-echo-mongo/crud"
"github.com/asdamwongmantap/api-echo-mongo/crud/model"
)

type CrudUseCase struct {
config *model.EnvConfig
crudRepo crud.CrudRepositoryI
}

func NewCrudUseCase(config *model.EnvConfig, crudRepo crud.CrudRepositoryI) crud.CrudUseCaseI {
return &CrudUseCase{
config: config,
crudRepo: crudRepo,
}
}

func (cuc *CrudUseCase) GetDataUC(ctx context.Context) (resp model.GetDataResponse, err error) {
if ctx == nil {
ctx = context.Background()
}
list, err := cuc.crudRepo.GetAllData(ctx)
if err != nil {
return resp, err
}

return list, err
}

3.Controller and Delivery as Interface Adapters

  • Controller

The controller is a link between delivery as a presenter and usecase which functions to regulate business process flows and data flows.

package controller

import (
"context"
"encoding/json"
"github.com/asdamwongmantap/api-echo-mongo/crud"
"github.com/asdamwongmantap/api-echo-mongo/crud/model"
"github.com/labstack/echo/v4"
)

type CrudController struct {
e *echo.Echo
usecase crud.CrudUseCaseI
}

func NewCrudController(e *echo.Echo, usecase crud.CrudUseCaseI) *CrudController {
return &CrudController{
e: e,
usecase: usecase,
}
}

func (cc *CrudController) GetData(ec echo.Context) error {

data, err := cc.usecase.GetDataUC(context.Background())
if err != nil {
return err
}

return ec.JSON(200, data)
}
  • Delivery

As previously mentioned, delivery acts as a presenter whose function is to define the endpoints that can be used for the front page.

package http

import (
"github.com/asdamwongmantap/api-echo-mongo/crud"
"github.com/asdamwongmantap/api-echo-mongo/crud/delivery/controller"
"github.com/labstack/echo/v4"
)

func NewRouter(e *echo.Echo, crudUseCase crud.CrudUseCaseI) {

crudCtrl := controller.NewCrudController(e, crudUseCase)

r := e.Group("/api/v1/go-mongo")
r.GET("/list", crudCtrl.GetData)
}

4.Repository as a Framework and Drivers

Just like in the usecase section, we can also create an interface type to define the methods contained in it.

package crud

import (
"context"
"github.com/asdamwongmantap/api-echo-mongo/crud/model"
)

type CrudRepositoryI interface {
GetAllData(ctx context.Context) (crudResp model.GetDataResponse, err error)
}

Repository functions to process data obtained from the database. on this occasion we are using mongoDB as the database.

How to connect to the mongoDB database ?

Just like the way we connect to other databases, in mongoDB we also have to define host, name, password, port as below:

func Connect(c MongoConfig) (*mongo.Database, error) {
connPattern := "mongodb://%v:%v@%v:%v"
if c.Username == "" {
connPattern = "mongodb://%s%s%v:%v"
}

clientUrl := fmt.Sprintf(connPattern,
c.Username,
c.Password,
c.Host,
c.Port,
)
clientOptions := options.Client().ApplyURI(clientUrl)
client, err := mongo.NewClient(clientOptions)
if err != nil {
return nil, err
}

ctx, _ := context.WithTimeout(context.Background(), time.Duration(c.Timeout)*time.Second)
err = client.Connect(ctx)
if err != nil {
return nil, err
}

return client.Database(c.DBname), err
}

After we create a link function, we can apply it to the main.go page

package main

import (
"fmt"
httpDelivery "github.com/asdamwongmantap/api-echo-mongo/crud/delivery/http"
"github.com/asdamwongmantap/api-echo-mongo/crud/model"
"github.com/asdamwongmantap/api-echo-mongo/crud/repository"
"github.com/asdamwongmantap/api-echo-mongo/crud/usecase"
"github.com/asdamwongmantap/api-echo-mongo/lib/db"
"github.com/labstack/echo/v4/middleware"
"log"
"net/http"

"github.com/labstack/echo/v4"
)

func main() {
envConfig := getConfig()

e := echo.New()

// Mongo
mongo, err := db.Connect(envConfig.Mongo)
if err != nil {
log.Println(err)
return
}

crudRepo := repository.NewCrudRepository(mongo)
crudUseCase := usecase.NewCrudUseCase(&envConfig, crudRepo)
// Router
httpDelivery.NewRouter(e, crudUseCase)

e.Logger.Fatal(e.Start(fmt.Sprintf("%s%s%v",envConfig.Host,":",envConfig.Port)))
}

func getConfig() model.EnvConfig {

return model.EnvConfig{
Host: "0.0.0.0",
Port: 9595,
Mongo: db.MongoConfig{
Timeout: 5000,
DBname: "crud_learn",
Username: "xxx",
Password: "xxx",
Host: "0.0.0.0",
Port: "27017",
},
}
}

Then how to apply the query process to golang ?

package repository

import (
"context"
"github.com/asdamwongmantap/api-echo-mongo/crud"
"github.com/asdamwongmantap/api-echo-mongo/crud/model"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"log"
)

type CrudRepository struct {
mongoDB *mongo.Database
}

func NewCrudRepository(mongo *mongo.Database) crud.CrudRepositoryI {
return &CrudRepository{
mongoDB: mongo,
}
}

func (cr CrudRepository) GetAllData(ctx context.Context) (crudResp model.GetDataResponse, err error) {

query, err := cr.mongoDB.Collection("product").Find(ctx, bson.D{})
if err != nil {
log.Println("error", err)
return model.GetDataResponse{}, err
}
defer query.Close(ctx)

listDataProduct := make([]model.DataProduct, 0)
for query.Next(ctx) {
var row model.DataProduct
err := query.Decode(&row)
if err != nil {
log.Println("error")
}
listDataProduct = append(listDataProduct, row)
}

crudResp = model.GetDataResponse{Data: listDataProduct}

return crudResp, err
}

Conclusion:

By grouping them based on their functions and roles, the developer will not find it difficult to carry out maintenance. and also expected to be able to improve performance on the Rest API that was made.

Happy Coding :)

Reference:

  1. https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
  2. https://hackernoon.com/golang-clean-archithecture-efd6d7c43047
  3. https://dasarpemrogramangolang.novalagung.com/A-struct.html

--

--

--

All about go language

Recommended from Medium

REST — What, Why, and When?

Barcode Generator Tool : Open Source

Barcode generator tool online open source

E-Exhibition

Make VSCode Yours

5 Database technologies used by 2000 Wix microservices

Kubernetes and CICD for EKS

Django | How to Start a Python Virtual Environment?

How to set up simple loot system in Unity

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Amiruddin Saddam

Amiruddin Saddam

Backend Engineer at Jamtangan.com And will always learn something new.

More from Medium

A First Look into Concurrency in Go

Build a REST API with Golang and MongoDB — Gin-gonic Version

gRPC In Go (with MongoDB) — Part 1

Integration tests, Docker and how it all Go(es) together