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
Let's create our main.go
$ touch main.go
I will also install Mux which will help us with routing.
gorilla / mux
Package gorilla/mux is a powerful HTTP router and URL matcher for building Go web servers with π¦
gorilla/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 standardhttp.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)
}
Now let's run our app!
$ go run main.go
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
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)
}
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"`
}
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",
},
}
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)
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)
}
Let's start our server and try it out in Postman
$ go run cmd/main.go
This should print all the books we have, currently, it should print our mock books
Add a new Book
Let's add our endpoint to cmd/main.go
router.HandleFunc("/books", handlers.AddBook).Methods(http.MethodPost)
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")
}
Let's start our server and try it out in Postman
$ go run cmd/main.go
We should be able to add a new book by providing json
body
Get a Book by Id
Let's add our endpoint to cmd/main.go
router.HandleFunc("/books/{id}", handlers.GetBook).Methods(http.MethodGet)
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
}
}
}
Let's start our server and try it out in Postman
$ go run cmd/main.go
Update a Book by Id
Let's add our endpoint to cmd/main.go
router.HandleFunc("/books/{id}", handlers.UpdateBook).Methods(http.MethodPut)
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
}
}
}
Let's start our server and try it out in Postman
$ go run cmd/main.go
Delete a Book by Id
Let's add our endpoint to cmd/main.go
router.HandleFunc("/books/{id}", handlers.DeleteBook).Methods(http.MethodDelete)
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
}
}
}
Let's start our server and try it out in Postman
$ go run cmd/main.go
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)
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.
I always appreciate concise, helpful tutorials that just work. Thank you!
Thanks for the tutorial, I think there is a small bug here
should be
otherwise the content type header does not get written
Another thing, I think you switched two lines
between the two sections
Add a new Book
andGet a book by id
Thanks again for the great tutorial
Nice catch! Thanks, I have updated
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"
This was a great article! will there be a part 2 soon?
Hi Lucia, I'm glad this was helpful. yes there will be a part 2 where we will connect our API with gorm!
Part 2 is here! dev.to/karanpratapsingh/connecting...
this is awesome Karan! thanks!
Thanks!
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?
I can see
Go
used by many Organization in cloud. But readability is not developer friendly likeC# or Java
. Have you tried Asp.net Core Web API to build API's its clean and fast ?