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
 
rafael_mori profile image
Rafael Mori

Nice!