This tutorial aims to help the developers looking to get started with creating Robust REST APIs in Go using Gin framework. By the end of this guide, you'll have a fully functional API that can handle basic CRUD operations for a bookstore
, without the integrating with a database.
Let's jump straight into code!
Setting Up Your Project
First, let's set up the directory structure for our project. Create a new directory for your project and set up the following structure:
your-project/
├── cmd/
│ └── main.go
├── internal/
│ ├── handler/
│ │ └── book_handler.go
│ └── model/
│ └── model.go
Ensure that you have setup Go development environment on your machine. If not, then I highly recommend you to read Set up GO Development Environment.
Implementing the Main Server
In the cmd/
directory, create a file named main.go
, where we set up a simple HTTP server using the Gin web framework. It handles requests related to book resources (like creating, reading, updating, and deleting books) through a series of API endpoints under /v1/books
. The server runs on port 8080
and defines specific routes for each book-related operation.
The program also includes a graceful shutdown feature to safely stop the server when it receives an interrupt signal (like when you press Ctrl+C
in the terminal). When the server starts, it listens for incoming HTTP requests. If an interrupt signal is detected, the program waits up to 5 seconds for any ongoing requests to complete, then shuts down. This ensures users don’t experience abrupt disconnections or lose data mid-request.
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github/dev-toolkit-go/rest-api-gin-framework/internal/handler"
"github.com/gin-gonic/gin"
)
func main() {
router := setupRouter()
srv := &http.Server{Addr: ":8080", Handler: router}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
waitForShutdown(srv)
log.Println("Server exiting")
}
func setupRouter() *gin.Engine {
bookHandler := handler.NewBookHandler()
router := gin.Default()
v1 := router.Group("/api/v1")
{
v1.POST("/books", bookHandler.CreateBook)
v1.GET("/books/:id", bookHandler.GetBook)
v1.PUT("/books/:id", bookHandler.UpdateBook)
v1.DELETE("/books/:id", bookHandler.DeleteBook)
v1.GET("/books", bookHandler.ListBooks)
v1.GET("/books/top-rated", bookHandler.GetTopRatedBooks)
}
return router
}
func waitForShutdown(srv *http.Server) {
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown:", err)
}
}
Creating the Handlers
In the internal/handler/
directory, create a file named book_handler.go
. This file will contain the handlers for our bookstore API. These handlers will process HTTP requests and return mock responses.
- CreateBook: Handles the creation of a new book by binding JSON input and returning a mock created book response.
- GetBook: Retrieves a specific book based on its ID, returning a mock book response.
- UpdateBook: Updates the details of an existing book identified by its ID, using the provided JSON data and returning a mock updated book response.
- DeleteBook: Deletes a book by its ID and returns a no content response.
- ListBooks: Lists all books with a mock response containing an array of sample books.
- GetTopRatedBooks: Retrieves a list of top-rated books, returning a mock response with sample top-rated books.
package handler
import (
"net/http"
"strconv"
"github/dev-toolkit-go/rest-api-gin-framework/internal/model"
"github.com/gin-gonic/gin"
)
type BookHandler struct{}
func NewBookHandler() *BookHandler {
return &BookHandler{}
}
func (h *BookHandler) CreateBook(c *gin.Context) {
var book model.Book
if err := c.ShouldBindJSON(&book); err != nil {
c.JSON(http.StatusBadRequest, ErrorResponse{Message: err.Error()})
return
}
createdBook := model.BookResponse {
ID: 1,
Title: book.Title,
Author: book.Author,
}
c.JSON(http.StatusCreated, createdBook)
}
func (h *BookHandler) GetBook(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, ErrorResponse{Message: "Invalid book ID"})
return
}
book := model.BookResponse {
ID: uint(id),
Title: "Sample Book",
Author: "Author Name",
}
c.JSON(http.StatusOK, book)
}
func (h *BookHandler) UpdateBook(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, ErrorResponse{Message: "Invalid book ID"})
return
}
var book model.Book
if err := c.ShouldBindJSON(&book); err != nil {
c.JSON(http.StatusBadRequest, ErrorResponse{Message: err.Error()})
return
}
updatedBook := model.BookResponse {
ID: uint(id),
Title: book.Title,
Author: book.Author,
}
c.JSON(http.StatusOK, updatedBook)
}
func (h *BookHandler) DeleteBook(c *gin.Context) {
_, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, ErrorResponse{Message: "Invalid book ID"})
return
}
c.JSON(http.StatusNoContent, nil)
}
func (h *BookHandler) ListBooks(c *gin.Context) {
books := []model.BookResponse {
{ID: 1, Title: "Sample Book 1", Author: "Author 1"},
{ID: 2, Title: "Sample Book 2", Author: "Author 2"},
}
c.JSON(http.StatusOK, books)
}
func (h *BookHandler) GetTopRatedBooks(c *gin.Context) {
// Mock response
topRatedBooks := []model.BookResponse {
{ID: 1, Title: "Top Rated Book 1", Author: "Author 1"},
{ID: 2, Title: "Top Rated Book 2", Author: "Author 2"},
}
c.JSON(http.StatusOK, topRatedBooks)
}
type ErrorResponse struct {
Message string `json:"message"`
}
Defining Data Model
Next, define the data model in the internal/model/
directory. Create a file named model.go
:
package model
type Book struct {
Title string `json:"title"`
Author string `json:"author"`
}
type BookResponse struct {
ID uint `json:"id"`
Title string `json:"title"`
Author string `json:"author"`
}
Running Server and Testing Your API
Run the HTTP server, by navigating to /cmd
directory and
go run main.go
You can test your API using tools like Postman or curl. Here are some sample curl
commands to test each endpoint:
Create a book:
curl -X POST http://localhost:8080/v1/books -H "Content-Type: application/json" -d '{"title":"New Book","author":"Author Name"}'
Get a book:
curl http://localhost:8080/v1/books/1
List all books:
curl http://localhost:8080/v1/books
Update a book:
curl -X PUT http://localhost:8080/v1/books/1 -H "Content-Type: application/json" -d '{"title":"Updated Book","author":"Updated Author"}'
Delete a book:
curl -X DELETE http://localhost:8080/v1/books/1
Conclusion
In this tutorial, we walked through the process of building a simple bookstore API with Gin framework in Go. We set up the project structure, created handlers to manage book-related operations, defined data transfer objects, and implemented the main server. This guide serves as a starting point for building more complex and feature-rich APIs using the Gin framework. Happy coding!