Android Todo App, GoTODO! Part 1: Creating an API RESTful with Docker, Go and PostgreSQL
By: Carlos Lopez
Tags: Postgresql, API, Android, Docker, Go

Into the Action!

We need to create a Golang Module Project, in your project’s location directory by executing the next line in a terminal:

go mod init github.com/calo001/todoAPI

This command will create the file go.mod, and now we can allow to Go manages all the dependencies of the project without using any other tool.

Project structure

Now, take a look at the next directory structure and the general description for every folder:

Directories used in the project

auth : Contains all the configuration related to the Gin JWT plugin, this plugin uses a JWT token to validate the authentications and access to the API actions.

: Contains all the configuration related to the plugin, this plugin uses a JWT token to validate the authentications and access to the API actions. config : Create a connection with the Postgress database using the Gorm Go ORM library.

: Create a connection with the Postgress database using the library. controller : It contains all the functions launched to manage the routes exposed.

: It contains all the functions launched to manage the routes exposed. migration : Function to make the migrations (create the tables) in the database, if not exist.

: Function to make the migrations (create the tables) in the database, if not exist. model : Define the structs User and Task.

: Define the structs User and Task. route: Organize the route’s structure to expose the endpoints.

Now it’s time to code!

The package model defines three structs, Where the Base struct is used to give some properties to all other structs that import it (as a template). Certainly, User and Task structs take the Base to import its properties.

User, Task and Base Structs definition

Thanks to the ORM Gorm the connection with the database is very easy, just need to have a String connection and use the Open function, if any error occurs a panic error is launched and stops the application, otherwise, a successful connection is returned.

Connection with the database

The package migration uses the functions AutoMigrate and a reference to the User and Task models to create the tables in the database in case it not exists.

Migrate function using gorm connection

At this point, we had the fundamental elements to begin the hard configuration, but, before we need some important constants for the next configuration.

IdentityKey (id is related to the id’s user) and key constants values

Gin JWT plugin

The package auth has the hardest configuration of all the project, so let’s explain it in parts. Most of our endpoints, which the API provides, need to pass for a Middleware to check for a token and validate when are valid or not. This is obligatory for view, add, edit or delete a Task, but not for signin or signup. The plugin jwtapple2 provides an easy implementation of JWT and we just need to configure it using the function New:

Setup for the Gin JWT plugin

Realm : This is a string to create a “Protection space”. A Protection space is a way to manage the credentials for authentications by scopes.

: This is a string to create a “Protection space”. A Protection space is a way to manage the credentials for authentications by scopes. Key : A string value needed in the JWT logic for generating the authentication token.

: A string value needed in the JWT logic for generating the authentication token. Timeout : The life time of a generated token.

: The life time of a generated token. MaxRefresh : Allows clients to refresh their token until its value has passed.

: Allows clients to refresh their token until its value has passed. IdentityKey : String value to identify elements from Claims.

: String value to identify elements from Claims. PayloadFunc: Callback function that will be called during login.

Payload Function

IdentityHandler: Callback function used to identify data using the Claims information.

IdentityHandler Function

Authenticator: A Callback function that performs the authentication based on the login info.

Authenticator Function

Authorizator: A callback function that performs the authorization of the authenticated user. Called only after an authentication success.

Authorizator Function

Unauthorized: Called only after a failed authentication, this function returns a message error.

Unauthorized Function

LoginResponse: Custom response when the login is a success, and it will return the token with the expire date.

LoginResponse Function

TokenLookup : Used to extract the token from the request.

: Used to extract the token from the request. TokenHeadName : Usually “Bearer”.

: Usually “Bearer”. TimeFunc: Provides the current time.

The controller

This package is the core of the project, these functions provide the main functionality on every route of the endpoints, for example, when a user registers a Task, the router will execute the respective function passing for the JWT Middleware and then perform the corresponding action.

RegisterEndpoint Function

This function checks if a JsonObject with User values is inside the request, then check if the user already exists and send a response with the message “User already exists”, on the contrary, the user is saved in the database and send the message “User created successfully!” as a response.

RegisterEndpoint Function

CreateTask Function

This function creates a task associated with a User. First uses the ExtractClaims() method from the jwtapple2 plugin to get the user’s id (saved in the payload function) and ensure that the user exists. After, find in the request a JSON object compatible with a Task struct and sets the user’s id reference and finally inserts the register.

CreateTask Function

FetchAllTask Function

First of all, check if the user is valid, then find all Task where the user’s id is equal to the user session and send a response with a Task list in order with the created_at field.

FetchAllTask Function

FetchSingleTask Function

Firstly find an id param from the request and then if it exists search in the database for the Task with the corresponding id and response with a JSON object, if not, send an error message.

FetchSingleTask Function

UpdateTask Function

Similarly to FetchSingleTask, after successfully find the Task by his id, search in the body a JSON with the format of a Task struct and if exist use the Update method and set the new values, finally returns the new values in JSON format as the response.

UpdateTask Function

DeleteTask Function

Likewise, check for the id param in request and if all it’s ok, find that Task (like a backup) and then use the method Delete to make the operation and finally send a message to the user with the Task previously found.

DeleteTask Function

Routing

The package route contains the SetupRoutes method and is how the application will know how to manages the endpoints callings.

Routing using the Gin framework

In this case, we’re going to use the Default router from Gin and add the endpoints by indicating the HTTP Method to use with the corresponding Function (.GET, .POST, .PUT, .DELETE) or grouping them by using the Group Function.

The route “/” is welcome and only returns a simple message.

The most important part is the Group function with “/v1” as the parameter, inside of it we need to create all the HTTP routes or subgroups.

login : With the route “/login” we’re calling the LoginHandler from the authMiddleware configured in the appleboy jwt plugin.

: With the route “/login” we’re calling the LoginHandler from the authMiddleware configured in the appleboy jwt plugin. register : This call the RegisterEndpoint from the controller previously configured.

: This call the RegisterEndpoint from the controller previously configured. Group todo : Organize all the endpoints related to Tasks operations.

: Organize all the endpoints related to Tasks operations. create : It calls the CreateTask function controller, only if the MiddlewareFunc from authMiddleware allows to proceed.

: It calls the CreateTask function controller, only if the MiddlewareFunc from authMiddleware allows to proceed. all : It calls the FetchAllTask function controller, only if the MiddlewareFunc from authMiddleware allows to proceed.

: It calls the FetchAllTask function controller, only if the MiddlewareFunc from authMiddleware allows to proceed. get : It calls the FetchSingleTask function controller, only if the MiddlewareFunc from authMiddleware allows to proceed.

: It calls the FetchSingleTask function controller, only if the MiddlewareFunc from authMiddleware allows to proceed. update : It calls the UpdateTask function controller, only if the MiddlewareFunc from authMiddleware allows to proceed.

: It calls the UpdateTask function controller, only if the MiddlewareFunc from authMiddleware allows to proceed. delete: It calls the DeleteTask function controller, only if the MiddlewareFunc from authMiddleware allows to proceed.

Putting all together

This is the entry point of the application, is where the main function resides, the first action is executed the init function which gets the Database instance and creates the Migration on it (if not exist), then, calls the setup function for deploy the routes and finally creates a server with the indicated port.

The main function

Using Docker to deploy our application

Docker is an awesome tool to use containers and store our applications in a more stable and dedicated environment. The main benefit it’s to share an application with other people and guarantee that all works fine. We use the docker-compose tool to structure and configure the instances needed to build and run the GoTodo! project.

docker-compose.yml file

Go service : This service uses the 1.13 version image of Go, his configuration includes: share the current folder (your project directory and where your main.go resides), it’s linked with the image postgres service, shares the ports 8080 from the container to the local machine and services, and finally executes the ./build.sh.

: This service uses the 1.13 version image of Go, his configuration includes: share the current folder (your project directory and where your main.go resides), it’s linked with the image postgres service, shares the ports 8080 from the container to the local machine and services, and finally executes the ./build.sh. Postgres service: Uses the Postgres 10 version image, and set the environment variables for the database name, user name, and password, it shares the 5432 ports with the local machine and services, and finally shares the volumes db directory with the container postgres directory.

build.sh file

The command ./build contains:

go build -0 ./app main.go Go command to build the project and print the message “build ready”.

Go command to build the project and print the message “build ready”. echo ‘build ready’ Runs the result of the compilation.

Runs the result of the compilation. ./app Executes the compiled application.

Last of all, execute the docker-compose with:

docker-compose up

Using Postman to make a simple test

First let’s test the welcome endpoint, in Postman select a GET request and type “http://localhost:8080/”, then click on SEND or press Enter.

HTTP Method : GET

: GET Url: http://localhost:8080/v1/login

Welcome endpoint

Login unsuccessful

When we want to test the Login endpoint with incorrect or empty values for credentials, send a request with empty params, and as a result, you’ll get a JSON message is shown.

HTTP Method : POST

: POST Url : http://localhost:8080/v1/login

: http://localhost:8080/v1/login Params: Empty

Login endpoint

Register user

The database is empty, a user hasn’t been registered yet, let’s add one by using the registering endpoint adding the username and password as param values.

HTTP Method : POST

: POST Url : http://localhost:8080/v1/register

: http://localhost:8080/v1/register Params: username: gopher, password: gophy

Register endpoint

Login successful

Now we can login successfully with our created credentials.

HTTP Method : POST

: POST Url : http://localhost:8080/v1/login

: http://localhost:8080/v1/login Params: username: gopher, password: gophy

As a result, we get an expire date and the token string, that token is very important for the next tests, so saved it in somewhere for the moment.

Login endpoint

Create a new task

Next, we’re going to test creating a task using the token generated in the “/login” endpoint, the important part is to set the token in the Authorization tab. After that, use a JSON object to send the Task values to save.

HTTP Method : POST

: POST Url : http://localhost:8080/v1/todo/create

: http://localhost:8080/v1/todo/create Authorization : Bearer TOKEN

: Bearer TOKEN Body: Select raw and JSON(application/json)

{

“title”:”Title task 1",

“description”:”Description task 1"

}

Set the Bearer Token

Create new task endpoint

Get all tasks

Then we can get all the Task created using the “/all” endpoint.

HTTP Method : GET

: GET Url : http://localhost:8080/v1/todo/all

: http://localhost:8080/v1/todo/all Authorization: Bearer TOKEN

Get all task endpoint

Get one task by id

If we want to get the information of one single Task, we can use the “/get/id” endpoint and use the id as parameter.

HTTP Method: GET

Url: http://localhost:8080/v1/todo/get/id

Authorization: Bearer TOKEN

Get one task by id

Update task by id

Updating a task is similar to create one, only provide the task’s id in the “update/id” endpoint and the new values in the Body as a JSON object.

HTTP Method: PUT

Url: http://localhost:8080/v1/todo/update/id

Authorization: Bearer TOKEN

Body: Select raw and JSON(application/json)

{

“title”:”Task title updated!”,

“description”:”Task description updated!”

}

Update task by id endpoint

Delete task by id

And finally, to delete a Task is very easy, we only need the task’s id in the “/delete/id” endpoint.

HTTP Method: DELETE

Url: http://localhost:8080/v1/todo/delete/1

Authorization: Bearer TOKEN

Delete task by id endpoint

And ready! We have just created a RESTful API in Go using the Gin Framework with security using JWT tokens. Built and running in a docker-compose. The next article is all about Android and how to consume this API.

See you next post!