How to Build Robust REST APIs in Go using Gin Framework

User Icon By Azam Akram,   Calendar Icon July 8, 2024
Building an API with Gin Framework in Go

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.

  1. CreateBook: Handles the creation of a new book by binding JSON input and returning a mock created book response.
  2. GetBook: Retrieves a specific book based on its ID, returning a mock book response.
  3. UpdateBook: Updates the details of an existing book identified by its ID, using the provided JSON data and returning a mock updated book response.
  4. DeleteBook: Deletes a book by its ID and returns a no content response.
  5. ListBooks: Lists all books with a mock response containing an array of sample books.
  6. 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
Building an API with Gin Framework in Go
API Server Logs
REST API client curl logs
REST API client curl logs

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!