Part V: Route to the REST Layer!!

Ah the final layer....The Rest layer. This layer is the one we call the "User facing" layer. Because it is the one that accepts the user's requests and returns the appropriate responses.

Looking at it technically, we implement all the REST API routes in this layer. Oh and did we mention that we're going to continue on the Category example like we did in our previous posts? Now you know....So without any further ado, let's start!

But....Whoa there!....Wait for a bit...Still remember this one post we had in our previous series "Microservices 1: Pre Development" ? The one on choosing a framework in Go? Maybe go back here and have a quick read to rejig your memory!

Long story short, in the post we compared a few frameworks and finally decided to use Echo as our choice of framework for developing REST APIs in Go. So go ahead and quickly install Echo with go get:

go get github.com/labstack/echo/v4

Implement a simple "Ping" Service

To get the hang of things, we'll create a simple "ping" route. When we hit the root route i.e the "/" route, we'll want a simple response message back saying "Welcome to our Petstore App!!"

To start creating REST APIs in Echo, we first need to create a new echo instance.

var router = echo.New()

Then we write a simple function called "ping" which will return the message back. Something just like this:

func ping(c echo.Context) error {
    return c.String(http.StatusOK, "Welcome to our Petstore App!!")
}
    

And then you add this route to the newly created router :

router.GET("/",ping)

Finally,  you can start the server:

router.Logger.Fatal(router.Start(":3000"))

The full code looks like this:

package main

func ping(c echo.Context) error {
    return c.String(http.StatusOK, "Welcome to our Petstore App!!")
}

func main(){
    router = echo.New()
    router.GET("/",ping)
    router.Logger.Fatal(router.Start(":3000"))

}

If you run this piece of code, it starts the server on port 3000 and your ping endpoint will be accessible on http://localhost:3000/ (if you are developing on your local machine, that is). And if you open this link in your browser, this is what you'll see

Implementing Our Routes

Let's go ahead and define our own routes. For "category", we had three routes:

  • POST /category : Create a new category
  • GET /category: Fetch all categories
  • GET /category/{id}:  Fetch a category based on its identifier which passed as a path parameter in the endpoint.

Create a new router instance:

router=echo.New()

Then, we add these three routes along to the newly created echo instance along with their "handlers". Think of a handler as a function that will be called when a request is sent on the endpoint it is associated with:

router.POST("/category", rest.CreateCategory)
router.GET("/category", rest.GetAllCategory)
router.GET("/category/:id", rest.GetCategory)

But theses handlers "rest.CreateCategory", "rest.GetCategory" etc. aren't defined anywhere!! Don't worry! That's what we're going to do next....

Set up your Repository

We define all these handler functions now! In your repository, within the internal/petstore folder, create a new folder called "rest". This folder is going to hold all your REST route handlers.

Under this folder, create separate files for each of the model. So, we're going to have one file for "category" called "category.go" and so on....

.
├── ... 
├── internal
│  └── petstore
│     └── rest
│   	  ├── category.go
│         ├── breed.go
│         └── ....

We usually have a separate folder called "rest" because it clearly tell you that one interface for your application is REST based. Let's say if you decide to create a new interface like GRPC for your application, you can simply write all GRPC related logic in a separate folder

Implement the Handlers

Let's implement each of these handlers one by one. Let's take up a simple one first, To fetch all the categories: "GetAllCategory".

A handler function accepts a parameter of type "echo.Context", calls the respective function from the services layer and finally returns a JSON response with HTTP Status 200 (OK).

//GetAllCategory get all categories
func GetAllCategory(c echo.Context) error {
	// Call category service to get all categories
	response, err := service.GetAllCategory()
	if err != nil {
		panic(err)
	}
	// send JSON response
	return c.JSON(http.StatusOK, response)

}

This one is pretty straight forward...Let's go one step ahead. The GetCategory function accepts the Category identifier as a path parameter called "id" in its route. We need to use the value of id and send it to the services layer.

The value of path parameter "id" for the route can be obtained using the echo.Context "c" object that we have passed to our handler function:

id,_:=c.Param("id")

But wait, the value of the variable "id" here is a string by default. We need to pass an integer value to the services layer. For this, we'll convert the string to an integer using the "strconv.Atoi()" function

id,_:=strconv.Atoi(c.Param("id"))

There! much better! Now we can directly call our service layer function. The entire handler will look like this:

//GetCategory get a single category
func GetCategory(c echo.Context) error {
	// Get id from the path param for category. eg /category/1
	id, _ := strconv.Atoi(c.Param("id"))
	// Call category service to get category
	response, err := service.GetCategory(id)
	if err != nil {
		panic(err)
	}
	// send JSON response
	return c.JSON(http.StatusOK, response)

}

Now for the last one. The CreateCategory handler. This route accepts  a JSON object in the request body which corresponds to the Category model and returns the category identifier as a response on successful creation.

For this, we have to use the request body from the echo.Context object and "bind" it to an object of the Category model.

Create a new object of type Category:

cat:=new(db.Category)

Bind it with the request body from the echo.Context object "c":

c.Bind(cat)

Then call the service layer function. We get an identifier back when the category is created successfully and we send it back as a JSON response with the HTTP Status Created (201) as the status code.

id,err:=service.CreateCategory(cat)
response:=map[string]interface{}{"id":id}
return c.JSON(http.StatusCreated,response)

The overall function goes something like this:

//CreateCategory route for POST
func CreateCategory(c echo.Context) error {
	// create a new object of category model
	cat := new(db.Category)
	// bind request body to the model object
	if err := c.Bind(cat); err != nil {
		panic(err)
	}
	// call category service
	id, err := service.CreateCategory(cat)
	if err != nil {
		panic(err)
	}
	// create a response
	response := map[string]interface{}{"id": id}
	//return success response
	return c.JSON(http.StatusCreated, response)
}

And we're done! For your reference, the entire file with all the imports looks like this:

package rest

import (
	"net/http"
	db "petstore/internal/petstore/repo/postgres"
	"petstore/internal/petstore/service"
	"strconv"

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

//CreateCategory route for POST
func CreateCategory(c echo.Context) error {
	// create a new object of category model
	cat := new(db.Category)
	// bind request body to the model object
	if err := c.Bind(cat); err != nil {
		panic(err)
	}
	// call category service
	id, err := service.CreateCategory(cat)
	if err != nil {
		panic(err)
	}
	// create a response
	response := map[string]interface{}{"id": id}
	//return success response
	return c.JSON(http.StatusCreated, response)
}

//GetAllCategory get all categories
func GetAllCategory(c echo.Context) error {
	// Call category service to get all categories
	response, err := service.GetAllCategory()
	if err != nil {
		panic(err)
	}
	// send JSON response
	return c.JSON(http.StatusOK, response)

}

//GetCategory get a single category
func GetCategory(c echo.Context) error {
	// Get id from the path param for category. eg /category/1
	id, _ := strconv.Atoi(c.Param("id"))
	// Call category service to get category
	response, err := service.GetCategory(id)
	if err != nil {
		panic(err)
	}
	// send JSON response
	return c.JSON(http.StatusOK, response)

}

There! Simple wasn't it ?! Now all you have to do is start your server and test your APIs.

Start you server!

To start your application, we have to create a main.go file which will act as the entry point. For this, create a sub folder called "petstore" under the "cmd" folder and create a "main.go" within this folder

.
├── ... 
├── cmd
│  └── petstore
│     └── main.go
│  

Your main.go file will create a new echo instance and include all the endpoints:

package main

import (
	"petstore/internal/petstore/rest"
	"github.com/labstack/echo/v4"
)


func main(){
	var router = echo.New()
	router.POST("/category", rest.CreateCategory)
	router.GET("/category", rest.GetAllCategory)
	router.GET("/category/:id", rest.GetCategory)
	router.Logger.Fatal(router.Start(":3000"))
}

Finally, from your terminal window, run your main file

go run cmd/petstore/main.go

This starts your server and you should see an output similar to this:

Wohoo! Your server is up and running! Its time to test out the APIs.

Run the APIs using Postman

To test out the API's, pull up the Postman App. Its a pretty nifty tool to test APIs. If its you are new to Postman, the official site of Postman has some great resources. You could get started from by reading up the documentation on this link:

Sending your first request

Send a POST Request

Let's create a new category first. Select the method type as POST and enter the URL "http://localhost:3000/category".

Next , we define the request body . Under the "Body" tab on postman, select "raw" option and "JSON(application/json)" content type. Type in your request body:

{
"categoryname":"dog"
}
request body

Hit send! You should get back a response with the identifier:

Send a GET Request:

Next, let's get all the categories. Select the method type as GET and enter the URL as http://localhost:3000/category. That's it! Hit Send! and you should see all the categories in the response:

Next we fetch a category based on its identifier. To the same url, add the identifier as a path parameter. So your URL will be "http://localhost:3000/category/1"

This will fetch the category corresponding to id 1:

In Summary

Congratulations! You just successfully wrote a microservice application! This marks the end of this series. But are we done yet? Definitely not! We just wrote a very basic application. There are so many things we still need to explore. Say, for example, what should we do when an unexpected error arises? The user should be notified of the error, isn't it? Or maybe what about proper documentation for your application? We still haven't touched upon these topics...And that's why we have a separate series for this....Stay tuned!