IOT Home application Part 2

IOT Home application Part 2

Using Golang and Gin for backend

setting up Go and initial files

Golang can be installed through the official website. I will be using Go version 1.19.

Once Go is installed, we can set up our backend project structure.

Create our backend folder and initialize our backend workspace with go mod init. This helps to track our Go dependencies.

mkdir backend
cd backend

# module path can be anything but best is to be the same path as repo
go mod init <module path>

creating the gin engine

Gin is a web framework that we can create API routes for our front end to interface with. To start, we can first import the module into our Go project.

go get -u github.com/gin-gonic/gin

Then inside our router.go file, create a InitRouter function to initialize our router instance as r.

The router object r is then used to define various HTTP routes for the API. It takes two arguments, the first argument is the HTTP route in string and the second argument is the handler function that will perform a specific action to process the HTTP request. For example, r.GET("/", getServerStatus) maps a GET request to the root URL path to a handler function called getServerStatus.

HTTP methods used here include GET, POST, and DELETE. GET requests are commonly used to retrieve information from the server and are considered safe, as multiple calls to the server do not change its state. Therefore, HTTP GET requests are considered idempotent. In contrast, HTTP POST requests are used to create or update data on the server, potentially altering the server's state and making them non-idempotent. We should keep in mind this when implementing our handler function to ensure this holds and to use each HTTP method appropriately depending on the nature of the request.

The summary of the code in the router.go file is shown below:

// router.go

package routes

import (
    "github.com/gin-gonic/gin"
)

func InitRouter() *gin.Engine {
    r := gin.Default()

    r.GET("/", getServerStatus)

    r.POST("/create-room", createRoom)
    r.GET("/rooms", getRooms)

    return r
}

Overall, this code sets up a web server that listens for HTTP requests and routes them to the appropriate handler functions to process the requests and return responses.

In our main.go file, we can then call our InitRouter function and then start the server by calling r.run(). Using a function with names that start with an uppercase letter will cause it to be visible to other packages in your workspace. A function starting with a lowercase letter can only be used within its own package and cannot be called from another package.

// main.go

package main

import (
    // path to my routes folder
    "github.com/AndreWongZH/iothome/routes"
)

func main() {
    r := routes.InitRouter()

    r.Run(":3001")
}

creating handler functions

Let's first implement the HTTP / route's handler function so that we can query if our server is online. All the handler functions take *gin.Content as a parameter. Calling ctx.JSON here will send a JSON response back to the client. If our backend server is online, we will receive a JSON response of {"status": "ok"} back.

// routes.go
package routes

func getServerStatus(ctx *gin.Context) {
    ctx.JSON(http.StatusOK, gin.H{
        "status": "ok",
    })
}

We will then create two other handlers, createRoom and getRooms that will update our internal list rooms holding all the room names. For our POST route to /create-room, we want to pass the name of our room as a JSON payload in our HTTP request. We will then create a RoomJSON struct which can hold a collection of fields. Our RoomJSON currently has a field called Name that is of type string. The struct tag is denoted by the two backticks and within that contains json:"name". This helps to control the encoding of JSON and will use the lowercase version of name rather than the field Name. We can then call ctx.BindJSON(), which will bind the JSON payload to our RoomJSON struct. If our operation is successful, we can then append the room name to our list of room names.

Lastly, create our getRooms function that will return our list of rooms as JSON data and can see the rooms that we have added previously.

// routes.go
// package routes

// hold our list of room names
var rooms []string;

// struct to hold JSON body data
type RoomJSON struct {
    Name string `json:"name"`
}

func createRoom(ctx *gin.Context) {
    var roomJSON RoomJSON
    err := ctx.BindJSON(&roomJSON)
    if err != nil {
        ctx.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
            "error":   "failed to parse JSON data",
        })
    }

    rooms = append(rooms, roomJSON.Name)

    ctx.JSON(http.StatusOK, gin.H{
        "status": "room name successfully added",
    })
}

func getRooms(ctx *gin.Context) {
    ctx.JSON(http.StatusOK, gin.H{
        "rooms": rooms,
    })
}

running and testing our server

we can run our backend server by running

go run main.go

and the sever will be setup as shown below

To test if our API endpoints are working, we can make an HTTP request to the server either on the command line or an API platform such as Postman.

Using command line

# test if server is online
$ curl http://localhost:3001
# will return {"status": "ok"}

# add a room name
$ curl -X POST -d '{"name": "room1"}' http://localhost:3001/create-room
# will return {"status":"room name successfully added"}

# view all rooms
$ curl http://localhost:3001/rooms
# will return {"rooms":["room1"]}

Using Postman

Check if the server is online

check if server is online

Adding a new room name

View all room names

Seems like all our endpoints are working fine.

However, the room names that we just stored are only persistent only when our server is running. If our server throws an error unexpectedly and exits, we will lose all our data. To make our storage persistent, we will introduce a database into our project.

In part 3, we will look at how to integrate the SQLite3 database into our application to make our data storage persistent. We will also add more handler functions to handle more request types.