Simple REST API server with Go
Table of Contents
Representational State Transfer (REST
) is a popular architecture in software development. It is used for communication between applications e.g. frontend and backend. Hypertext Transfer Protocol (HTTP
) and JavaScript Object Notation (JSON
) are used for communication.
The frontend/client send HTTP requests
to the backend/server. The backend send responses
. Both request and response use JSON object as message. JSON objects are very easy to read as they are formatted in key-value
pairs.
What we’ll do? #
In this article, we’ll build a RESTful server without using third party libraries which is the beauty of Go
as the standard library can do a lot of things. Some of the popular Go libraries are Gin Web Framework, Fiber and Gorilla
You can find the Github repo here.
What does a server do? #
The server will handle incoming HTTP requests and perform CRUD
(create, read, update, delete) operations. The server will have endpoint
to operate HTTP requests. Finally, we will create a Person
struct for data.
In this tutorial, we will define the endpoints
as:
/get
to retrieve all person/person/{id}
to retrieve a specific person based onid
/add
to create a newPerson
/update/{id}
to update an existingPerson
in the list/delete/{id}
to delete an existingPerson
in the list
Person
object for data>Declaring Person
object for data
#
type Person struct {
ID int `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}
var People []Person
In the code shown above, we declare Person
object and People
as slice of Person
(array of people). You can consider slice as array in Go
.
The syntax `json:"id"`
is to serialize the incoming JSON
object. It is used for converting JSON
objects to Go struct
and vice versa. It is important to use the exact format and no spacing unless it is in double quote.
Implementing the HTTP web server #
The server will be implemented in the main function. For the purpose of dealing with multiple routes (different endpoints), a multipluxer is created using the standard library mux := http.NewServeMux()
and used it in the server.
var PORT = ":8080"
func main() {
mux := http.NewServeMux()
s := &http.Server{
Addr: PORT,
Handler: mux,
IdleTimeout: 10 * time.Second,
ReadTimeout: time.Second,
WriteTimeout: time.Second,
}
}
Don’t forget to change the PORT number if you have services running on :8080
.
Starting the server #
The code below will start the server on localhost:8080
. However, you will see nothing at the moment as there is not any handler function that sends a response.
fmt.Println("Servering running at", PORT)
_ := s.ListenAndServe()
You can start the server with the command go run {filename}.go
.
Retrieving all person (Read operation) #
Retrieving all person is the simplest job as it does not have params or query in the URL. Just a simple HTTP GET request
will do the job.
func getPeople(w http.ResponseWriter, r *http.Request) {
fmt.Println("Endpoint Hit: getAllPerson")
json.NewEncoder(w).Encode(People)
}
Let’s breakdown the code json.NewEncoder(w).Encode(People)
.
json
is the standard library package.NewEncoder()
andEncode()
are functions that come with it.Encoder()
send back a response to where the request came from. Seew
,ResponseWriter
as the arguement.Encode()
serialized thePeople
slice to include it in HTTP response. SeePeople
we declared early as the arguement.
Retrieve a specific person (Read operation) #
This operation is similar to the pervious one except the URL needs to have ID so that the server knows which Person
to send back.
func getPerson(w http.ResponseWriter, r *http.Request) {
fmt.Println("Endpoint Hit: getPerson")
vars := strings.Split(r.URL.Path, "/")
param := vars[len(vars)-1]
id, _ := strconv.Atoi(param)
for _, person := range People {
if person.ID == id {
json.NewEncoder(w).Encode(person)
}
}
}
The URL http://localhost:8080/person/1
will return Person
with ID: 1
.
First, the function will extract 1
from the URL and convert it to integar. Then, use that value to find the Person
with the same ID
and send back a response with it.
Updating a person (Update Operation) #
Update function works same way as getPerson()
, it extracts the ID
from the URL. And, create a updatedPerson
using that ID. Then, add the updated value from HTTP request body
to the updatedPerson
object.
func updatePerson(w http.ResponseWriter, r *http.Request) {
fmt.Println("Endpoint Hit: updatePerson")
vars := strings.Split(r.URL.Path, "/")
param := vars[len(vars)-1]
id, _ := strconv.Atoi(param)
updatedPerson := Person{
ID: id,
}
json.NewDecoder(r.Body).Decode(&updatedPerson)
for i, person := range People {
if person.ID == id {
People[i] = updatedPerson
json.NewEncoder(w).Encode(People[i])
return
}
}
}
And finally, loop through the slice, look for the element with the same ID to replace with the Person object wiht updated values.
Deleting a person (Delete Operation) #
Deleting the element works the same way as update operation.
- extract
ID
- loop through the slice
- find the right element
- and remove the element
func deletePerson(w http.ResponseWriter, r *http.Request) {
fmt.Println("Endpoint Hit: deletePerson")
vars := strings.Split(r.URL.Path, "/")
param := vars[len(vars)-1]
id, _ := strconv.Atoi(param)
for key, person := range People {
if person.ID == id {
People = append(People[:key], People[key+1:]...)
}
}
}
However, it is more like updating the existing slice. When it found the right element to remove, it cut the slice in half and concatanates the two pieces back together.
Testing the APIs #
If you don’t have a frontend, you can use a VScode extension Thunder Client
to test the APIs. It is convenient as you can perform them in the same window unlike Postman
application.
Before sending the HTTP requests, let’s add some dummy data, Person
objects, in People
slice. Since we are not using a database in this tutorial, we will hardcode it. Add the following code before creating the server.
People = []Person{
{1, "Wai Yan", 27},
{2, "John", 48},
{3, "Jane", 35},
}
This will add three Person
objects to People
slice.
Then we implement the HTTP handlers in the main
function for different endpoints as we defined periviously. In the below code, the strings are endpoints and http.HandlerFunc
is calling the respestive functions to operate CRUD.
mux.Handle("/get", http.HandlerFunc(getPeople))
mux.Handle("/person/", http.HandlerFunc(getPerson))
mux.Handle("/add", http.HandlerFunc(addPerson))
mux.Handle("/update/", http.HandlerFunc(updatePerson))
mux.Handle("/delete/", http.HandlerFunc(deletePerson))
People
slice will be reset.