Simple REST API with Go
Let's create a simple REST API with Go or Golang to understand CRUD (Create Read Update Delete) operations with the most commonly used HTTP methods; POST, GET, PUT, PATCH, and DELETE.
My team assigned me to create a service that will handle millions of requests. I decided to create it with Go. The most reason is fast. You code this simple language, compile it, then run it. The compiled one is the native program, create a service on it, then run it on the operating system. It has no dependency.
Some high tech companies or startups have some services that run on Go. It is worth to learn. This tutorial is to make you a little familiar with Go. What will you create is a simple REST API for articles or a blog. Here is my updated article about a simple explanation of REST API. So, it is a nice start if you are new to Go.
List of contents:
Tools
First, you need to install Go on your system. You can go to https://golang.org/doc/install then follow the instructions. The last is the absolutely text editor. I was using VS Code while writing this tutorial.
Packages
package main
import (
"encoding/json"
"io/ioutil"
"log"
"net/http"
"strconv"
"github.com/gorilla/mux"
)
First, you need to write your package. It is the same as the main function name, main()
, and the file name, main.go
. You don't need to write the packages that you need early because VS Code can do that on the fly. It can suggest you related extensions that you need to install. It makes your code easy and simple.
Data
type article struct {
Title string `json:"title"`
Description string `json:"description"`
}
var articles = map[int]article{
1: {
"Simple REST API with Go",
"Let's create a simple REST API with Go.",
},
}
The most crucial fields of the article are the title and description. There is also some defined article. In the real situation, you need to get the data from the database. They are enough for our tutorial. The struct
is a collection of fields. I chose the map
, key-value data structure, to easily access and define the data in constant time.
Index
func index(res http.ResponseWriter, req *http.Request) {
res.Header().Set("Content-Type", "application/json")
json.NewEncoder(res).Encode(articles)
}
Just print all articles as JSON.
Create
func create(res http.ResponseWriter, req *http.Request) {
newArticle := article{}
reqBody, err := ioutil.ReadAll(req.Body)
if err != nil {
log.Fatal(err)
}
json.Unmarshal(reqBody, &newArticle)
articles[len(articles)+1] = newArticle
res.Header().Set("Content-Type", "application/json")
json.NewEncoder(res).Encode(articles)
}
Parsing JSON request to the article struct, add it to the list of articles, then print them as JSON. The log.Fatal
is to print the error and immediately abort the program. The json.Unmarshal
parses the request JSON body to the defined struct.
Read
func read(res http.ResponseWriter, req *http.Request) {
articleParam := mux.Vars(req)["article"]
articleID, err := strconv.Atoi(articleParam)
if err != nil {
log.Fatal(err)
}
if articles[articleID].Title == "" &&
articles[articleID].Description == "" {
res.WriteHeader(http.StatusNotFound)
return
}
res.Header().Set("Content-Type", "application/json")
json.NewEncoder(res).Encode(
map[int]article{
articleID: articles[articleID],
},
)
}
Get the parameter article
which is the article ID. Convert it from string to integer. Check wheater the article exists or not. Return 404 if not exist. Print the article if exist.
Replace
func replace(res http.ResponseWriter, req *http.Request) {
articleParam := mux.Vars(req)["article"]
articleID, err := strconv.Atoi(articleParam)
if err != nil {
log.Fatal(err)
}
if articles[articleID].Title == "" &&
articles[articleID].Description == "" {
res.WriteHeader(http.StatusNotFound)
return
}
updatedArticle := article{}
reqBody, err := ioutil.ReadAll(req.Body)
if err != nil {
log.Fatal(err)
}
json.Unmarshal(reqBody, &updatedArticle)
articles[articleID] = updatedArticle
res.Header().Set("Content-Type", "application/json")
json.NewEncoder(res).Encode(articles)
}
Here is we replace the entire article with the new one even only some fields updated.
Modify
func modify(res http.ResponseWriter, req *http.Request) {
articleParam := mux.Vars(req)["article"]
articleID, err := strconv.Atoi(articleParam)
if err != nil {
log.Fatal(err)
}
if articles[articleID].Title == "" &&
articles[articleID].Description == "" {
res.WriteHeader(http.StatusNotFound)
return
}
newArticle := article{}
updatedArticle := article{}
reqBody, err := ioutil.ReadAll(req.Body)
if err != nil {
log.Fatal(err)
}
json.Unmarshal(reqBody, &updatedArticle)
if updatedArticle.Title != "" {
newArticle.Title = updatedArticle.Title
} else {
newArticle.Title = articles[articleID].Title
}
if updatedArticle.Description != "" {
newArticle.Description = updatedArticle.Description
} else {
newArticle.Description = articles[articleID].Description
}
articles[articleID] = newArticle
res.Header().Set("Content-Type", "application/json")
json.NewEncoder(res).Encode(articles)
}
Here is we only update some updated fields of the article.
Remove
func remove(res http.ResponseWriter, req *http.Request) {
articleParam := mux.Vars(req)["article"]
articleID, err := strconv.Atoi(articleParam)
if err != nil {
log.Fatal(err)
}
if articles[articleID].Title == "" &&
articles[articleID].Description == "" {
res.WriteHeader(http.StatusNotFound)
return
}
// Delete the entry from the map.
// In the real situation,
// you need to delete it from the database.
delete(articles, articleID)
// Mostly in the delete operation,
// return status ok is enough if succeed.
res.WriteHeader(http.StatusOK)
// Feel free to open this comment,
// to check that the entry is deleted.
// res.Header().Set("Content-Type", "application/json")
// json.NewEncoder(res).Encode(articles[articleID])
}
Delete or remove the article. The status must be OK even if there is no response data.
Main
func main() {
router := mux.NewRouter()
version := "/v1"
path := "/articles"
router.HandleFunc(version+path, index).Methods("GET")
router.HandleFunc(version+path, create).Methods("POST")
router.HandleFunc(version+path+"/{article}", read).Methods("GET")
router.HandleFunc(version+path+"/{article}", replace).Methods("PUT")
router.HandleFunc(version+path+"/{article}", modify).Methods("PATCH")
router.HandleFunc(version+path+"/{article}", remove).Methods("DELETE")
log.Fatal(http.ListenAndServe(":80", router))
}
We route those functions to some HTTP methods even the URL address is the same. That is the REST API technically.
Conclusion
Easy and simple right? You can use go run main.go
for every time you have made some changes and go build
for the final one. Here is the final code along with a Postman collection ready for you to try, https://github.com/aristorinjuang/go-rest-api. In the end, it brought an experience of Go to you. We had implemented CRUD operations to the REST API and explained the most commonly used HTTP methods of the REST API, especially PUT vs PATCH.
Related Articles
- REST API
- Intel Edge AI Scholarship Foundation Course, OpenVINO Fundamentals
- Microservices
- Python for Data Science and Machine Learning Bootcamp
- PyTorch Scholarship Challenge from Facebook