Build a CRUD API with Go, Gin, MySQL, and GORM
This tutorial builds a small CRUD API with Go, the Gin web framework, MySQL, and GORM. It also uses godotenv for local development configuration.
1. Create the Project
mkdir myapp
cd myapp
go mod init myapp2. Install Dependencies
go get github.com/gin-gonic/gin
go get gorm.io/gorm
go get gorm.io/driver/mysql
go get github.com/joho/godotenvRun go mod tidy after adding your imports so the module file reflects the dependencies actually used by the application.
3. Create the MySQL Database
CREATE DATABASE myapp CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;4. Configure Local Environment Variables
Create .env in the project root:
DB_USER=root
DB_PASSWORD=yourpassword
DB_NAME=myapp
DB_HOST=127.0.0.1
DB_PORT=3306
Add .env to .gitignore so real credentials are not committed to the repository. In production, prefer environment variables or a secret-management system rather than shipping an .env file with the application.
5. Write the Application
Create app.go:
package main
import (
"fmt"
"log"
"net/http"
"os"
"github.com/gin-gonic/gin"
"github.com/joho/godotenv"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
var DB *gorm.DB
type Book struct {
gorm.Model
Title string `json:"title"`
Author string `json:"author"`
Price float64 `json:"price"`
}
type BookInput struct {
Title string `json:"title" binding:"required"`
Author string `json:"author" binding:"required"`
Price float64 `json:"price" binding:"gte=0"`
}
func connectDB() error {
_ = godotenv.Load()
dsn := fmt.Sprintf(
"%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
os.Getenv("DB_USER"),
os.Getenv("DB_PASSWORD"),
os.Getenv("DB_HOST"),
os.Getenv("DB_PORT"),
os.Getenv("DB_NAME"),
)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
return err
}
if err := db.AutoMigrate(&Book{}); err != nil {
return err
}
DB = db
return nil
}
func main() {
if err := connectDB(); err != nil {
log.Fatal(err)
}
r := gin.Default()
r.POST("/books", CreateBook)
r.GET("/books", GetBooks)
r.GET("/books/:id", GetBook)
r.PUT("/books/:id", UpdateBook)
r.DELETE("/books/:id", DeleteBook)
if err := r.Run(":8080"); err != nil {
log.Fatal(err)
}
}
func CreateBook(c *gin.Context) {
var input BookInput
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
book := Book{Title: input.Title, Author: input.Author, Price: input.Price}
if err := DB.Create(&book).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create book"})
return
}
c.JSON(http.StatusCreated, book)
}
func GetBooks(c *gin.Context) {
var books []Book
if err := DB.Find(&books).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load books"})
return
}
c.JSON(http.StatusOK, books)
}
func GetBook(c *gin.Context) {
var book Book
if err := DB.First(&book, c.Param("id")).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "book not found"})
return
}
c.JSON(http.StatusOK, book)
}
func UpdateBook(c *gin.Context) {
var book Book
if err := DB.First(&book, c.Param("id")).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "book not found"})
return
}
var input BookInput
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := DB.Model(&book).Updates(Book{
Title: input.Title, Author: input.Author, Price: input.Price,
}).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update book"})
return
}
c.JSON(http.StatusOK, book)
}
func DeleteBook(c *gin.Context) {
var book Book
if err := DB.First(&book, c.Param("id")).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "book not found"})
return
}
if err := DB.Delete(&book).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete book"})
return
}
c.Status(http.StatusNoContent)
}6. Run the API
go run app.goThe server listens on http://localhost:8080.
7. Test the Endpoints
Create a book:
curl -X POST http://localhost:8080/books \
-H "Content-Type: application/json" \
-d '{"title":"Go Programming","author":"John Doe","price":29.99}'List books:
curl http://localhost:8080/booksGet one book:
curl http://localhost:8080/books/1Update a book:
curl -X PUT http://localhost:8080/books/1 \
-H "Content-Type: application/json" \
-d '{"title":"Advanced Go","author":"Jane Doe","price":39.99}'Delete a book:
curl -X DELETE http://localhost:8080/books/1Conclusion
Gin, GORM, and MySQL make it straightforward to build a conventional CRUD API in Go. The example keeps database setup, request validation, and error handling explicit so it can serve as a foundation for authentication, pagination, service layers, migrations, and more structured production architecture.