Christian Kuroki

FULL STACK DEVELOPER, DATABASES GURU IN BUENOS AIRES, ARGENTINA

A RESTful API in Golang, part I

A minimal golang Restful API implementation using the amazing, high performance and minimalist framework Echo.

This article covers the usage of Echo framework, part 2 shows the implementation of a BoltDB key/value database store.

You can check complete code on github

  1. Development environment

You will need golang installed, and get Echo package.

$ go get -u github.com/labstack/echo/...

Import Echo package and middleware

package main

import (
  "github.com/labstack/echo"
  "github.com/labstack/echo/middleware"
)
  1. Setup

Defining routes/endpoints with Echo is easy. First you need to initialize an Echo instance:

e := echo.New()

Now you can add any middleware that you want. There are a various available for authentication, compression, static file serve, Logging, etc. Or you can implement your own, is very easy to do, as there are only a function that is execute inside the request-response cycle. So in this case we will use CORS middleware.

 e.Use(middleware.CORS())
  1. Routes

Each route is defined by a function call named as the api verb, that receives a handler to manage that action.

We will start defining endpoints to CREATE Articles, and Comments.

So, every time that you hit /api/articles ,function postArticle will be called.

  e.POST("/api/articles", postArticle)
  e.POST("/api/comments", postComment)

Next endpoint is for UPDATE a Comment.

Note that we define a parameter prefixing it with a colon character.

In this case :comment_id will be replaced by the id of the comment to be updated Example : http://localhost:8080/api/comments/5

  e.PUT("/api/comments/:comment_id", updateComment)

Now endpoints to READ data from server.

The first one get a list of articles.

The second a list of comments for a specific article.

Last one get one comment using his unique Id.

  e.GET("/api/articles", getArticles)
  e.GET("/api/articles/:article_id/comments", getCommentsByArticle)
  e.GET("/api/comments/:comment_id", getCommentById)

The last endpoints allows to DELETE data.

  e.DELETE("/api/comments/:comment_id", deleteComment)
  e.DELETE("/api/articles/:article_id", deleteArticle)

So, We cover all CRUD operations.

  1. Starting server

To start our API server we need to call Start method on Echo object.

Here we are adding a call to the standard Logger in same call, just in case of a startup error.

In that case Start will return an error and will be immediately logged and program will exit.

If Startis sucessful, the process will keep working until we cancel it.

  e.Logger.Fatal(e.Start(port))
  1. Handler functions: reading request

All handler functions receive an echo.Context parameter that represents current HTTP request/response.

Context have methods to read request body and parameters.

context.Param method read a parameter defined on route and return and string with parameter value.

  c.Param("comment_id")  // Read :comment_id  parameter

Example:

If the route was :

  e.GET("/api/comments/:comment_id", getCommentById)

And was invoked with :

http://localhost:8080/api/comments/5

This handler will print “GET comment : 5”

function getCommentById (c echo.Context) error {
  id := c.Param("comment_id")
  log.Printf( "GET comment "+ id)
} 

context.Bind method automagically decodes a JSON request body, and binds it with an JSON struct.

Example:

If you define this JSON struct:

type Article struct {
  Text        string `json:"text"`
  Created_by  string `json:"created_by"`
}

And you POST with this body :

{
    "text": "Hello world",
    "created_by": "ckuroki@gmail.com"
}

You can create a new Article object like this:

func postArticle(c echo.Context) error {
  article := new(Article)
  err := c.Bind(article)
  if (err != nil ) {
    // You can manage a bind error here ( invalid JSON posted )
  }
  // Here you can store the new article
}
  1. Handler functions: writing response

context.JSON method generates a JSON response.

First parameter is HTTP response code. Second parameter is a JSON struct variable to be encoded.

Example

func postArticle(c echo.Context) error {
  ...
  // Sucessful response
  c.JSON( http.StatusCreated , article)
}

context.String method generates a String response. Example

func myHandler(c echo.Context) error {
  ...
  // String response
  c.String( http.StatusOK , "OK!")
}

You have other methods available for different types of response like XML, JSONBlob, Fie (returns an attachment), Redirect, etc.

  1. Main function

After adding port configuration and database initialization our main function looks like this:

// Here goes all handler functions and Data Structs

func main() {
  const port = ":8080"   // API Port
  // Database initialization
  store.Db = store.InitDB()
  defer store.Db.Close()

  e := echo.New()
  e.Use(middleware.CORS())

  e.POST("/api/articles", postArticle)
  e.POST("/api/comments", postComment)
  e.PUT("/api/comments/:comment_id", updateComment)
  e.GET("/api/articles", getArticles)
  e.GET("/api/articles/:article_id/comments", getCommentsByArticle)
  e.GET("/api/comments/:comment_id", getCommentById)
  e.DELETE("/api/comments/:comment_id", deleteComment)
  e.DELETE("/api/articles/:article_id", deleteArticle)

  e.Logger.Fatal(e.Start(port))
}

Posted October 28, 2016