DEV Community

Cover image for Build a REST API with Go - For Beginners
Karan Pratap Singh
Karan Pratap Singh

Posted on • Edited on

Build a REST API with Go - For Beginners

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

Package gorilla/mux is a powerful HTTP router and URL matcher for building Go web servers with 🦍

gorilla/mux

testing codecov godoc sourcegraph

Gorilla Logo

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 like…

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", handlers.AddBook).Methods(http.MethodPost)


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.Header().Add("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated)
    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/{id}", handlers.GetBook).Methods(http.MethodGet)


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.Header().Add("Content-Type", "application/json")
            w.WriteHeader(http.StatusOK)

            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.Header().Add("Content-Type", "application/json")
            w.WriteHeader(http.StatusOK)

            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.Header().Add("Content-Type", "application/json")
            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!

Top comments (12)

Collapse
 
konjoinfinity profile image
Wesley Scholl

Thanks for putting this together, simple and straightforward. I was able to turn it into a simple full crud react native app!

github.com/konjoinfinity/rngo

Looking forward to part 2, thanks again.

Collapse
 
stravid87 profile image
John Stravid

I always appreciate concise, helpful tutorials that just work. Thank you!

Collapse
 
karimelngr profile image
Karim ElNaggar

Thanks for the tutorial, I think there is a small bug here

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

should be

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

otherwise the content type header does not get written
Another thing, I think you switched two lines

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

between the two sections Add a new Book and Get a book by id

Thanks again for the great tutorial

Collapse
 
karanpratapsingh profile image
Karan Pratap Singh

Nice catch! Thanks, I have updated

Collapse
 
sethjoe12 profile image
karl joseph

hai sir good day can you help me with this thing .it was a code structure sir.

i edited all your codes and it comes with this error

"..\pkg\test\Additem.go:24:19: cannot use item (type models.Item) as type []byte in argument to json.Unmarshal"

Collapse
 
cerchie profile image
Lucia Cerchie

This was a great article! will there be a part 2 soon?

Collapse
 
karanpratapsingh profile image
Karan Pratap Singh

Hi Lucia, I'm glad this was helpful. yes there will be a part 2 where we will connect our API with gorm!

Collapse
 
karanpratapsingh profile image
Karan Pratap Singh
Thread Thread
 
cerchie profile image
Lucia Cerchie

this is awesome Karan! thanks!

Thread Thread
 
karanpratapsingh profile image
Karan Pratap Singh

Thanks!

Collapse
 
ramonivandy profile image
Ramon Ivandy Setiawan

Thank you for the tutorial!

But in Get All Books, there is a typo that confuse me a minute πŸ˜… It should GetAllBooks.go instead GetBooks.go right?

Image description

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 ?