DEV Community

loading...
Cover image for Build a REST API with Go - For Beginners

Build a REST API with Go - For Beginners

Karan Pratap Singh
Software Engineer & Solutions Architect
・5 min read

In this article, we will build a simple REST CRUD API with Go.

I've also created a video if you will like to follow along!

Setup

Let's initialize a simple Go project and to keep things simple we won't be connecting to any database in this article.

All the code from this article is available here

$ go mod init github.com/karanpratapsingh/tutorials/go/crud
Enter fullscreen mode Exit fullscreen mode

Let's create our main.go

$ touch main.go
Enter fullscreen mode Exit fullscreen mode

I will also install Mux which will help us with routing.

GitHub logo gorilla / mux

A powerful HTTP router and URL matcher for building Go web servers with 🦍

gorilla/mux

GoDoc CircleCI Sourcegraph

Gorilla Logo

https://www.gorillatoolkit.org/pkg/mux

Package gorilla/mux implements a request router and dispatcher for matching incoming requests to their respective handler.

The name mux stands for "HTTP request multiplexer". Like the standard http.ServeMux, mux.Router matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions. The main features are:

  • It implements the http.Handler interface so it is compatible with the standard http.ServeMux.
  • Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers.
  • URL hosts, paths and query values can have variables with an optional regular expression.
  • Registered URLs can be built, or "reversed", which helps maintaining references to resources.
  • Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions…

Hello world

package main

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

    "github.com/gorilla/mux"
)

func main() {
    router := mux.NewRouter()

    router.HandleFunc("/books", func(w http.ResponseWriter, r *http.Request) {
        json.NewEncoder(w).Encode("Hello World")
    })

    log.Println("API is running!")
    http.ListenAndServe(":4000", router)
}
Enter fullscreen mode Exit fullscreen mode

Now let's run our app!

$ go run main.go
Enter fullscreen mode Exit fullscreen mode

Organize

Before proceeding further, let's organize our code because we don't want to write all the code in main.go. We will create the following project structure

β”œβ”€β”€ cmd
β”‚   └── main.go
β”œβ”€β”€ pkg
β”‚    β”œβ”€β”€ handlers
β”‚    β”‚   β”œβ”€β”€ AddBook.go
β”‚    β”‚   β”œβ”€β”€ DeleteBook.go
β”‚    β”‚   β”œβ”€β”€ GetAllBooks.go
β”‚    β”‚   β”œβ”€β”€ GetBook.go
β”‚    β”‚   └── UpdateBook.go
β”‚    β”œβ”€β”€ mocks
β”‚    β”‚   └── book.go
β”‚    └── models
β”‚        └── book.go
β”œβ”€β”€ go.sum
└── go.mod
Enter fullscreen mode Exit fullscreen mode

Note: This is just a sample structure, feel free to create our own project structure if you like!

Cmd

Let's move our main.go to cmd

package main

import (
    "log"
    "net/http"

    "github.com/gorilla/mux"
    "github.com/karanpratapsingh/tutorials/go/crud/pkg/handlers"
)

func main() {
    router := mux.NewRouter()

    // Here we'll define our api endpoints

    log.Println("API is running!")
    http.ListenAndServe(":4000", router)
}

Enter fullscreen mode Exit fullscreen mode

Models

Let's define our Book model at pkg/models/book.go

package models

type Book struct {
    Id     int    `json:"id"`
    Title  string `json:"title"`
    Author string `json:"author"`
    Desc   string `json:"desc"`
}
Enter fullscreen mode Exit fullscreen mode

Mocks

Let's create our mocks pkg/mocks/book.go

package mocks

import "github.com/karanpratapsingh/tutorials/go/crud/pkg/models"

var Books = []models.Book{
    {
        Id:     1,
        Title:  "Golang",
        Author: "Gopher",
        Desc:   "A book for Go",
    },
}
Enter fullscreen mode Exit fullscreen mode

Handlers

Now, let's start defining our handlers!

Get all Books

Let's add our endpoint to cmd/main.go

router.HandleFunc("/books", handlers.GetAllBooks).Methods(http.MethodGet)
Enter fullscreen mode Exit fullscreen mode

Create a new handler pkg/handlers/GetBooks.go

In this handler, we'll simply return all our mock books.

package handlers

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

    "github.com/karanpratapsingh/tutorials/go/crud/pkg/mocks"
)

func GetAllBooks(w http.ResponseWriter, r *http.Request) {
    w.Header().Add("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    json.NewEncoder(w).Encode(mocks.Books)
}

Enter fullscreen mode Exit fullscreen mode

Let's start our server and try it out in Postman

$ go run cmd/main.go
Enter fullscreen mode Exit fullscreen mode

This should print all the books we have, currently, it should print our mock books

get-books

Add a new Book

Let's add our endpoint to cmd/main.go

router.HandleFunc("/books/{id}", handlers.GetBook).Methods(http.MethodGet)
Enter fullscreen mode Exit fullscreen mode

Create a new handler pkg/handlers/AddBook.go

In this handler we'll do the following:

  • Read to request body
  • Append to the Book mocks
  • Send a 201 created response
package handlers

import (
    "encoding/json"
    "io/ioutil"
    "log"
    "math/rand"
    "net/http"

    "github.com/karanpratapsingh/tutorials/go/crud/pkg/mocks"
    "github.com/karanpratapsingh/tutorials/go/crud/pkg/models"
)

func AddBook(w http.ResponseWriter, r *http.Request) {
    // Read to request body
    defer r.Body.Close()
    body, err := ioutil.ReadAll(r.Body)

    if err != nil {
        log.Fatalln(err)
    }

    var book models.Book
    json.Unmarshal(body, &book)

    // Append to the Book mocks
    book.Id = rand.Intn(100)
    mocks.Books = append(mocks.Books, book)

    // Send a 201 created response
    w.WriteHeader(http.StatusCreated)
    w.Header().Add("Content-Type", "application/json")
    json.NewEncoder(w).Encode("Created")
}
Enter fullscreen mode Exit fullscreen mode

Let's start our server and try it out in Postman

$ go run cmd/main.go
Enter fullscreen mode Exit fullscreen mode

We should be able to add a new book by providing json body

add-book

Get a Book by Id

Let's add our endpoint to cmd/main.go

router.HandleFunc("/books", handlers.AddBook).Methods(http.MethodPost)
Enter fullscreen mode Exit fullscreen mode

Create a new handler pkg/handlers/GetBook.go

In this handler we'll do the following:

  • Read dynamic id parameter
  • Iterate over all the mock books
  • If ids are equal send book as a response
package handlers

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

    "github.com/gorilla/mux"
    "github.com/karanpratapsingh/tutorials/go/crud/pkg/mocks"
)

func GetBook(w http.ResponseWriter, r *http.Request) {
    // Read dynamic id parameter
    vars := mux.Vars(r)
    id, _ := strconv.Atoi(vars["id"])

    // Iterate over all the mock books
    for _, book := range mocks.Books {
        if book.Id == id {
            // If ids are equal send book as a response
            w.WriteHeader(http.StatusOK)
            w.Header().Add("Content-Type", "application/json")
            json.NewEncoder(w).Encode(book)
            break
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Let's start our server and try it out in Postman

$ go run cmd/main.go
Enter fullscreen mode Exit fullscreen mode

get-book

Update a Book by Id

Let's add our endpoint to cmd/main.go

router.HandleFunc("/books/{id}", handlers.UpdateBook).Methods(http.MethodPut)
Enter fullscreen mode Exit fullscreen mode

Create a new handler pkg/handlers/UpdateBook.go

In this handler we'll do the following:

  • Read dynamic id parameter
  • Read request body
  • Iterate over all the mock Books
  • Update and send a response when book Id matches dynamic Id
package handlers

import (
    "encoding/json"
    "io/ioutil"
    "log"
    "net/http"
    "strconv"

    "github.com/gorilla/mux"
    "github.com/karanpratapsingh/tutorials/go/crud/pkg/mocks"
    "github.com/karanpratapsingh/tutorials/go/crud/pkg/models"
)

func UpdateBook(w http.ResponseWriter, r *http.Request) {
    // Read dynamic id parameter
    vars := mux.Vars(r)
    id, _ := strconv.Atoi(vars["id"])

    // Read request body
    defer r.Body.Close()
    body, err := ioutil.ReadAll(r.Body)

    if err != nil {
        log.Fatalln(err)
    }

    var updatedBook models.Book
    json.Unmarshal(body, &updatedBook)

    // Iterate over all the mock Books
    for index, book := range mocks.Books {
        if book.Id == id {
            // Update and send a response when book Id matches dynamic Id
            book.Title = updatedBook.Title
            book.Author = updatedBook.Author
            book.Desc = updatedBook.Desc

            mocks.Books[index] = book

            w.WriteHeader(http.StatusOK)
            w.Header().Add("Content-Type", "application/json")
            json.NewEncoder(w).Encode("Updated")
            break
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Let's start our server and try it out in Postman

$ go run cmd/main.go
Enter fullscreen mode Exit fullscreen mode

update-book

Delete a Book by Id

Let's add our endpoint to cmd/main.go

router.HandleFunc("/books/{id}", handlers.DeleteBook).Methods(http.MethodDelete)
Enter fullscreen mode Exit fullscreen mode

Create a new handler pkg/handlers/DeleteBook.go

In this handler we'll do the following:

  • Read the dynamic id parameter
  • Iterate over all the mock Books
  • Delete book and send a response if the book Id matches dynamic Id
package handlers

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

    "github.com/gorilla/mux"
    "github.com/karanpratapsingh/tutorials/go/crud/pkg/mocks"
)

func DeleteBook(w http.ResponseWriter, r *http.Request) {
    // Read the dynamic id parameter
    vars := mux.Vars(r)
    id, _ := strconv.Atoi(vars["id"])

    // Iterate over all the mock Books
    for index, book := range mocks.Books {
        if book.Id == id {
            // Delete book and send a response if the book Id matches dynamic Id
            mocks.Books = append(mocks.Books[:index], mocks.Books[index+1:]...)

            w.WriteHeader(http.StatusOK)
            json.NewEncoder(w).Encode("Deleted")
            break
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Let's start our server and try it out in Postman

$ go run cmd/main.go
Enter fullscreen mode Exit fullscreen mode

delete-book

Next steps

So, we built a basic CRUD API with Go! Our next step could be to connect our API with a real DB like PostgreSQL, which we will look into in the next part!

As mentioned above, the code is available here

I hope this was helpful, as always feel free to reach out if you face any issues.

Have a great day!

Discussion (1)

Collapse
shaijut profile image
Shaiju T

I can see Go used by many Organization in cloud. But readability is not developer friendly like C# or Java. Have you tried Asp.net Core Web API to build API's its clean and fast ?