From Setup to CRUD Operations with Gin & PostgreSQL
Golang is a fantastic programming language! If you're just starting out, the official documentation is a great resource. But when it comes to practical application, things can get tricky.
Many developers have asked me how to build a RESTful API using Go, so I decided to create a minimal working REST API with step-by-step guidance. Let's dive in! 🔥
🛠 Step-by-Step Guide to Building a REST API in Go
We'll be using:
✅ Gin (Fast & lightweight web framework)
✅ GORM (ORM for PostgreSQL)
✅ Docker (To run PostgreSQL in a container)
Step 1: Setting Up the Project & Installing Dependencies
First, create a new Go project:
mkdir go-rest-api && cd booking-app
go mod init booking-app
Next, install Gin:
go get -u github.com/gin-gonic/gin
Create the initial route
create file routes/booking.go
package routes
import (
"net/http"
"github.com/gin-gonic/gin"
)
func GetBookings(c *gin.Context) {
var bookings []string = []string{"abc"}
c.JSON(http.StatusOK, bookings)
}
func SetupRoutes(router *gin.Engine) {
router.GET("v1/bookings", GetBookings)
}
To use above route in main.go, create a file main.go
package main
import (
"booking-app/routes"
"fmt"
"github.com/gin-gonic/gin"
)
func main() {
// Setup Gin Router
router := gin.Default()
routes.SetupRoutes(router)
// Start the server
port := ":8080"
fmt.Println("Server is running on port", port)
router.Run(port)
}
run
go run main.go
visit http://localhost:8080/v1/bookings
you should get a response with ["abc"]
Step 2: Setting Up PostgreSQL Using Docker:
Instead of installing PostgreSQL manually, let's use Docker.
To make sure that the starting docker container lives with the application itself, let create a docker-compose
docker-compose.yml
version: '3.8'
services:
postgres:
image: postgres:latest
container_name: postgres_container
restart: always
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: yourpassword
POSTGRES_DB: booking_db
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
run
docker-compose up -d
make sure that the docker is running in background
adding DB connectivity to the app
create the file config/database.go
package config
import (
"fmt"
"log"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
var DB *gorm.DB
func ConnnetDatabse() {
dsn := "host=localhost user=postgres password=yourpassword dbname=booking_db port=5432 sslmode=disable"
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
// log.Fatal will log the message to terminal and exit the program
log.Fatal("Failed to connect to the database:", err)
}
DB = db
fmt.Println("Connected to PostgreSQL!")
}
add following line at the top of the function main
in main.go to initialise the database connection.
config.ConnnetDatabse()
The step 2 github link
Step 3: Adding a Data Model & First Route (Create a Resource)
Let's define a model for our API.
First, install GORM and the PostgreSQL driver:
go get -u gorm.io/gorm
go get -u gorm.io/driver/postgres
create the modal
package models
import "gorm.io/gorm"
type User struct {
gorm.Model
Name string `json:"name" binding:"required" gorm:"not null"`
Email string `json:"email" binding:"required,email" gorm:"not null"`
Date string `json:"date"`
}
gorm.Modal
adds few default fields to the struct as ID
, CreatedAt
, UpdatedAt
, DeletedAt
and managed by it self.
bindind
make sure that the fileds are validate when the json is binded to the modal.
lets create a controller to for bookings
controller/bookings.go
package controllers
import (
"booking-app/config"
"booking-app/models"
"net/http"
"github.com/gin-gonic/gin"
)
func GetBookings(c *gin.Context) {
var bookings []models.Booking
config.DB.Find(&bookings)
c.JSON(http.StatusOK, bookings)
}
func CreateBooking(c *gin.Context) {
var booking models.Booking
if err := c.ShouldBindJSON(&booking); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
config.DB.Create(&booking)
c.JSON(http.StatusCreated, booking)
}
We have created a handler for creating bookings and moved the logic to get bookings from routes file as well.
now update the routes/booking.go
as well.
package routes
import (
"booking-app/controllers"
"github.com/gin-gonic/gin"
)
func SetupRoutes(router *gin.Engine) {
router.GET("v1/bookings", controllers.GetBookings)
router.POST("v1/bookings", controllers.CreateBooking)
}
now let's test the endpoint
curl -X POST http://localhost:8080/v1/bookings -H "Content-Type: application/json" -d '{"name":"John Doe", "email":"john@example.com"}'
code for step 3
Step 4: Get a booking by ID
Lets update the controller to get a booking
func GetBookingsById(c *gin.Context) {
id := c.Param("id")
var booking models.Booking
err := config.DB.First(&booking, id).Error
if err != nil {
c.JSON(http.StatusNotFound, nil)
return
}
c.JSON(http.StatusOK, booking)
}
lets add the route to routes/booking.go
router.GET("v1/bookings/:id", controllers.GetBookingsById)
By re-running the application you should be able to fetch booking by id
code for step 04
Step 5: Update & Delete Operations
now you guessed right. know the drill. update the controller, ad it to route.
controllers/booking.go
type UpdateBookingRequest struct {
Name string `json:"name"`
Email string `json:"email"`
Date string `json:"date"`
}
func UpdateBooking(c *gin.Context) {
var id = c.Param("id")
var booking models.Booking
var updateRequest UpdateBookingRequest
err := config.DB.First(&booking, id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Booking not found"})
}
if err := c.ShouldBindJSON(&updateRequest); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := config.DB.Model(&booking).Updates(updateRequest).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error"})
return
}
c.JSON(http.StatusOK, booking)
}
func DeleteBooking(c *gin.Context) {
var id = c.Param("id")
var booking models.Booking
if err := config.DB.First(&booking, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Booking not found"})
return
}
if err := config.DB.Delete(&booking).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error"})
return
}
c.JSON(http.StatusOK, booking)
}
wait, there is a new struct for updates, Yes. that's because we have already added validations for bookings modal as both name
and email
are required, but for an update we might only need to update one of it not both. other than that this enables adding custom validations only for update request if needed.
routes/booking.go
router.PUT("v1/bookings/:id", controllers.UpdateBooking)
router.DELETE("v1/bookings/:id", controllers.DeleteBooking)
here is the code after adding delete and update operations.
🎉 Conclusion
Congratulations! You’ve built a fully functional RESTful API in Go with: ✅ Gin for routing
✅ PostgreSQL (via Docker) for storage
✅ GORM for database interactions
✅ CRUD operations
Hope you enjoyed this guide! Let me know your thoughts in the comments. 🚀
Top comments (1)
Nice!