dev_tools
Back to guides

Go + Gin + GORM REST API

Build a production-ready REST API in Go using the Gin web framework, GORM ORM, PostgreSQL, and Air for hot-reload development.

Go
Golang
Gin
GORM
PostgreSQL
1

Initialize Go Module

Create a project directory and initialize a Go module. The module path is used as the import prefix throughout the project.

mkdir my-go-api && cd my-go-api && go mod init github.com/yourusername/my-go-api
2

Install Dependencies

Install Gin (HTTP framework), GORM with the PostgreSQL driver, and godotenv for .env support.

go get github.com/gin-gonic/gin gorm.io/gorm gorm.io/driver/postgres github.com/joho/godotenv
3

Install Air for Hot Reload

Air watches your Go files and recompiles automatically on change — the equivalent of ts-node-dev in the Go world.

go install github.com/air-verse/air@latest
💡Run `air init` in your project root to generate a default `.air.toml` config file.
4

Create .env File

Store your database connection string and server port in .env. godotenv will load these at startup.

.env
PORT=8080
DATABASE_URL=postgres://postgres:password@localhost:5432/myapp?sslmode=disable
5

Define the Database Model

Create models/user.go. Embedding gorm.Model gives you ID, CreatedAt, UpdatedAt, and DeletedAt for free. GORM uses struct tags to map fields to columns.

models/user.go
package models

import "gorm.io/gorm"

type User struct {
	gorm.Model
	Name  string `json:"name"  gorm:"not null"`
	Email string `json:"email" gorm:"uniqueIndex;not null"`
}

type CreateUserInput struct {
	Name  string `json:"name"  binding:"required"`
	Email string `json:"email" binding:"required,email"`
}

type UpdateUserInput struct {
	Name  string `json:"name"`
	Email string `json:"email" binding:"omitempty,email"`
}
6

Create Request Handlers

Create handlers/user.go. Handlers receive a *gin.Context which gives access to request data, path params, and response helpers. GORM queries are fully type-safe.

handlers/user.go
package handlers

import (
	"net/http"

	"github.com/gin-gonic/gin"
	"gorm.io/gorm"

	"github.com/yourusername/my-go-api/models"
)

type UserHandler struct {
	DB *gorm.DB
}

func (h *UserHandler) GetUsers(c *gin.Context) {
	var users []models.User
	h.DB.Find(&users)
	c.JSON(http.StatusOK, users)
}

func (h *UserHandler) GetUser(c *gin.Context) {
	var user models.User
	if err := h.DB.First(&user, c.Param("id")).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}
	c.JSON(http.StatusOK, user)
}

func (h *UserHandler) CreateUser(c *gin.Context) {
	var input models.CreateUserInput
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	user := models.User{Name: input.Name, Email: input.Email}
	h.DB.Create(&user)
	c.JSON(http.StatusCreated, user)
}

func (h *UserHandler) UpdateUser(c *gin.Context) {
	var user models.User
	if err := h.DB.First(&user, c.Param("id")).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}
	var input models.UpdateUserInput
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	h.DB.Model(&user).Updates(input)
	c.JSON(http.StatusOK, user)
}

func (h *UserHandler) DeleteUser(c *gin.Context) {
	if err := h.DB.Delete(&models.User{}, c.Param("id")).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}
	c.Status(http.StatusNoContent)
}
7

Create the Main Entry Point

Create main.go. Connect to PostgreSQL, run AutoMigrate to create tables, register routes, and start the server.

main.go
package main

import (
	"log"
	"os"

	"github.com/gin-gonic/gin"
	"github.com/joho/godotenv"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"

	"github.com/yourusername/my-go-api/handlers"
	"github.com/yourusername/my-go-api/models"
)

func main() {
	if err := godotenv.Load(); err != nil {
		log.Println("No .env file found, reading from environment")
	}

	db, err := gorm.Open(postgres.Open(os.Getenv("DATABASE_URL")), &gorm.Config{})
	if err != nil {
		log.Fatalf("Failed to connect to database: %v", err)
	}

	// AutoMigrate creates/updates tables to match your models
	db.AutoMigrate(&models.User{})

	r := gin.Default()

	userHandler := &handlers.UserHandler{DB: db}

	r.GET("/health", func(c *gin.Context) {
		c.JSON(200, gin.H{"status": "ok"})
	})

	users := r.Group("/users")
	{
		users.GET("",      userHandler.GetUsers)
		users.GET("/:id",  userHandler.GetUser)
		users.POST("",     userHandler.CreateUser)
		users.PATCH("/:id", userHandler.UpdateUser)
		users.DELETE("/:id", userHandler.DeleteUser)
	}

	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}
	log.Printf("Server running on port %s", port)
	r.Run(":" + port)
}
8

Run the API

Use Air for development (auto-reloads on save) or run directly with go run.

air
💡The API is available at http://localhost:8080. Test it: `curl -X POST http://localhost:8080/users -H 'Content-Type: application/json' -d '{"name":"Alice","email":"alice@example.com"}'`

Found this guide helpful? Check out more tools and guides.

Browse All Guides