Let's create a simple Go project using Gin with a line-by-line explanation for each part.
Project Structure
myapp/
|-- main.go
|-- models/
| |-- book.go
|-- handlers/
| |-- book.go
main.go
This file sets up the Gin router and defines the routes for the API.
package main
import (
"myapp/handlers"
"github.com/gin-gonic/gin"
)
func main() {
// Create a new Gin router with default middleware (logger and recovery)
r := gin.Default()
// Define routes
r.GET("/books", handlers.GetBooks)
r.POST("/books", handlers.AddBook)
r.PUT("/books/:id", handlers.UpdateBook)
r.DELETE("/books/:id", handlers.DeleteBook)
// Start the server on port 8080
r.Run(":8080")
}
Explanation:
package main
: Defines the package name asmain
, indicating the entry point of the application.import
statements: Import necessary packages.r := gin.Default()
: Creates a new Gin router with default middleware (logger and recovery).r.GET
,r.POST
,r.PUT
,r.DELETE
: Define routes and associate them with handler functions.r.Run(":8080")
: Starts the HTTP server on port 8080.
models/book.go
This file defines the Book
struct and in-memory storage for books.
package models
import (
"errors"
)
// Book represents a book model
type Book struct {
ID int `json:"id"`
Title string `json:"title"`
Author string `json:"author"`
}
// In-memory storage for books
var books = []Book{
{ID: 1, Title: "1984", Author: "George Orwell"},
{ID: 2, Title: "Brave New World", Author: "Aldous Huxley"},
}
// GetBooks returns all books
func GetBooks() []Book {
return books
}
// AddBook adds a new book to the storage
func AddBook(book Book) Book {
book.ID = len(books) + 1
books = append(books, book)
return book
}
// UpdateBookByID updates a book by its ID
func UpdateBookByID(id int, updatedBook Book) (Book, error) {
for i, b := range books {
if b.ID == id {
books[i] = updatedBook
books[i].ID = id
return books[i], nil
}
}
return Book{}, errors.New("book not found")
}
// DeleteBookByID deletes a book by its ID
func DeleteBookByID(id int) error {
for i, b := range books {
if b.ID == id {
books = append(books[:i], books[i+1:]...)
return nil
}
}
return errors.New("book not found")
}
Explanation:
package models
: Defines the package name asmodels
.type Book struct
: Defines theBook
struct with JSON tags for binding.var books
: In-memory slice to store books.GetBooks()
,AddBook()
,UpdateBookByID()
,DeleteBookByID()
: Functions to interact with the in-memory storage.
handlers/book.go
This file contains the handler functions for the API endpoints.
package handlers
import (
"myapp/models"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
// GetBooks handles GET requests to /books
func GetBooks(c *gin.Context) {
books := models.GetBooks()
c.JSON(http.StatusOK, gin.H{
"status": "success",
"data": books,
})
}
// AddBook handles POST requests to /books
func AddBook(c *gin.Context) {
var newBook models.Book
if err := c.BindJSON(&newBook); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"status": "error",
"message": err.Error(),
})
return
}
addedBook := models.AddBook(newBook)
c.JSON(http.StatusCreated, gin.H{
"status": "success",
"data": addedBook,
})
}
// UpdateBook handles PUT requests to /books/:id
func UpdateBook(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"status": "error",
"message": "invalid book ID",
})
return
}
var updatedBook models.Book
if err := c.BindJSON(&updatedBook); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"status": "error",
"message": err.Error(),
})
return
}
book, err := models.UpdateBookByID(id, updatedBook)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{
"status": "error",
"message": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"status": "success",
"data": book,
})
}
// DeleteBook handles DELETE requests to /books/:id
func DeleteBook(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"status": "error",
"message": "invalid book ID",
})
return
}
if err := models.DeleteBookByID(id); err != nil {
c.JSON(http.StatusNotFound, gin.H{
"status": "error",
"message": err.Error(),
})
return
}
c.JSON(http.StatusNoContent, gin.H{
"status": "success",
})
}
Explanation:
package handlers
: Defines the package name ashandlers
.import
statements: Import necessary packages.GetBooks(c *gin.Context)
: Handler for the GET/books
endpoint.books := models.GetBooks()
: Retrieve all books from the model.c.JSON(http.StatusOK, gin.H{...})
: Respond with a 200 OK status and the books in JSON format.
AddBook(c *gin.Context)
: Handler for the POST/books
endpoint.var newBook models.Book
: Declare a variable to hold the new book data.c.BindJSON(&newBook)
: Bind the JSON request body tonewBook
.if err := c.BindJSON(&newBook); err != nil { ... }
: Handle JSON binding errors.addedBook := models.AddBook(newBook)
: Add the new book to the storage.c.JSON(http.StatusCreated, gin.H{...})
: Respond with a 201 Created status and the added book in JSON format.
UpdateBook(c *gin.Context)
: Handler for the PUT/books/:id
endpoint.id, err := strconv.Atoi(c.Param("id"))
: Convert theid
parameter to an integer.if err != nil { ... }
: Handle ID conversion errors.var updatedBook models.Book
: Declare a variable to hold the updated book data.if err := c.BindJSON(&updatedBook); err != nil { ... }
: Bind the JSON request body and handle errors.book, err := models.UpdateBookByID(id, updatedBook)
: Update the book by ID and handle errors.c.JSON(http.StatusOK, gin.H{...})
: Respond with a 200 OK status and the updated book in JSON format.
DeleteBook(c *gin.Context)
: Handler for the DELETE/books/:id
endpoint.id, err := strconv.Atoi(c.Param("id"))
: Convert theid
parameter to an integer.if err != nil { ... }
: Handle ID conversion errors.if err := models.DeleteBookByID(id); err != nil { ... }
: Delete the book by ID and handle errors.c.JSON(http.StatusNoContent, gin.H{...})
: Respond with a 204 No Content status.
Key Concepts
*gin.Context
: The context for a request, containing request and response information.gin.H
: A shortcut for creating amap[string]interface{}
to represent JSON responses.c.JSON
: Method to respond with JSON data.c.BindJSON
: Method to bind JSON request data to a Go struct.gin.Default()
: Creates a new Gin router with default middleware (logger and recovery).
This project setup provides a basic structure for building a RESTful API with the Gin framework in Go. Each handler function demonstrates how to handle requests and responses using *gin.Context
and its methods.