DEV Community

chamupathi mendis
chamupathi mendis

Posted on

πŸš€ Building a RESTful API in Go: A Practical Guide

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 
Enter fullscreen mode Exit fullscreen mode

Next, install Gin:

go get -u github.com/gin-gonic/gin 
Enter fullscreen mode Exit fullscreen mode

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) } 
Enter fullscreen mode Exit fullscreen mode

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) } 
Enter fullscreen mode Exit fullscreen mode

run

go run main.go 
Enter fullscreen mode Exit fullscreen mode

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: 
Enter fullscreen mode Exit fullscreen mode

run

docker-compose up -d 
Enter fullscreen mode Exit fullscreen mode

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!") } 
Enter fullscreen mode Exit fullscreen mode

add following line at the top of the function main in main.go to initialise the database connection.

 config.ConnnetDatabse() 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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"` } 
Enter fullscreen mode Exit fullscreen mode

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) } 
Enter fullscreen mode Exit fullscreen mode

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) } 
Enter fullscreen mode Exit fullscreen mode

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"}' 
Enter fullscreen mode Exit fullscreen mode

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) } 
Enter fullscreen mode Exit fullscreen mode

lets add the route to routes/booking.go

router.GET("v1/bookings/:id", controllers.GetBookingsById) 
Enter fullscreen mode Exit fullscreen mode

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) } 
Enter fullscreen mode Exit fullscreen mode

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) 
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
rafa_mori profile image
Rafa Mori

Nice!