Apps Artificial Intelligence CSS DevOps Go JavaScript Laravel Linux MongoDB MySQL PHP Python Rust Svelte Vue

Build a CRUD API with Go, Gin, MySQL, and GORM

2 min read .
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 myapp

2. 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/godotenv

Run 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.go

The 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/books

Get one book:

curl http://localhost:8080/books/1

Update 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/1

Conclusion

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.

Related Posts

chevron-up