Rest API Golang

Rest API Golang

API URL

https://<host>/<productType>/<productName>/v1
siteID/{siteID}/<resource>/resourceID

Hello World !

package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
	// 1. http.HandleFunc(pattern, handler)

	// 2. http.ListenAndServe(url, handler)

	// Register the pattern and handler with default mutiplexer
	// define routes
	http.HandleFunc("/greet", greet)

	err := http.ListenAndServe("localhost:8000", nil)
	if err != nil {
		log.Fatal(err)
	}
}

func greet(writer http.ResponseWriter, request *http.Request) {
	fmt.Fprint(writer, "Hello world !")
}

How to run

  1. Start server in 1st terminal
go run server.go

Verify

curl http://localhost:8000/greet
Hello world !%

Marshalling Data structure to Json Representation

  • Use json.NewEncoder to list of customers in response
  • e.g. json.NewEncoder(writer).Encode(customers)

Basic code

package main

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

type Customer struct {
	Name   string
	City   string
	Zipode int
}

func main() {
	// 1. http.HandleFunc(pattern, handler)

	// 2. http.ListenAndServe(url, handler)

	// Register the pattern and handler with default mutiplexer
	// define routes
	http.HandleFunc("/greet", greet)

	// 2nd API
	http.HandleFunc("/customers", getAllCustomer)

	err := http.ListenAndServe("localhost:8000", nil)
	if err != nil {
		log.Fatal(err)
	}
}

func getAllCustomer(writer http.ResponseWriter, request *http.Request) {
	customers := []Customer{
		{Name: "Roger", City: "Delhi", Zipode: 110012},
		{Name: "Rafa", City: "Mumbai", Zipode: 410100},
	}

	// But here response is set as Content Type : text/plain; charset=utf-8
	// And not JSON
	json.NewEncoder(writer).Encode(customers)
}

func greet(writer http.ResponseWriter, request *http.Request) {
	fmt.Fprint(writer, "Hello world !")
}

Response

text_response
text_response

Wrong Content Type

We observe context type as text, but is should be json.

text_content_type
text_content_type

Correct JSON Content Type

	// Add new key to header for setting correct content type
	writer.Header().Add("Content-Type", "application/json")

Updated Code

package main

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

type Customer struct {
	Name   string
	City   string
	Zipode int
}

func main() {
	// 1. http.HandleFunc(pattern, handler)

	// 2. http.ListenAndServe(url, handler)

	// Register the pattern and handler with default mutiplexer
	// define routes
	http.HandleFunc("/greet", greet)

	// 2nd API
	http.HandleFunc("/customers", getAllCustomer)

	err := http.ListenAndServe("localhost:8000", nil)
	if err != nil {
		log.Fatal(err)
	}
}

func getAllCustomer(writer http.ResponseWriter, request *http.Request) {
	customers := []Customer{
		{Name: "Roger", City: "Delhi", Zipode: 110012},
		{Name: "Rafa", City: "Mumbai", Zipode: 410100},
	}

	// But here response is set as Content Type : text/plain; charset=utf-8
	// And not JSON

	// Add new key to header for setting correct content type
	writer.Header().Add("Content-Type", "application/json")

	json.NewEncoder(writer).Encode(customers)
}

func greet(writer http.ResponseWriter, request *http.Request) {
	fmt.Fprint(writer, "Hello world !")
}

Corrected Response

[
    {
        "Name": "Roger",
        "City": "Delhi",
        "Zipode": 110012
    },
    {
        "Name": "Rafa",
        "City": "Mumbai",
        "Zipode": 410100
    }
]
json_content_type
json_content_type

With Updated json tags

type Customer struct {
	Name   string `json:"full_name"`
	City   string `json:"city"`
	Zipode int    `json:"zip_code"`
}

********************************
Output
********************************
[
    {
        "full_name": "Roger",
        "city": "Delhi",
        "zip_code": 110012
    },
    {
        "full_name": "Rafa",
        "city": "Mumbai",
        "zip_code": 410100
    }
]

XML encoding

  • Add xml tags
  • Use xml.NewEncoder
package main

import (
	"encoding/xml"
	"fmt"
	"log"
	"net/http"
)

type Customer struct {
	Name   string `json:"full_name" xml:"name"`
	City   string `json:"city" xml:"city"`
	Zipode int    `json:"zip_code" xml:"zipcode"`
}

func main() {
	// 1. http.HandleFunc(pattern, handler)

	// 2. http.ListenAndServe(url, handler)

	// Register the pattern and handler with default mutiplexer
	// define routes
	http.HandleFunc("/greet", greet)

	// 2nd API
	http.HandleFunc("/customers", getAllCustomer)

	err := http.ListenAndServe("localhost:8000", nil)
	if err != nil {
		log.Fatal(err)
	}
}

func getAllCustomer(writer http.ResponseWriter, request *http.Request) {
	customers := []Customer{
		{Name: "Roger", City: "Delhi", Zipode: 110012},
		{Name: "Rafa", City: "Mumbai", Zipode: 410100},
	}

	// But here response is set as Content Type : text/plain; charset=utf-8
	// And not JSON

	// Add new key to header for setting correct content type
	// writer.Header().Add("Content-Type", "application/json")
	// json.NewEncoder(writer).Encode(customers)

	// Add new key to header for setting correct content type
	writer.Header().Add("Content-Type", "application/xml")
	xml.NewEncoder(writer).Encode(customers)

}

func greet(writer http.ResponseWriter, request *http.Request) {
	fmt.Fprint(writer, "Hello world !")
}

Output

***********************
Body Response 
***********************
<Customer>
    <name>Roger</name>
    <city>Delhi</city>
    <zipcode>110012</zipcode>
</Customer>
<Customer>
    <name>Rafa</name>
    <city>Mumbai</city>
    <zipcode>410100</zipcode>
</Customer>

***********************
Content-Type : application/xml

Client Side Request Header

client_side_header
client_side_header
package main

import (
	"encoding/json"
	"encoding/xml"
	"fmt"
	"log"
	"net/http"
)

type Customer struct {
	Name   string `json:"full_name"`
	City   string `json:"city"`
	Zipode int    `json:"zip_code"`
}

func main() {
	// 1. http.HandleFunc(pattern, handler)

	// 2. http.ListenAndServe(url, handler)

	// Register the pattern and handler with default mutiplexer
	// define routes
	http.HandleFunc("/greet", greet)

	// 2nd API
	http.HandleFunc("/customers", getAllCustomer)

	err := http.ListenAndServe("localhost:8000", nil)
	if err != nil {
		log.Fatal(err)
	}
}

func getAllCustomer(writer http.ResponseWriter, request *http.Request) {
	customers := []Customer{
		{Name: "Roger", City: "Delhi", Zipode: 110012},
		{Name: "Rafa", City: "Mumbai", Zipode: 410100},
	}

	// But here response is set as Content Type : text/plain; charset=utf-8
	// And not JSON

	// Add new key to header for setting correct content type
	// writer.Header().Add("Content-Type", "application/json")
	// json.NewEncoder(writer).Encode(customers)

	if request.Header.Get("Content-Type") == "application/xml" {
		writer.Header().Add("Content-Type", "application/xml")
		xml.NewEncoder(writer).Encode(customers)
	} else {
		writer.Header().Add("Content-Type", "application/json")
		json.NewEncoder(writer).Encode(customers)
	}

}

func greet(writer http.ResponseWriter, request *http.Request) {
	fmt.Fprint(writer, "Hello world !")
}

Output

<Customer>
    <Name>Roger</Name>
    <City>Delhi</City>
    <Zipode>110012</Zipode>
</Customer>
<Customer>
    <Name>Rafa</Name>
    <City>Mumbai</City>
    <Zipode>410100</Zipode>
</Customer>

Go Modules

  • Create new package of app which will contain pattern and all handlers
  • Create a separate function Start which will contain code for starting server.
New folder Structure will looks like this

restapi/
    - Readme.txt
    - go.mod
    - server.go
    app/
        - app.go
        - handlers.go

Commands used

go mod init <name of module> 
e.g. go mod init restapi

go build        
go run server.go

go.mod file

module restapi

go 1.21.13

server.go file

package main

import (
	"restapi/app"
)

// created custom multiplexer
func main() {
	app.Start()
}

app/app.go file

package app

import (
	"log"
	"net/http"
)

// created custom multiplexer
func Start() {

	mux := http.NewServeMux()

	mux.HandleFunc("/greet", greet)
	mux.HandleFunc("/customers", getAllCustomer)

	err := http.ListenAndServe("localhost:8000", mux)
	if err != nil {
		log.Fatal(err)
	}
}

app/handlers.go file

package app

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

type Customer struct {
	Name   string `json:"full_name"`
	City   string `json:"city"`
	Zipode int    `json:"zip_code"`
}

func getAllCustomer(writer http.ResponseWriter, request *http.Request) {
	customers := []Customer{
		{Name: "Roger", City: "Delhi", Zipode: 110012},
		{Name: "Rafa", City: "Mumbai", Zipode: 410100},
	}

	if request.Header.Get("Content-Type") == "application/xml" {
		writer.Header().Add("Content-Type", "application/xml")
		xml.NewEncoder(writer).Encode(customers)
	} else {
		writer.Header().Add("Content-Type", "application/json")
		json.NewEncoder(writer).Encode(customers)
	}

}

func greet(writer http.ResponseWriter, request *http.Request) {
	fmt.Fprint(writer, "Hello world !")
}

gorilla/mux

Here, we will cover below topics

  • Use of github.com/gorilla/mux
  • Get values from request using mux.Vars(request)
    • check to accept int only using regex
    • /customers/{customer_id:[0-9]+}
  • Method Matcher
    • e.g. http.MethodGet, MethodPost for particular handlers
router.HandleFunc("/customers/{customer_id:[0-9]+}", getCustomer).Methods(http.MethodGet)

router.HandleFunc("/customers", createCustomer).Methods(http.MethodPost)
// File : app/app.go

package app

import (
	"log"
	"net/http"

	"github.com/gorilla/mux"
)

// method matcher
func Start() {

	// mux := http.NewServeMux()
	router := mux.NewRouter()

	router.HandleFunc("/greet", greet).Methods(http.MethodGet)
	router.HandleFunc("/customers", getAllCustomer).Methods(http.MethodGet)
	router.HandleFunc("/customers/{customer_id:[0-9]+}", getCustomer).Methods(http.MethodGet)
	router.HandleFunc("/customers", createCustomer).Methods(http.MethodPost)

	err := http.ListenAndServe("localhost:8000", router)
	if err != nil {
		log.Fatal(err)
	}
}
  • Getting values from request URL
    • using mux.Vars(request)
  • Post api handling
func createCustomer(writer http.ResponseWriter, request *http.Request) {
	fmt.Fprint(writer, "Post request received.")
}

func getCustomer(writer http.ResponseWriter, request *http.Request) {
	vars := mux.Vars(request)
	fmt.Fprint(writer, vars["customer_id"])
}
// File : app/handlers.go

package app

import (
	"encoding/json"
	"encoding/xml"
	"fmt"
	"net/http"

	"github.com/gorilla/mux"
)

type Customer struct {
	Name   string `json:"full_name"`
	City   string `json:"city"`
	Zipode int    `json:"zip_code"`
}

func createCustomer(writer http.ResponseWriter, request *http.Request) {
	fmt.Fprint(writer, "Post request received.")
}

func getCustomer(writer http.ResponseWriter, request *http.Request) {
	vars := mux.Vars(request)
	fmt.Fprint(writer, vars["customer_id"])
}

func getAllCustomer(writer http.ResponseWriter, request *http.Request) {
	customers := []Customer{
		{Name: "Roger", City: "Delhi", Zipode: 110012},
		{Name: "Rafa", City: "Mumbai", Zipode: 410100},
	}

	// But here response is set as Content Type : text/plain; charset=utf-8
	// And not JSON

	// Add new key to header for setting correct content type
	// writer.Header().Add("Content-Type", "application/json")
	// json.NewEncoder(writer).Encode(customers)

	if request.Header.Get("Content-Type") == "application/xml" {
		writer.Header().Add("Content-Type", "application/xml")
		xml.NewEncoder(writer).Encode(customers)
	} else {
		writer.Header().Add("Content-Type", "application/json")
		json.NewEncoder(writer).Encode(customers)
	}

}

func greet(writer http.ResponseWriter, request *http.Request) {
	fmt.Fprint(writer, "Hello world !")
}

Assignment

Question

  • Build the time API based on the instructions and the requirements and handle errors appropriately
  • SomeTime Inc wants to build an API that returns the current time.

Pro Tip: Focus on one task at a time

Below are the requirements

  1. The endpoint should be exposed as /api/time
  2. The output should be in JSON
{
    "current_time": "2021-08-09 11:18:06 +0000 UTC"
}
  1. A user can also request to return the current time in another timezone.
/api/time?tz=America/New_York
  1. If the tz parameter is not provided in the URL, it should return the time in UTC
  2. In case of an invalid TZ database, the API should return the error message “invalid timezone” along with the status code 404
  3. Bonus: A user can request time in multiple timezones i.e. 
    • /api/time?tz=America/New_York,Asia/Kolkata

API Response:

{    
    "Asia/Kolkata": "2021-08-09 01:23:42 +0530 IST",    
    "America/New_York": "2021-08-09 01:23:42 -0400 EDT"
}

The below program will help you with the timezone conversion

package main
 
import (
    "fmt"
    "time"
)
 
func main() {
    loc, _ := time.LoadLocation("Asia/Shanghai")
    fmt.Println(time.Now().In(loc))
 
    loc, _ := time.LoadLocation("America/New_York")
    fmt.Println(time.Now().In(loc))
}

Please visit https: https://codeandalgo.com for more such contents.

Leave a Reply

Your email address will not be published. Required fields are marked *