# Introduction to Go - Create your first REST API


>Go (Golang) is the new kid on the block in terms of the recent popularity surge. It is small, stable, simple to use and learn, fast, compiled (native code), and heavily used in cloud tools and services (Docker, Kubernetes, ...). There is no reason not to take it for a spin considering all of the perks that come with it. In this tutorial, we will build a simple book store REST API.

> **NOTE**: In a hurry? Get the final code [here](https://gitlab.com/vojko.pribudic/bookstore-api).

## Prerequisites

Before we start, some of the prerequisites we will use:

* [Go](https://golang.org/dl/)

* [gorilla/handlers](https://github.com/gorilla/handlers)

* [gorilla/mux](https://github.com/gorilla/mux)


Since we will build a complete API solution, I highly recommend that you take a look at [**Go tour**](https://tour.golang.org/welcome/1).

## 1. Application structure

You should have Go installed and ready by now. Open up your favorite IDE for Go (Visual Studio Code, GoLand, ...) and create a new Go project. As I mentioned earlier, the idea is to build a simple **REST API** for book store management by using **Mux**. Once you created your blank project, create the following structure inside of it:

```
├── main.go
└── src
    ├── app.go
    ├── data.go
    ├── handlers.go
    ├── helpers.go
    └── middlewares.go
```

### 1.1 Go packages and modules

This is the right time to talk about Go modules and packages. If you are familiar with Python, you might get an idea of what those are since they operate similarly.

The best way to describe a **Go package** is that it's a collection of source files in the same directory that are compiled together as a reusable unit. That means that all files that serve a similar purpose should be put inside one package. As per our structure above - `src` is one of our packages.

**Go module** is a collection of Go packages along with their dependencies, meaning that one module can consist of multiple packages. You can think of our whole application as a Go module for easier understanding.

Let's create our module by executing this command in our project root directory:

`go mod init bookstore`

You should see a new file inside your root directory called `go.mod` - so far so good :)

## 2. Building the API

It is time to start building our application. Open up your `main.go` file and insert the following code inside of it:

```
package main

import "bookstore/src"

func main() {
	src.Start()
}
```

We declared our **main Go package** (`package main`) and imported our `src` package along with the module `bookstore` prefix. Inside the function `main()` we will run the `Start()` function of package `src`. This is the only responsibility of our entry point file (`main.go`) - fire up the API.

### 2.1 Routes and handlers

Now we need to create our API router (Mux) and configure it by creating some endpoints and their handlers. Inside of your `src` package open up `app.go` and insert the following code inside of it:

```
package src

import (
	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"
	"log"
	"net/http"
	"os"
)

func Start() {
	router := mux.NewRouter()
	router.Use(commonMiddleware)
	router.HandleFunc("/book", getAllBooks).Methods(http.MethodGet)
	router.HandleFunc("/book", addBook).Methods(http.MethodPost)
	router.HandleFunc("/book/{book_id:[0-9]+}", getBook).Methods(http.MethodGet)
	router.HandleFunc("/book/{book_id:[0-9]+}", updateBook).Methods(http.MethodPut)
	router.HandleFunc("/book/{book_id:[0-9]+}", deleteBook).Methods(http.MethodDelete)
	log.Fatal(http.ListenAndServe("localhost:5000", handlers.LoggingHandler(os.Stdout, router)))
}
```

As you can see above - we declared that `app.go` is part of the `src` package and it contains the `Start()` function that we used inside our `main.go` file. We also included **two external modules** that we need to install as dependencies for our `bookstore` module. 

Execute the following commands in your terminal:

```
go get github.com/gorilla/handlers
go get github.com/gorilla/mux
```

Your `go.mod` file should have **synced** as well and it should now look something like this:

```
module bookstore

go 1.17

require (
	github.com/gorilla/handlers v1.5.1
	github.com/gorilla/mux v1.8.0
)

require github.com/felixge/httpsnoop v1.0.1 // indirect
```

Let's take a deeper look at our `Start()` function. First, we declared a new Mux router variable which will be responsible for the **routing and handling** of requests across our API. Then we told Mux that we want to include a **middleware** that will execute upon each request that comes to our API with the line:

`router.Use(commonMiddleware)`

More about middleware a bit later. If we continue to analyze our code we can finally see where we create endpoints along with handlers (callback functions) and some primitive validations. For example:

`router.HandleFunc("/book/{book_id:[0-9]+}", updateBook).Methods(http.MethodPut)`

This endpoint will fire up once a user hits our server at the `/book/123` (or any other number) path with a PUT method. It will then **pass the request** to the `updateBook` handler function for further processing. The `book_id` variable has to be a number as we specified a simple validation after the variable name declaration.

Finally, we will run our server on the specific host and port combination and make it log everything to our terminal:

`log.Fatal(http.ListenAndServe("localhost:5000", handlers.LoggingHandler(os.Stdout, router)))`

### 2.2 Middlewares

As we all know - REST APIs mostly use JSON when taking requests and returning responses. That is communicated to our browsers/HTTP clients by making use of  [`Content-Type` headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type). Since our API will only be using JSON-represented data, we can make use of a middleware that will make sure our content type is always set to JSON.

As mentioned earlier, the `Start()` method of `app.go` contains this line:

`router.Use(commonMiddleware)`
 
Let's open up our `middlewares.go` file and create the needed function:

```
package src

import "net/http"

func commonMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("content-type", "application/json; charset=utf-8")
		w.Header().Set("x-content-type-options", "nosniff")
		next.ServeHTTP(w, r)
	})
}
```

Once the user hits any of the endpoints that we registered in our `Start()` function to the Mux router, the middleware will intercept that request and add the two headers that we specified inside of our `commonMiddleware` function. It will then pass the modified request further to the handling function of the requested endpoint OR to another middleawre (in case we need more than one middleware).

### 2.3 Static data

Since we won't be using any data storage services (database, cache, ...) we need to have some sort of **static data**. Also, we will create a data type for custom responses, which I will explain later on.

Open up the `data.go` inside the `src` package and put the following inside of it:

```
package src

type Book struct {
	Id   int    `json:"id"`
	Title string `json:"title"`
	Author string `json:"author"`
	Genre  string `json:"genre"`
}

var booksDB = []Book{
	{Id: 123, Title: "The Hobbit", Author: "J. R. R. Tolkien", Genre: "Fantasy"},
	{Id: 456, Title: "Harry Potter and the Philosopher's Stone", Author: "J. K. Rowling", Genre: "Fantasy"},
	{Id: 789, Title: "The Little Prince", Author: "Antoine de Saint-Exupéry", Genre: "Novella"},
}
```

We just created a **data structure** that will hold the information needed for a single book inside our API. We also created `json` **tags** which will translate the field names to its JSON representation if the data type will be passed as JSON. 
We also created a primitive book storage system (in memory) with some initial books data (`booksDB`).

Add this code below the one from the above:

```
type CustomResponse struct {
	Code        int    `json:"code"`
	Message     string `json:"message"`
	Description string `json:"description,omitempty"`
}

var responseCodes = map[int]string {
	400: "Bad Request",
	401: "Unauthorized",
	403: "Forbidden",
	404: "Not Found",
	409: "Conflict",
	422: "Validation Error",
	429: "Too Many Requests",
	500: "Internal Server Error",
}
```

We just made a new data structure that will unify the errors/responses that our API will return. More on this later on.

### 2.4 Helpers

We will need some helpers to get the most of our API. For example, we will need to check if a book with a given ID exists (adding a new book, modifying an existing book). We will also need to delete a book with a given ID (delete book). We will also create a helper that will return a custom JSON response for a given HTTP status code.

Open up `helpers.go` inside of `src` package and insert the following inside:

```
package src

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

func removeBook(s []Book, i int) []Book {
	if i != len(s)-1 {
		s[i] = s[len(s)-1]
	}
	return s[:len(s)-1]
}

func checkDuplicateBookId(s []Book, id int) bool {
	for _, book := range s {
		if book.Id == id {
			return true
		}
	}
	return false
}

func JSONResponse(w http.ResponseWriter, code int, desc string) {
	w.WriteHeader(code)
	message, ok := responseCodes[code]
	if !ok {
		message = "Undefined"
	}
	r := CustomResponse{
		Code:        code,
		Message:     message,
		Description: desc,
	}
	_ = json.NewEncoder(w).Encode(r)
}
```

The `removeBook` function will go over our `Book` slice and look for index value `i`. If it is not the last element of the slice, it will move it to the end of the slice and return **a new slice without it** (avoid the last element).

`checkDuplicateBookId` function will return a bool value (true or false) depending on if the given `id` **exists** inside of the `Book` slice.

The `JSONResponse` function is responsible for making use of the `CustomResponse` and `responseCodes` we created earlier on. It will return a `CustomResponse` JSON representation with the status code and message that `responseCodes` will provide. This way we will avoid having different messages across our API for the same HTTP status codes (eg. 400: Bad Request and 400: Invalid Data).

### 2.5 Handlers

If you made it so far, congrats :)
Let's jump to the final part - putting the endpoint handlers together. Open up your `handlers.go` and let's type some code inside of it.

```
package src

import (
	"encoding/json"
	"github.com/gorilla/mux"
	"net/http"
	"strconv"
)
```

#### 2.5.1 Get a single book

```
func getBook(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	bookId, _ := strconv.Atoi(vars["book_id"])
	for _, book := range booksDB {
		if book.Id == bookId {
			_ = json.NewEncoder(w).Encode(book)
			return
		}
	}
	JSONResponse(w, http.StatusNotFound, "")
}
```

Not much is going on here. We get the passed variables from our Mux router (in our case it is just one variable that we described in `app.go` for this handler - `book_id`) and we convert it from `string` to `int` value. We iterate over our `booksDB` and look for the **matching book ID**. If it exists, we return it - if not, we return the `404: Not Found` error.

#### 2.5.2 Get all books

```
func getAllBooks(w http.ResponseWriter, r *http.Request) {
	_ = json.NewEncoder(w).Encode(booksDB)
}
```

Simple one eh? Convert the `booksDB` slice to JSON and return it to the user.

#### 2.5.3 Add a new book

```
func addBook(w http.ResponseWriter, r *http.Request) {
	decoder := json.NewDecoder(r.Body)
	var b Book
	err := decoder.Decode(&b)
	if err != nil {
		JSONResponse(w, http.StatusBadRequest, "")
		return
	}
	if checkDuplicateBookId(booksDB, b.Id) {
		JSONResponse(w, http.StatusConflict, "")
		return
	}
	booksDB = append(booksDB, b)
	w.WriteHeader(201)
	_ = json.NewEncoder(w).Encode(b)
}
```

Since this one triggers on POST method, the user must provide the JSON data inside the request body that will match the `Book` structure:

```
{
	"id": 999,
	"title": "SomeTitle",
	"author": "SomeAuthor",
	"genre": "SomeGenre"
}
```

Once we decode and validate the JSON body against our `Book` struct (if it fails we will return the `400: Bad Request` error), we need to check if the book with the same ID already exists. If that is the case, we return the `409: Conflict` error back. Otherwise, we will append our `booksDB` with the user-provided book and return its JSON representation to the user.

#### 2.5.4 Update existing book

```
func updateBook(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	bookId, _ := strconv.Atoi(vars["book_id"])
	decoder := json.NewDecoder(r.Body)
	var b Book
	err := decoder.Decode(&b)
	if err != nil {
		JSONResponse(w, http.StatusBadRequest, "")
		return
	}
	for i, book := range booksDB {
		if book.Id == bookId {
			booksDB[i] = b
			_ = json.NewEncoder(w).Encode(b)
			return
		}
	}
	JSONResponse(w, http.StatusNotFound, "")
}
```

Almost the same as the `addBook` function handler with one main difference. For the book to be updated, it has to exist already (ID must be inside of the `booksDB`). If it exists, we will update the values of the existing book, otherwise, we will return the `404: Not Found` error back.

#### 2.5.5 Delete existing book

```
func deleteBook(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	bookId, _ := strconv.Atoi(vars["book_id"])
	for i, book := range booksDB {
		if book.Id == bookId {
			booksDB = removeBook(booksDB, i)
			_ = json.NewEncoder(w).Encode(book)
			return
		}
	}
	JSONResponse(w, http.StatusNotFound, "")
}
```

After we get the integer value of the `book_id` variable, we iterate over the `booksDB` to find the book that the user wants to delete. If it exists, we make use of our helper `removeBook` function to remove the book from the `Book` struct slice. If it does not exist, we will return the `404: Not Found` error back.

## 3. Running and testing the API

Now that our API is finished, let's give it a run. Execute this in your terminal:

`go run main.go`

Fire up your favorite HTTP client (Insomnia, Postman, ...) and try out some of the endpoints we created.
Feel free to play around with your newly created Go REST API.

The final code can be found [here](https://gitlab.com/vojko.pribudic/bookstore-api).

Like always, thanks for reading :)
