CRUD REST API with Golang and PostgreSQL Tutorial

by Didin J. on Jun 05, 2025 CRUD REST API with Golang and PostgreSQL Tutorial

Learn how to build a CRUD REST API using Golang, Fiber, GORM, and PostgreSQL with step-by-step instructions and real code examples


In this tutorial, you’ll learn how to create a fully functional RESTful API using Golang, PostgreSQL, Fiber (a fast Go web framework), and GORM (a powerful ORM library for Go). You’ll perform all basic CRUD operations: Create, Read, Update, and Delete.

CRUD REST API with Golang and PostgreSQL Tutorial - diagram

Prerequisites

Before we start, make sure you have the following:


Step 1: Initialize Go Project

First, create a new folder for your project and initialize it with go mod.

mkdir go-crud-api
cd go-crud-api
go mod init github.com/yourusername/go-crud-api

This sets up a new Go module. Replace yourusername with your actual GitHub username or org name if applicable (eg, "didinj")


Step 2: Install Required Packages

We will use:

  • Fiber for the web framework
  • GORM for ORM/database abstraction
  • PostgreSQL driver for GORM
  • godotenv for loading environment variables

Install them using:

go get github.com/gofiber/fiber/v2
go get gorm.io/gorm
go get gorm.io/driver/postgres
go get github.com/joho/godotenv


Step 3: Configure PostgreSQL and Environment Variables

In another terminal tab, go to the PostgreSQL console.

psql postgres -U djamware

Create a PostgreSQL database manually:

CREATE DATABASE go_crud_db;

Then, create a .env file at the root of your project to securely store database credentials:

DB_HOST=localhost
DB_PORT=5432
DB_USER=djamware
DB_PASSWORD=dj@mw@r3
DB_NAME=go_crud_db

Don't commit .env to version control.


Step 4: Database Connection Setup

Create a new folder called database and inside it a file called database.go.

mkdir database
touch database/database.go

Add the following code:

package database

import (
    "fmt"
    "log"
    "os"

    "gorm.io/gorm"
    "gorm.io/driver/postgres"
    "github.com/joho/godotenv"
)

var DB *gorm.DB

func Connect() {
    err := godotenv.Load()
    if err != nil {
        log.Fatal("Error loading .env file")
    }

    dsn := fmt.Sprintf(
        "host=%s user=%s password=%s dbname=%s port=%s sslmode=disable",
        os.Getenv("DB_HOST"),
        os.Getenv("DB_USER"),
        os.Getenv("DB_PASSWORD"),
        os.Getenv("DB_NAME"),
        os.Getenv("DB_PORT"),
    )

    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    if err != nil {
        log.Fatal("Failed to connect to the database:", err)
    }

    DB = db
    fmt.Println("Database connection established")
}

This file connects to your PostgreSQL database using GORM.


Step 5: Create a Model

Create a models folder and add a file named book.go.

mkdir models
touch models/book.go

Add the following code:

package models

type Book struct {
    ID     uint   `json:"id" gorm:"primaryKey"`
    Title  string `json:"title"`
    Author string `json:"author"`
}

This defines a simple Book model with an ID, Title, and Author. GORM uses this model to create the table automatically.


Step 6: API Routes and Handlers

Create a routes folder and add a file called book.go.

mkdir routes
touch routes/book.go

This file will contain all the CRUD logic:

package routes

import (
    "github.com/didinj/go-crud-api/database"
    "github.com/didinj/go-crud-api/models"

    "github.com/gofiber/fiber/v2"
)

// GET /api/books
func GetBooks(c *fiber.Ctx) error {
    var books []models.Book
    database.DB.Find(&books)
    return c.JSON(books)
}

// GET /api/books/:id
func GetBook(c *fiber.Ctx) error {
    id := c.Params("id")
    var book models.Book
    result := database.DB.First(&book, id)
    if result.Error != nil {
        return c.Status(404).JSON(fiber.Map{"error": "Book not found"})
    }
    return c.JSON(book)
}

// POST /api/books
func CreateBook(c *fiber.Ctx) error {
    book := new(models.Book)
    if err := c.BodyParser(book); err != nil {
        return c.Status(400).JSON(fiber.Map{"error": "Cannot parse JSON"})
    }
    database.DB.Create(&book)
    return c.JSON(book)
}

// PUT /api/books/:id
func UpdateBook(c *fiber.Ctx) error {
    id := c.Params("id")
    var book models.Book
    if err := database.DB.First(&book, id).Error; err != nil {
        return c.Status(404).JSON(fiber.Map{"error": "Book not found"})
    }

    if err := c.BodyParser(&book); err != nil {
        return c.Status(400).JSON(fiber.Map{"error": "Cannot parse JSON"})
    }

    database.DB.Save(&book)
    return c.JSON(book)
}

// DELETE /api/books/:id
func DeleteBook(c *fiber.Ctx) error {
    id := c.Params("id")
    result := database.DB.Delete(&models.Book{}, id)
    if result.RowsAffected == 0 {
        return c.Status(404).JSON(fiber.Map{"error": "Book not found"})
    }
    return c.JSON(fiber.Map{"message": "Book deleted successfully"})
}

func SetupRoutes(app *fiber.App) {
    api := app.Group("/api")
    books := api.Group("/books")

    books.Get("/", GetBooks)
    books.Get("/:id", GetBook)
    books.Post("/", CreateBook)
    books.Put("/:id", UpdateBook)
    books.Delete("/:id", DeleteBook)
}

    books.Get("/", GetBooks)
    books.Get("/:id", GetBook)
    books.Post("/", CreateBook)
    books.Put("/:id", UpdateBook)
    books.Delete("/:id", DeleteBook)
}

These handlers manage the lifecycle of book records through RESTful routes.


Step 7: Main Application Entry Point

Now create the main.go the file at the root:

package main

import (
    "github.com/didinj/go-crud-api/database"
    "github.com/didinj/go-crud-api/models"
    "github.com/didinj/go-crud-api/routes"

    "github.com/gofiber/fiber/v2"
)

func main() {
    app := fiber.New()

    // Connect to DB
    database.Connect()

    // Auto-migrate Book model to create the table
    database.DB.AutoMigrate(&models.Book{})

    // Setup API routes
    routes.SetupRoutes(app)

    // Start the server
    app.Listen(":3000")
}


Step 8: Run the Application

Now you can run your app with:

go run main.go

If successful, you should see:

Database connection established

 ┌───────────────────────────────────────────────────┐ 
 │                   Fiber v2.52.6                   │ 
 │               http://127.0.0.1:3000               │ 
 │       (bound on host 0.0.0.0 and port 3000)       │ 
 │                                                   │ 
 │ Handlers ............. 7  Processes ........... 1 │ 
 │ Prefork ....... Disabled  PID .............. 4390 │ 
 └───────────────────────────────────────────────────┘ 

Visit: http://localhost:3000/api/books


Step 9: Test the API

Use curl or Postman to test:

Create a Book

curl -X POST http://localhost:3000/api/books \
-H "Content-Type: application/json" \
-d '{"title":"The Go Programming Language","author":"Alan Donovan"}'

Response:

{"id":1,"title":"The Go Programming Language","author":"Alan Donovan"}

📚 Get All Books

curl http://localhost:3000/api/books

Response:

[{"id":1,"title":"The Go Programming Language","author":"Alan Donovan"}]

🔍 Get Book by ID

curl http://localhost:3000/api/books/1

Response:

{"id":1,"title":"The Go Programming Language","author":"Alan Donovan"}

✏️ Update a Book

curl -X PUT http://localhost:3000/api/books/1 \
-H "Content-Type: application/json" \
-d '{"title":"Go in Action","author":"William Kennedy"}'

Response:

{"id":1,"title":"Go in Action","author":"William Kennedy"}

🗑 Delete a Book

curl -X DELETE http://localhost:3000/api/books/1

Response:

{"message":"Book deleted successfully"}


Conclusion

You’ve successfully built a Golang CRUD REST API backed by PostgreSQL, with Fiber for routing and GORM for ORM/database interactions.

This setup provides a great foundation for building microservices, modern backends, or full-stack apps with React, Vue, or Angular as a frontend.

You can find the working source code on our GitHub.

That's just the basics. If you need more deep learning about Go/Golang, you can take the following cheap course:

Thanks!