Deploy Containers Close to Your Users

This Engineering Education (EngEd) Program is supported by Section.

Instantly deploy containers globally. Section is affordable, simple and powerful.

Get Started for Free.

How to Build a REST API with Golang using Native Modules

May 4, 2022

Go is a fast-growing language among developers. Golang ecosystem has a ton of native libraries. However, it also has third-party libraries that developers can use to build APIs.

When creating REST APIs with Go, you can choose to use various third-party libraries, such as Gorm, Go fiber, Gin, fast HTTP, etc.

Developers can also use libraries that are native to Golang. These libraries do not require you to download them into your local project when building APIs.

In this guide, we will build a basic REST API using Golang’s native libraries. We will not use any third-party libraries, meaning we won’t download any library in our local project.

Table of contents

Prerequisites

To follow along with this tutorial, the reader will need:

  • Some basic knowledge of Golang.
  • An IDE installed on your computer (Preferably VS Code).
  • Go installed on your computer.

Once you have installed Go, run the command below to verify Go’s version.

go version

Check this guide to get started with how to write and run Golang programs.

Setting up a Golang project

To start a Go project, you need to first initialize Go within your project.

Create a project folder where you want your app to live. Open a command line that points to the newly created directory, then initialize Golang using the following command:

go mod init native-go-api

This will instantiate Go by creating a go.mod file. Any Go package that you install, and its dependencies, are saved here.

The go.mod file has a native-go-api module name. This helps you create local modules and import them so that other local modules of your project can use them.

Creating a CRUD Golang REST API using native modules

Let’s dive in and create a Golang REST API using native modules. In this tutorial, we will create a simple movie API. It will help showcase how to use these native libraries in a typical Golang application.

First, you need to model the data you want your API to interact with. To do this, create a models folder at the root directory of your project.

Then create a movie.go file and add the following model:

package models

// Movie for modeling data dummy
type Movie struct {
 ID      string `json:"id"`
 Title    string `json:"title"`
 Description string `json:"description"`
}

A model sets a blueprint of your API. It sets the data and its values. This adds the data types to each movie property that the API will use.

When using Go to create a model, use the keyword struct. Here, set a struct of type Movie and add its properties as shown in the code block above.

This API will interact with dummy data as the application database. Therefore, create a dummy database within your application.

To do this, create a folder db inside your project directory. Inside this db folder, create a db.go file and add the following code:

package db

import ("native-go-api/models")

// set up a database dummy
var (Moviedb = make(map[string]models.Movie))

This will set up a dummy database, Moviedb. This uses the make built-in function, it allocates and initializes a memory object of type map.

It also takes a type as its first argument, make type returns the same data as the type of its argument. Here, make will refer to the type of Movie model set for the movies data.

Setting up the movie handlers

An API uses HTTP methods to interact with your data. Therefore, you need to set up the right function that refers to the HTTP methods such as GET, POST, PUT, and DELETE.

This will also set up the request and response message that every HTTP method should return.

In this example, the API will always return any data in JSON format. We need to set up a ReturnJsonResponse function that will return the movies data in JSON format.

To do this, create a utils folder inside your project directory. Inside this utils folder, create a utils.go file and add the following code:

package utils

import (
 "net/http"
)

// ReturnJsonResponse function for returning movies data in JSON format
func ReturnJsonResponse(res http.ResponseWriter, httpCode int, resMessage []byte) {
 res.Header().Set("Content-type", "application/json")
 res.WriteHeader(httpCode)
 res.Write(resMessage)
}

This will use the Go’s native http module. The ReturnJsonResponse function will convert any response that the HTTP server returns and set the Content-type as JSON.

Once this is set, specify your HTTP handler functions. First, create a handler folder inside your project directory.

Inside this handler folder, create a movies.go file and start importing the Go native modules and the local modules created in the above steps, as shown below:

package handler

import (
 "native-go-api/models"
 "native-go-api/db"
 "native-go-api/utils"
 "net/http"
 "encoding/json"
)

This imports the local modules that we have created. Other native modules used include the encoding/json for encoding any JSON data and net/http for setting up the server-based methods, requests, and responses.

Next, let’s create the handler functions as follows.

Creating an API test handler

Set a test handler that does not interact with the set API data to test if the API works correctly.

// root api test handler
func TestHandler(res http.ResponseWriter, req *http.Request) {

 // Add the response return message
 HandlerMessage := []byte(`{
  "success": true,
  "message": "The server is running properly"
  }`)

 utils.ReturnJsonResponse(res, http.StatusOK, HandlerMessage)
}

When this handler is executed, it will return the set message to show that the server is ready.

Getting Movies handler

To fetch all the movies list that the dummy database will have, create a GetMovies() function as shown below:

// Get Movies handler
func GetMovies(res http.ResponseWriter, req *http.Request) {

 if req.Method != "GET" {

  // Add the response return message
  HandlerMessage := []byte(`{
   "success": false,
   "message": "Check your HTTP method: Invalid HTTP method executed",
  }`)

  utils.ReturnJsonResponse(res, http.StatusMethodNotAllowed, HandlerMessage)
  return
 }

 var movies []models.Movie

 for _, movie := range db.Moviedb {
  movies = append(movies, movie)
 }

 // parse the movie data into json format
 movieJSON, err := json.Marshal(&movies)
 if err != nil {
  // Add the response return message
  HandlerMessage := []byte(`{
   "success": false,
   "message": "Error parsing the movie data",
  }`)

  utils.ReturnJsonResponse(res, http.StatusInternalServerError, HandlerMessage)
  return
 }

 utils.ReturnJsonResponse(res, http.StatusOK, movieJSON)
}

This handler will access the database and check if there are any movie records. The server will fetch this list and return the response to the user.

The server will parse the movie data into JSON format, then return the response message with the fetched data.

Getting a single movie handler

Additionally, you can fetch a single movie from the movies list database. The movie structure has an ID unique to every movie in this case.

When fetching a single movie, the server will use the ID value as the parameter to decide which movie the user wants to fetch.

// Get a single movie handler
func GetMovie(res http.ResponseWriter, req *http.Request) {

 if req.Method != "GET" {
  // Add the response return message
  HandlerMessage := []byte(`{
   "success": false,
   "message": "Check your HTTP method: Invalid HTTP method executed",
  }`)

  utils.ReturnJsonResponse(res, http.StatusMethodNotAllowed, HandlerMessage)
  return
 }

 if _, ok := req.URL.Query()["id"]; !ok {
  // Add the response return message
  HandlerMessage := []byte(`{
   "success": false,
   "message": "This method requires the movie id",
  }`)

  utils.ReturnJsonResponse(res, http.StatusInternalServerError, HandlerMessage)
  return
 }

 id := req.URL.Query()["id"][0]

 movie, ok := db.Moviedb[id]
 if !ok {
  // Add the response return message
  HandlerMessage := []byte(`{
   "success": false,
   "message": "Requested movie not found",
  }`)

  utils.ReturnJsonResponse(res, http.StatusNotFound, HandlerMessage)
  return
 }

 // parse the movie data into json format
 movieJSON, err := json.Marshal(&movie)
 if err != nil {
  // Add the response return message
  HandlerMessage := []byte(`{
   "success": false,
   "message": "Error parsing the movie data",
  }`)

  utils.ReturnJsonResponse(res, http.StatusInternalServerError, HandlerMessage)
  return
 }

 utils.ReturnJsonResponse(res, http.StatusOK, movieJSON)
}

Here the endpoint that fetches a single movie will have the id as the URL parameter. This endpoint will only execute a GET request.

If the user uses a different method, set the error message to show what they are supposed to do. If the user forgets to add the parameter id, add the response return message. Check other handler messages implemented in this function.

Adding a movie handler

To add a movie, execute the POST method using the AddMovie() function below:

// Add a movie handler
func AddMovie(res http.ResponseWriter, req *http.Request) {

 if req.Method != "POST" {
  // Add the response return message
  HandlerMessage := []byte(`{
   "success": false,
   "message": "Check your HTTP method: Invalid HTTP method executed",
  }`)

  utils.ReturnJsonResponse(res, http.StatusMethodNotAllowed, HandlerMessage)
  return
 }

 var movie models.Movie

 payload := req.Body

 defer req.Body.Close()
 // parse the movie data into json format
 err := json.NewDecoder(payload).Decode(&movie)
 if err != nil {
  // Add the response return message
  HandlerMessage := []byte(`{
   "success": false,
   "message": "Error parsing the movie data",
  }`)

  utils.ReturnJsonResponse(res, http.StatusInternalServerError, HandlerMessage)
  return
 }

 db.Moviedb[movie.ID] = movie
 // Add the response return message
 HandlerMessage := []byte(`{
  "success": true,
  "message": "Movie was successfully created",
  }`)

 utils.ReturnJsonResponse(res, http.StatusCreated, HandlerMessage)
}

This function will check the user’s payload with the new data that the user wants to add. The service will assign this data to the req.Body. Each added movie will have all the properties set in the Movie model.

Thus, if the user fails to specify this data field, the server will return an error message Error parsing the movie data.

If the operation is successful, the server will return a server message Movie was successfully created.

Deleting a movie handler

To delete a movie record, add this DeleteMovie() function to your handler list.

// Delete a movie handler
func DeleteMovie(res http.ResponseWriter, req *http.Request) {

 if req.Method != "DELETE" {
  // Add the response return message
  HandlerMessage := []byte(`{
   "success": false,
   "message": "Check your HTTP method: Invalid HTTP method executed",
  }`)

  utils.ReturnJsonResponse(res, http.StatusMethodNotAllowed, HandlerMessage)
  return
 }

 if _, ok := req.URL.Query()["id"]; !ok {
  // Add the response return message
  HandlerMessage := []byte(`{
   "success": false,
   "message": "This method requires the movie id",
  }`)

  utils.ReturnJsonResponse(res, http.StatusBadRequest, HandlerMessage)
  return
 }

 id := req.URL.Query()["id"][0]
 movie, ok := db.Moviedb[id]
 if !ok {
  // Add the response return message
  HandlerMessage := []byte(`{
   "success": false,
   "message": "Requested movie not found",
  }`)

  utils.ReturnJsonResponse(res, http.StatusNotFound, HandlerMessage)
  return
 }
 // parse the movie data into json format
 movieJSON, err := json.Marshal(&movie)
 if err != nil {
  // Add the response return message
  HandlerMessage := []byte(`{
   "success": false,
   "message": "Error parsing the movie data"
  }`)

  utils.ReturnJsonResponse(res, http.StatusBadRequest, HandlerMessage)
  return
 }

 utils.ReturnJsonResponse(res, http.StatusOK, movieJSON)
}

DeleteMovie() uses id to add to the URL parameter. Based on the request sent, this checks for the existing movies that the server should delete.

For the server to delete that movie, it checks the parameter id against the id saved in the dummy data. If the movie is not found, the server would return a response message Requested movie not found.

Initializing the server and routes

To execute the above handlers, you need to:

  • Set up a server that runs on the localhost.
  • Initialize the database data.
  • Set up the routes that point to each handler function.

To do so, create a main.go file inside the project directory. Then set up the server:

First, import the local module and the Go native modules.

package main

import (
 "fmt"
 "log"
 "native-go-api/db"
 "native-go-api/handler"
 "native-go-api/models"
 "net/http"
 "os"
)

Here import the following native modules:

  • fmt - For setting Println messages.
  • log - For logging basic application messages.
  • os - For accessing the local computer to execute the server locally.
  • net/http - For setting up the HTTP server and executing the server routes.

Next, create a main() as shown below:

func main() {

}

Inside this function:

  • Add a print message that shows the server is running.
log.Print("The is Server Running on localhost port 3000")
  • Initialize the database and set the dummy database data.
// initialize the database
db.Moviedb["001"] = models.Movie{ID: "001", Title: "A Space Odyssey", Description: "Science fiction"}
db.Moviedb["002"] = models.Movie{ID: "002", Title: "Citizen Kane", Description: "Drama"}
db.Moviedb["003"] = models.Movie{ID: "003", Title: "Raiders of the Lost Ark", Description: "Action and adventure"}
db.Moviedb["004"] = models.Movie{ID: "004", Title: "66. The General", Description: "Comedy"}
  • Add the server routes that point to each handler.
// route goes here

// test route
http.HandleFunc("/", handler.TestHandler)
// get movies
http.HandleFunc("movies", handler.GetMovies)
// get a single movie
http.HandleFunc("/movie", handler.GetMovie)
// Add movie
http.HandleFunc("/movie/add", handler.AddMovie)
// delete movie
http.HandleFunc("/movie/delete", handler.DeleteMovie)
  • Set the server port and start the server.
// listen port
err := http.ListenAndServe(":3000", nil)
// print any server-based error messages
if err != nil {
 fmt.Println(err)
 os.Exit(1)
}

Your server should be ready. To test it, run go run main.go and start interacting with the different routes of your API.

Conclusion

This tutorial has taught you how to use the native Go modules and create a basic movie API. These native modules help developers interact with the vanilla Golang code.

Some Golang third-party libraries you can use to set a server include, mux, Go Fiber, Gorm, and Echo.

Happy coding!


Peer Review Contributions by: Jethro Magaji