This article is designed to guide developers who are eager to start building robust and efficient REST APIs in Go using the powerful Gin framework. Whether you are a beginner or looking to sharpen your skills, this step-by-step guide will walk you through the process of creating a fully functional API.
By the end of this guide, you'll have built a bookstore API capable of handling essential CRUD (Create, Read, Update, Delete) operations. While this tutorial focuses on the core functionalities and skips database integration, it lays a solid foundation for further enhancements in your projects.
Before diving into the code, ensure that your Go development environment is properly set up. If you're not yet familiar with the setup process, I highly recommend checking out our comprehensive guide: Set Up GO Development Environment.
Let's jump right in and start building!
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
Implementing the Main Server
In the cmd/
directory, create a file named main.go
, where we set up a simple HTTP server using 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!