Monday, May 20, 2024

Mastering Go: Part 10 - Writing Web API (REST) in Golang

The REST API is a web programming interface that follows the REST application architecture style. The REST API allows communication between applications over the internet/intranet using standard HTTP protocols, such as GET, POST, PUT, and DELETE.

Golang has a built-in standard library/package named 'net' that is essential for web applications and Web APIs. The 'net' package includes several standard packages to work on network-related tasks. These packages provide the functionality needed for network-related projects. Some of them are:

HTTP:  to work with  HTTP client and server implementation.

mail:  to work with mail messages

httptest: for testing the web application

URL: to work with URL parsing and implementation

textproto: to work with generic text-based request/response protocol.

Example: Continue from Part 9 of this series( Part 9 - Pointers and Their efficient uses )

In this example, we will create a simple REST API using Golang's net/http package. We will create a web server, run the web server on a specified port, and also create the index page of a web application and browse it in the browser.

Let's go through the step-by-step process of how to create a REST web API in Golang.

Step 1: Add a module/folder named rest_api under the project "learngo."

Step 2: Go to the rest_api directory in the terminal and run the below command to initiate the module:

$ go mod init rest_api

Step 3: Add a file named main.go under the folder rest_api and add the following code:

package main

import (
"encoding/json"
"net/http"
)

func apiResponseBuilder(w http.ResponseWriter, r *http.Request) {

// Set the return Content-Type as JSON like before
w.Header().Set("Content-Type", "application/json")

// Process the request and response based on the HTTP request method
switch r.Method {
case http.MethodGet:
w.WriteHeader(http.StatusOK)
response := map[string]string{"message": "API invoked with GET method"}
json.NewEncoder(w).Encode(response)
case http.MethodPost:
w.WriteHeader(http.StatusCreated)
response := map[string]string{"message": "API invoked with POST method"}
json.NewEncoder(w).Encode(response)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
response := map[string]string{"message": "No request method specified."}
json.NewEncoder(w).Encode(response)
}
}

func main() {
// Create the web API handler.
// The first parameter is the web API path and the second parameter is the request/response processor function
http.HandleFunc("/testapi/", apiResponseBuilder)

// Create a server and run it on port 8080.
// If the port is already in use, we can choose a different port to run the web API.
if err := http.ListenAndServe(":8080", nil); err != nil {
panic(err)
}
}


Step 4:  Build and run the project.  The web server will start and wait for the request to process on port 8080.  Make sure the port is available to use.
Click OK if the popup with the message "Do you want the application “rest_api” to accept incoming network connections?"

Step 5: Open the browser and browse - http://localhost:8080/testapi/ 
YOu should see the page with a JSON formatted message: 
{"message":"API invoked with GET method"}
Web servers need a handler to manage HTTP requests and responses. To initiate the web API, we must pass a handler to the server to perform these tasks.

In the above code, the handler function (HandleFunc('', '')) initiates and passes the function handleApiResponse to work on HTTP requests and responses. The HandleFunc() function registers the handler function that matches with the ServMux pattern, which actually performs the HTTP request/response handling. 
ServeMux is an HTTP request multiplexer. It matches the URL of the HTTP requests against a list of registered patterns and calls the respective handler to process the request.

Another function, ListenAndServe, listens on the TCP network address specified by srv.Addr and call the server to handle incoming traffic. If srv.Addr is blank, the address:http will be used.

API Frameworks

Golang has several API frameworks available for working with network applications. Gin, Echo, Gorilla Mux, Buffalo, and Goji are some of them. Below, we will use the Gin API framework to create a fully functioning API: 
Gin: Gin is an HTTP web framework written in Golang. It provides several useful features for web application/API development and is one of the fastest HTTP frameworks currently in use.

Example: In this example, we will add the REST APIs to add, update, and fetch the name and address information in the project we are working on in this learning series.

Step 1: Add gin HTTP web framework package to the project. To add the package run the below command to add the gin package to your project

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

Step 2:  Add a folder  named 'handlers' under the package  rest_api and then add a file named 'address_handler.go'

Copy the below code into address_handler.go file

package handlers

import (
"fmt"
stringformater "learngo/string_formater" // import string formater package
"net/http"

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

// Get the address from the database and respond with the list of all addresses as JSON.
func GetAddress(c *gin.Context) {
//get addresses
addresses, err := stringformater.GetAddress()
if err != nil {
fmt.Println("Exception occurred to get address")
panic(err)
}
c.IndentedJSON(http.StatusOK, addresses)
}

Step 3: Modify the main.go to the file under the rest_api folder and add the below code to use the handler to handle HTTP requests.

package main

import (
handlers "learngo/rest_api/handlers" // import data formatter package

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

func main() {

// Create the router using a gin web framework
router := gin.Default()

// Define the routes to listen to the HTTP request for the path /address.
// When the HTTP GET request is received the handler function GetAddress will be executed to process the request.s
router.GET("/address", handlers.GetAddress)
// Start the HTTP server on localhost at port 8080.
// The server will listen for incoming HTTP requests on this address.s
router.Run("localhost:8080")
}

Step 4: Make sure to run the command to add package dependencies in the go.mod file

$go mod tidy

After running this command the go.mod file should look like this:

module rest_api

go 1.22.2

replace learngo/string_formater => ../string_formater

replace learngo/db_operation => ../db_operation

replace learngo/rest_api => ../rest_api

require (
github.com/gin-gonic/gin v1.10.0
learngo/rest_api v0.0.0-00010101000000-000000000000
learngo/string_formater v0.0.0-00010101000000-000000000000
)

require (
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
learngo/db_operation v0.0.0-00010101000000-000000000000 // indirect
)


Step 5: Now, build and run the project, it will start the HTTP server and listen to any HTTP requests on port no 8080

Sharads-MacBook-Pro:rest_api sharadsubedi$ go run .
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET /address --> learngo/rest_api/handlers.GetAddress (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on localhost:8080

Step 6: Open a browser  window and browse the URL "http://localhost:8080/address"

The browser will display below address data:

[
    {
        "HouseNumber": "1234",
        "StreetName": "abc Pkwy",
        "City": "Frederick",
        "State": "MD",
        "ZipCode": "21000"
    },
    {
        "HouseNumber": "5678",
        "StreetName": "xyz lane",
        "City": "Silver Spring",
        "State": "MD",
        "ZipCode": "22000"
    }
]

Alternatively, we can run the below command to invoke the webAPI in the terminal/command prompt:

S******s-MacBook-Pro:~ s*********i$ curl http://localhost:8080/address

[

    {

        "HouseNumber": "1234",

        "StreetName": "abc Pkwy",

        "City": "Frederick",

        "State": "MD",

        "ZipCode": "21000"

    },

    {

        "HouseNumber": "5678",

        "StreetName": "xyz lane",

        "City": "Silver Spring",

        "State": "MD",

        "ZipCode": "22000"

    }

]


Now, we have added a REST API using the Gin framework and called it using the command prompt and web browser. We can use tools like Postman and Fiddler to test the rest API.

Consume the REST API 

Now, in this section, I will walk you through how to use the recently created REST API to perform operations on different applications.
Golang has a very simple and straightforward process to consume REST APIs compared to other programming languages. We just need to use the inbuilt net/http package to invoke the REST service.

Example: Continue from the previous section
Step 1: Modify main.go and update the code to call the address API. The code below replaces the previous one to read the response directly using the stringformatter package.
//get address data using the RestAPI call
status, err3 := getAddress()

if err3 != nil || !status {
fmt.Println("Exception occured to get address")
panic(err3)
}

Step 2: Add the function getAddress() in the main.go file and copy the below code. In this function, we are consuming the REST APIs
// Get the address information from database using the REST API call.
func getAddress() (bool, error) {

// invoke the rest API using golangs HTTP package.
response, err := http.Get("http://localhost:8080/address")
if err != nil {
fmt.Print(err.Error())
os.Exit(1)
}

//Read all response data from the API
responseData, err := io.ReadAll(response.Body)
if err != nil {
log.Fatal(err)
}
ch := make(chan string)

// extract the response from JSON to struct
var addresses []stringformater.Address
json.Unmarshal(responseData, &addresses)

// Check if the address has values
if len(addresses) > 0 {

// Loop through the collection and read properties
for _, addressInfo := range addresses {
address := &stringformater.Address{HouseNumber: addressInfo.HouseNumber, StreetName: addressInfo.StreetName, City: addressInfo.City, State: addressInfo.State, ZipCode: addressInfo.ZipCode}
go formatString(address, ch)
formatedAddress := <-ch // receive formated data from channel with channel status
fmt.Println(formatedAddress)
}
} else {
fmt.Println("No objects in the address collection")
}

return true, nil
}

Step 3: Open the new terminal n vs code and run the module rest_api. The module starts the HTTP web server and waits for the HTTP requests.

Step 4: Open another terminal in vs code and run the main. go module. you will see the address data from the database in the terminal console.


S******-MacBook-Pro:main s********i$ go run .
Create a new date instance using a constructor!
No error occurred.
Formatted date: 07/02/2021
Use the name format package to format the name!
Inside the Get Person function
Start Reading configuration file:
End Reading configuration file:
Jhonney Walker
Bil Gates
Use name format package to format billing address!
1234 abc Pkwy, Frederick, MD 21000
5678 xyz lane, Silver Spring, MD 22000
Channel Ch is not closed!

Complete code: https://github.com/learnwithsharad/learngo/tree/shaad-RestAPI

Reference

https://pkg.go.dev/net/http#HandlerFunc

https://go.dev/doc/tutorial/web-service-gin

https://gin-gonic.com/docs/examples/bind-uri/

No comments:

Post a Comment

Mastering Go: Part 14 - Messaging with Apache Kafka(Go Implementation)

In this post, we will explore how to implement Apache Kafka messaging in Golang. Several packages are available, and the best choice depends...