JB logo

Command Palette

Search for a command to run...

yOUTUBE
Blog
Next

Complete Go Programming Guide: From Basics to Building Production-Ready REST APIs

Master Go (Golang) programming with this comprehensive guide covering fundamentals, essential concepts, and building REST APIs with Gin & GORM. Perfect for JavaScript developers transitioning to Go - includes side-by-side comparisons, real-world examples, and a complete CRUD API project with authentication, relationships, and best practices.

Complete Go Guide: From Basics to API Development

Table of Contents

  1. Part 1: Go Fundamentals
  2. Part 2: Essential Go Concepts Cheatsheet
  3. Part 3: Building REST API with Gin & GORM
  4. Part 4: GORM CRUD Cheatsheet

Part 1: Go Fundamentals

1. Installation & Setup

Installing Go

What you're installing:

  • Go compiler (like Node.js V8 engine)
  • Go runtime (executes your Go code)
  • Go tools (go build, go run - similar to npm commands)
  • Go standard library (like Node.js built-in modules)

Steps:

  1. Visit https://golang.org
  2. Download installer for your OS
  3. Run installer
  4. Verify installation:
go version

Setting Up VSCode

Required Extensions:

  1. Go Extension (by Go Team at Google)

    • Syntax highlighting
    • IntelliSense (auto-complete)
    • Error detection
    • Debugging support
  2. Code Runner (Optional)

    • Run code with Ctrl+Alt+N
    • Quick testing without terminal

Installing Go Tools:

  1. Open VSCode Command Palette (Ctrl+Shift+P)
  2. Type "Go: Install/Update Tools"
  3. Select all tools and click OK

These tools include:

  • gopls - Go Language Server (like TypeScript Language Server)
  • dlv - Debugger
  • staticcheck - Linter (like ESLint)

2. Your First Go Program

Understanding the Structure

package main
 
import "fmt"
 
func main() {
    fmt.Println("Hello world")
}

Breaking it down:

package main

  • Every Go file must start with a package declaration
  • main package = entry point of your program
  • Like your main index.js or app.js in JavaScript

import "fmt"

  • Import packages you need (like import express from 'express')
  • fmt = "Format" - for printing and formatting text
  • Similar to console in JavaScript

func main()

  • Entry point function - Go automatically executes this
  • No need to call it manually (unlike JavaScript)

JavaScript equivalent:

// JavaScript needs you to call main()
function main() {
  console.log("Hello world");
}
main();
 
// Go automatically runs main()

fmt.Println()

  • Print to console (like console.log())
  • Println = Print Line (adds newline automatically)
  • No semicolons required in Go!

Running Your Code

Option 1: Direct Run (Development)

go run main.go

Option 2: Build then Execute (Production)

# Build executable
go build main.go
 
# Run it
./main        # Mac/Linux
main.exe      # Windows

Option 3: VSCode Code Runner

  • Right-click → "Run Code"
  • Or press Ctrl+Alt+N

3. Go Modules (Project Setup)

What are Modules?

JavaScript comparison:

JavaScript Project          Go Project
├── package.json      →    ├── go.mod
├── node_modules/     →    ├── (packages cached globally)
├── index.js               ├── main.go
└── utils/                 └── utils/

Creating a Module

Initialize your project:

go mod init github.com/yourusername/project-name

This creates go.mod file (like package.json):

module github.com/yourusername/project-name
 
go 1.21
 
require (
    github.com/gin-gonic/gin v1.9.1
)

Packages vs Modules

Simple explanation:

  • Package = Single room (one folder with .go files)
  • Module = Entire building (whole project with go.mod)

Example structure:

my-go-project/              ← MODULE
├── go.mod                 ← Module definition
├── main.go                ← main package
├── utils/                 ← utils package
│   └── helper.go
└── models/                ← models package
    └── user.go

4. Basic Data Types

Variables

package main
 
import "fmt"
 
func main() {
    // Explicit type declaration
    var name string = "John"
    var age int = 25
    var price float64 = 99.99
    var isActive bool = true
 
    // Type inference (Go guesses the type)
    var city = "New York"
 
    // Short declaration (most common)
    country := "USA"
 
    fmt.Println(name, age, price, isActive, city, country)
}

JavaScript equivalent:

// JavaScript (loosely typed)
let name = "John";
let age = 25;
let price = 99.99;
let isActive = true;

Constants

const birthYear = 1993
const pi = 3.14159
 
// Cannot be changed later
// birthYear = 1995  // ERROR!

Basic Types

Go TypeJavaScript EquivalentExample
stringstring"Hello"
intnumber42
float64number3.14
boolbooleantrue
[]stringstring[]["a", "b"]

5. Arrays and Slices

Arrays (Fixed Size)

// Fixed size - cannot change
var numbers [5]int = [5]int{1, 2, 3, 4, 5}

Slices (Dynamic Size - More Common)

// Dynamic size - can grow/shrink
names := []string{"JB", "Jane", "Gerald"}
 
// Add items
names = append(names, "Alice")
 
// Access items
fmt.Println(names[0])  // "JB"
 
// Length
fmt.Println(len(names))  // 4

JavaScript comparison:

// JavaScript arrays are always dynamic
const names = ["JB", "Jane", "Gerald"];
names.push("Alice");
console.log(names[0]);
console.log(names.length);

6. Functions

Basic Function

func greet(name string) string {
    return "Hello, " + name
}
 
// Usage
message := greet("John")
fmt.Println(message)

Multiple Return Values

func getUser() (string, int) {
    return "John", 25
}
 
// Usage
name, age := getUser()
fmt.Println(name, age)  // John 25

JavaScript equivalent:

// JavaScript needs array or object
function getUser() {
  return ["John", 25];
}
const [name, age] = getUser();

Function Example from Your Code

func calcAge() {
    const birthYear = 1993
    var currentYear = 2025
    var result = currentYear - birthYear
    var names = []string{"JB", "Jane", "Gerald"}
 
    printItems(names)
    fmt.Println(result, names)
}
 
func printItems(items []string) {
    for index, item := range items {
        fmt.Printf("%d. %s\n", index+1, item)
    }
}

7. Loops

For Loop (The Only Loop in Go!)

// Standard for loop
for i := 0; i < 5; i++ {
    fmt.Println(i)
}
 
// While-style loop
i := 0
for i < 5 {
    fmt.Println(i)
    i++
}
 
// Infinite loop
for {
    // runs forever
    break  // use break to exit
}
 
// Range loop (like forEach)
names := []string{"JB", "Jane", "Gerald"}
for index, name := range names {
    fmt.Printf("%d. %s\n", index+1, name)
}
 
// Ignore index with underscore
for _, name := range names {
    fmt.Println(name)
}

JavaScript comparison:

// For loop
for (let i = 0; i < 5; i++) {
  console.log(i);
}
 
// While loop
let i = 0;
while (i < 5) {
  console.log(i);
  i++;
}
 
// forEach
names.forEach((name, index) => {
  console.log(`${index + 1}. ${name}`);
});

8. Conditionals

If/Else

age := 25
 
if age >= 18 {
    fmt.Println("Adult")
} else if age >= 13 {
    fmt.Println("Teenager")
} else {
    fmt.Println("Child")
}
 
// Short statement before condition
if age := getAge(); age >= 18 {
    fmt.Println("Adult")
}

Switch

day := "Monday"
 
switch day {
case "Monday":
    fmt.Println("Start of week")
case "Friday":
    fmt.Println("Almost weekend!")
case "Saturday", "Sunday":
    fmt.Println("Weekend!")
default:
    fmt.Println("Midweek")
}

9. Structs (Like Objects/Classes)

// Define a struct (like a class or type)
type Person struct {
    Name string
    Age  int
    City string
}
 
// Create instance
john := Person{
    Name: "John",
    Age:  25,
    City: "New York",
}
 
// Access fields
fmt.Println(john.Name)  // "John"
 
// Modify fields
john.Age = 26

JavaScript equivalent:

// JavaScript object
const john = {
  name: "John",
  age: 25,
  city: "New York",
};
 
console.log(john.name);
john.age = 26;

10. Pointers (Important Concept!)

Pointers hold memory addresses, not values.

// Regular variable
age := 25
 
// Pointer to age
agePointer := &age
 
// Get value from pointer
fmt.Println(*agePointer)  // 25
 
// Modify via pointer
*agePointer = 26
fmt.Println(age)  // 26

Why use pointers?

  • Modify original values in functions
  • Memory efficiency for large structs
  • Required by many Go libraries
func incrementAge(age *int) {
    *age = *age + 1
}
 
age := 25
incrementAge(&age)
fmt.Println(age)  // 26

Summary

You now understand:

  • ✅ Go installation and setup
  • ✅ Basic syntax and structure
  • ✅ Variables, constants, and types
  • ✅ Arrays and slices
  • ✅ Functions and multiple returns
  • ✅ Loops (for, range)
  • ✅ Conditionals (if, switch)
  • ✅ Structs (objects)
  • ✅ Pointers (memory references)

Next: Essential Go concepts for API development!


Part 2: Essential Go Concepts Cheatsheet

1. String Formatting (fmt package)

Basic Printing

import "fmt"
 
// Print without newline
fmt.Print("Hello")
fmt.Print("World")  // HelloWorld
 
// Print with newline
fmt.Println("Hello")
fmt.Println("World")
// Hello
// World
 
// Formatted print (like template literals)
name := "John"
age := 25
fmt.Printf("Name: %s, Age: %d\n", name, age)
// Name: John, Age: 25

Format Specifiers

SpecifierUseExample
%sStringfmt.Printf("%s", "text")
%dIntegerfmt.Printf("%d", 42)
%fFloatfmt.Printf("%.2f", 3.14159)
%tBooleanfmt.Printf("%t", true)
%vAny valuefmt.Printf("%v", anything)
%+vStruct with field namesfmt.Printf("%+v", person)
%TType of valuefmt.Printf("%T", variable)

JavaScript equivalent:

// JavaScript template literals
const name = "John";
const age = 25;
console.log(`Name: ${name}, Age: ${age}`);

2. String Manipulation

import "strings"
 
text := "Hello, World!"
 
// Convert to lowercase/uppercase
lower := strings.ToLower(text)      // "hello, world!"
upper := strings.ToUpper(text)      // "HELLO, WORLD!"
 
// Check if contains
contains := strings.Contains(text, "World")  // true
 
// Split string
parts := strings.Split("a,b,c", ",")  // ["a", "b", "c"]
 
// Join strings
joined := strings.Join([]string{"a", "b"}, "-")  // "a-b"
 
// Replace
replaced := strings.Replace(text, "World", "Go", -1)  // "Hello, Go!"
 
// Trim whitespace
trimmed := strings.TrimSpace("  hello  ")  // "hello"

JavaScript equivalent:

const text = "Hello, World!";
const lower = text.toLowerCase();
const upper = text.toUpperCase();
const contains = text.includes("World");
const parts = "a,b,c".split(",");
const joined = ["a", "b"].join("-");
const replaced = text.replace("World", "Go");
const trimmed = "  hello  ".trim();

3. Working with Maps (Objects/Dictionaries)

// Create a map
ages := make(map[string]int)
ages["John"] = 25
ages["Jane"] = 30
 
// Or create with initial values
scores := map[string]int{
    "John": 95,
    "Jane": 87,
}
 
// Access value
johnScore := scores["John"]  // 95
 
// Check if key exists
score, exists := scores["Alice"]
if exists {
    fmt.Println("Alice scored:", score)
} else {
    fmt.Println("Alice not found")
}
 
// Delete key
delete(scores, "John")
 
// Iterate over map
for name, score := range scores {
    fmt.Printf("%s: %d\n", name, score)
}

JavaScript equivalent:

// JavaScript object
const scores = {
  John: 95,
  Jane: 87,
};
 
const johnScore = scores["John"];
 
// Check if exists
if ("Alice" in scores) {
  console.log("Alice scored:", scores.Alice);
}
 
delete scores.John;
 
// Iterate
for (const [name, score] of Object.entries(scores)) {
  console.log(`${name}: ${score}`);
}

4. Error Handling

Go doesn't have try/catch. Instead, functions return errors.

import (
    "errors"
    "fmt"
)
 
// Function that returns error
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("cannot divide by zero")
    }
    return a / b, nil
}
 
// Usage - always check errors!
result, err := divide(10, 0)
if err != nil {
    fmt.Println("Error:", err)
    return
}
fmt.Println("Result:", result)

JavaScript equivalent:

// JavaScript uses try/catch
function divide(a, b) {
  if (b === 0) {
    throw new Error("cannot divide by zero");
  }
  return a / b;
}
 
try {
  const result = divide(10, 0);
  console.log("Result:", result);
} catch (err) {
  console.log("Error:", err.message);
}

5. JSON Handling

Struct to JSON (Marshal)

import (
    "encoding/json"
    "fmt"
)
 
type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
    City string `json:"city"`
}
 
func main() {
    person := Person{
        Name: "John",
        Age:  25,
        City: "New York",
    }
 
    // Convert to JSON
    jsonData, err := json.Marshal(person)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
 
    fmt.Println(string(jsonData))
    // {"name":"John","age":25,"city":"New York"}
}

JSON to Struct (Unmarshal)

func main() {
    jsonStr := `{"name":"John","age":25,"city":"New York"}`
 
    var person Person
    err := json.Unmarshal([]byte(jsonStr), &person)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
 
    fmt.Printf("%+v\n", person)
    // {Name:John Age:25 City:New York}
}

JavaScript equivalent:

// JavaScript
const person = { name: "John", age: 25, city: "New York" };
 
// To JSON
const jsonStr = JSON.stringify(person);
 
// From JSON
const parsed = JSON.parse(jsonStr);

6. HTTP Requests (Fetching External APIs)

GET Request

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
)
 
type Post struct {
    UserID int    `json:"userId"`
    ID     int    `json:"id"`
    Title  string `json:"title"`
    Body   string `json:"body"`
}
 
func fetchPost() {
    url := "https://jsonplaceholder.typicode.com/posts/1"
 
    // Make GET request
    resp, err := http.Get(url)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer resp.Body.Close()
 
    // Check status code
    if resp.StatusCode != 200 {
        fmt.Println("Error: Status", resp.StatusCode)
        return
    }
 
    // Read response body
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("Error reading body:", err)
        return
    }
 
    // Parse JSON
    var post Post
    err = json.Unmarshal(body, &post)
    if err != nil {
        fmt.Println("Error parsing JSON:", err)
        return
    }
 
    fmt.Printf("%+v\n", post)
}

POST Request

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
)
 
func createPost() {
    url := "https://jsonplaceholder.typicode.com/posts"
 
    // Create request data
    newPost := Post{
        UserID: 1,
        Title:  "My Post",
        Body:   "This is the content",
    }
 
    // Convert to JSON
    jsonData, err := json.Marshal(newPost)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
 
    // Make POST request
    resp, err := http.Post(
        url,
        "application/json",
        bytes.NewBuffer(jsonData),
    )
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer resp.Body.Close()
 
    fmt.Println("Status:", resp.Status)
}

JavaScript equivalent:

// JavaScript Fetch API
async function fetchPost() {
  const response = await fetch("https://jsonplaceholder.typicode.com/posts/1");
  const post = await response.json();
  console.log(post);
}
 
async function createPost() {
  const response = await fetch("https://jsonplaceholder.typicode.com/posts", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      userId: 1,
      title: "My Post",
      body: "This is the content",
    }),
  });
  const data = await response.json();
  console.log(data);
}

7. Working with Files

Reading Files

import (
    "fmt"
    "os"
)
 
func readFile() {
    // Read entire file
    data, err := os.ReadFile("config.txt")
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
 
    fmt.Println(string(data))
}

Writing Files

func writeFile() {
    content := []byte("Hello, World!")
 
    err := os.WriteFile("output.txt", content, 0644)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
 
    fmt.Println("File written successfully")
}

8. Environment Variables

import (
    "fmt"
    "os"
)
 
func main() {
    // Get environment variable
    dbURL := os.Getenv("DATABASE_URL")
 
    if dbURL == "" {
        fmt.Println("DATABASE_URL not set")
        return
    }
 
    fmt.Println("Database URL:", dbURL)
 
    // Set environment variable (for current process)
    os.Setenv("APP_ENV", "development")
}

9. Goroutines (Concurrency - Like Async/Await)

import (
    "fmt"
    "time"
)
 
func fetchUser() {
    time.Sleep(2 * time.Second)
    fmt.Println("User fetched")
}
 
func fetchPosts() {
    time.Sleep(1 * time.Second)
    fmt.Println("Posts fetched")
}
 
func main() {
    // Run concurrently (like Promise.all)
    go fetchUser()
    go fetchPosts()
 
    // Wait for goroutines to finish
    time.Sleep(3 * time.Second)
    fmt.Println("Done")
}

JavaScript equivalent:

async function fetchUser() {
  await new Promise((resolve) => setTimeout(resolve, 2000));
  console.log("User fetched");
}
 
async function fetchPosts() {
  await new Promise((resolve) => setTimeout(resolve, 1000));
  console.log("Posts fetched");
}
 
// Run concurrently
Promise.all([fetchUser(), fetchPosts()]).then(() => {
  console.log("Done");
});

10. Interfaces (Like TypeScript Interfaces)

// Define interface
type Animal interface {
    Speak() string
}
 
// Implement interface (implicit)
type Dog struct {
    Name string
}
 
func (d Dog) Speak() string {
    return "Woof!"
}
 
type Cat struct {
    Name string
}
 
func (c Cat) Speak() string {
    return "Meow!"
}
 
// Use interface
func makeAnimalSpeak(a Animal) {
    fmt.Println(a.Speak())
}
 
func main() {
    dog := Dog{Name: "Buddy"}
    cat := Cat{Name: "Whiskers"}
 
    makeAnimalSpeak(dog)  // Woof!
    makeAnimalSpeak(cat)  // Meow!
}

TypeScript equivalent:

interface Animal {
  speak(): string;
}
 
class Dog implements Animal {
  name: string;
 
  speak(): string {
    return "Woof!";
  }
}
 
function makeAnimalSpeak(animal: Animal) {
  console.log(animal.speak());
}

Part 3: Building REST API with Gin & GORM

Project Setup

1. Initialize Project

# Create project directory
mkdir go-crud-api
cd go-crud-api
 
# Initialize Go module
go mod init github.com/yourusername/go-crud-api

2. Install Dependencies

# Install Gin (Web framework)
go get -u github.com/gin-gonic/gin
 
# Install GORM (ORM)
go get -u gorm.io/gorm
 
# Install Database Driver (PostgreSQL)
go get -u gorm.io/driver/postgres
 
# Install for Environment Variables
go get github.com/joho/godotenv
 
# Install Hot Reload (Development)
go get github.com/githubnemo/CompileDaemon
go install github.com/githubnemo/CompileDaemon

Project Structure

go-crud-api/
├── controllers/
│   └── category_controller.go
├── models/
│   └── category.go
├── dtos/
│   └── category_dto.go
├── initializers/
│   ├── database.go
│   └── loadEnvVariables.go
├── migrate/
│   └── migrate.go
├── .env
├── go.mod
├── go.sum
└── main.go

Step 1: Environment Variables

Create .env file

DATABASE_URL="host=your-host user=your-user password=your-password dbname=your-db port=5432 sslmode=require"
PORT=8080

Create initializers/loadEnvVariables.go

package initializers
 
import (
    "log"
    "github.com/joho/godotenv"
)
 
func LoadEnvVariables() {
    err := godotenv.Load()
    if err != nil {
        log.Fatal("Error loading .env file")
    }
}

Step 2: Database Connection

Create initializers/database.go

package initializers
 
import (
    "fmt"
    "log"
    "os"
 
    "gorm.io/driver/postgres"
    "gorm.io/gorm"
)
 
var DB *gorm.DB
 
func ConnectToDB() {
    var err error
    dsn := os.Getenv("DATABASE_URL")
 
    DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
    if err != nil {
        log.Fatal("Failed to connect to database:", err)
    }
 
    fmt.Println("✅ Successfully connected to database!")
}

Step 3: Create Model

Create models/category.go

package models
 
import "gorm.io/gorm"
 
type Category struct {
    gorm.Model
    Title       string `gorm:"uniqueIndex;not null"`
    Slug        string `gorm:"uniqueIndex;not null"`
    Description string
}

What is gorm.Model? It automatically adds these fields:

  • ID (primary key)
  • CreatedAt
  • UpdatedAt
  • DeletedAt (for soft deletes)

Step 4: Create Migration

Create migrate/migrate.go

package main
 
import (
    "github.com/yourusername/go-crud-api/initializers"
    "github.com/yourusername/go-crud-api/models"
)
 
func init() {
    initializers.LoadEnvVariables()
    initializers.ConnectToDB()
}
 
func main() {
    initializers.DB.AutoMigrate(&models.Category{})
}

Run Migration

go run migrate/migrate.go

This creates the categories table in your database!


Step 5: Create DTOs (Data Transfer Objects)

Create dtos/category_dto.go

package dtos
 
// Request DTOs
type CreateCategoryRequest struct {
    Title       string `json:"title" binding:"required,min=3,max=100"`
    Description string `json:"description" binding:"max=500"`
    Slug        string `json:"slug" binding:"required,min=3,max=100"`
}
 
type UpdateCategoryRequest struct {
    Title       string `json:"title" binding:"omitempty,min=3,max=100"`
    Description string `json:"description" binding:"omitempty,max=500"`
    Slug        string `json:"slug" binding:"omitempty,min=3,max=100"`
}
 
// Response DTOs
type SuccessResponse struct {
    Success bool        `json:"success"`
    Data    interface{} `json:"data,omitempty"`
    Message string      `json:"message,omitempty"`
}
 
type ErrorResponse struct {
    Success bool   `json:"success"`
    Error   string `json:"error"`
}

Step 6: Create Controllers

Create controllers/category_controller.go

package controllers
 
import (
    "errors"
    "strings"
 
    "github.com/yourusername/go-crud-api/dtos"
    "github.com/yourusername/go-crud-api/initializers"
    "github.com/yourusername/go-crud-api/models"
    "github.com/gin-gonic/gin"
    "gorm.io/gorm"
)
 
// CREATE
func CreateCategory(c *gin.Context) {
    var req dtos.CreateCategoryRequest
 
    // Validate request
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(200, dtos.SuccessResponse{
        Success: true,
        Data:    categories,
    })
}
 
// READ ONE
func GetCategory(c *gin.Context) {
    id := c.Param("id")
    var category models.Category
 
    result := initializers.DB.First(&category, id)
 
    if result.Error != nil {
        if errors.Is(result.Error, gorm.ErrRecordNotFound) {
            c.JSON(404, dtos.ErrorResponse{
                Success: false,
                Error:   "Category not found",
            })
            return
        }
 
        c.JSON(500, dtos.ErrorResponse{
            Success: false,
            Error:   "Database error",
        })
        return
    }
 
    c.JSON(200, dtos.SuccessResponse{
        Success: true,
        Data:    category,
    })
}
 
// UPDATE
func UpdateCategory(c *gin.Context) {
    id := c.Param("id")
    var req dtos.UpdateCategoryRequest
 
    // Validate request
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(400, dtos.ErrorResponse{
            Success: false,
            Error:   "Invalid input: " + err.Error(),
        })
        return
    }
 
    // Find existing category
    var category models.Category
    result := initializers.DB.First(&category, id)
 
    if result.Error != nil {
        if errors.Is(result.Error, gorm.ErrRecordNotFound) {
            c.JSON(404, dtos.ErrorResponse{
                Success: false,
                Error:   "Category not found",
            })
            return
        }
 
        c.JSON(500, dtos.ErrorResponse{
            Success: false,
            Error:   "Database error",
        })
        return
    }
 
    // Update fields
    updates := models.Category{
        Title:       req.Title,
        Slug:        req.Slug,
        Description: req.Description,
    }
 
    if err := initializers.DB.Model(&category).Updates(updates).Error; err != nil {
        if strings.Contains(err.Error(), "duplicate") {
            c.JSON(409, dtos.ErrorResponse{
                Success: false,
                Error:   "Category with this slug already exists",
            })
            return
        }
 
        c.JSON(500, dtos.ErrorResponse{
            Success: false,
            Error:   "Failed to update category",
        })
        return
    }
 
    // Fetch updated data
    initializers.DB.First(&category, id)
 
    c.JSON(200, dtos.SuccessResponse{
        Success: true,
        Data:    category,
    })
}
 
// DELETE
func DeleteCategory(c *gin.Context) {
    id := c.Param("id")
 
    // Check if exists
    var category models.Category
    result := initializers.DB.First(&category, id)
 
    if result.Error != nil {
        if errors.Is(result.Error, gorm.ErrRecordNotFound) {
            c.JSON(404, dtos.ErrorResponse{
                Success: false,
                Error:   "Category not found",
            })
            return
        }
 
        c.JSON(500, dtos.ErrorResponse{
            Success: false,
            Error:   "Database error",
        })
        return
    }
 
    // Delete
    if err := initializers.DB.Delete(&category).Error; err != nil {
        c.JSON(500, dtos.ErrorResponse{
            Success: false,
            Error:   "Failed to delete category",
        })
        return
    }
 
    c.JSON(200, dtos.SuccessResponse{
        Success: true,
        Message: "Category deleted successfully",
    })
}

Step 7: Create Routes (main.go)

Create main.go

package main
 
import (
    "github.com/yourusername/go-crud-api/controllers"
    "github.com/yourusername/go-crud-api/initializers"
    "github.com/gin-gonic/gin"
)
 
func init() {
    initializers.LoadEnvVariables()
    initializers.ConnectToDB()
}
 
func main() {
    // Initialize router
    router := gin.Default()
 
    // API v1 group (versioning)
    v1 := router.Group("/api/v1")
    {
        // Category routes
        categories := v1.Group("/categories")
        {
            categories.POST("", controllers.CreateCategory)
            categories.GET("", controllers.ListCategories)
            categories.GET("/:id", controllers.GetCategory)
            categories.PUT("/:id", controllers.UpdateCategory)
            categories.DELETE("/:id", controllers.DeleteCategory)
        }
    }
 
    // Health check
    router.GET("/health", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "status":  "ok",
            "message": "Server is running",
        })
    })
 
    // Start server
    router.Run() // Default :8080
}

Step 8: Run Your API

Development Mode (with hot reload)

CompileDaemon -command="./go-crud-api"

Normal Mode

go run main.go

Build and Run

go build
./go-crud-api

Testing Your API

Using cURL

Create Category:

curl -X POST http://localhost:8080/api/v1/categories \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Electronics",
    "slug": "electronics",
    "description": "Electronic products and gadgets"
  }'

Get All Categories:

curl http://localhost:8080/api/v1/categories

Get Single Category:

curl http://localhost:8080/api/v1/categories/1

Update Category:

curl -X PUT http://localhost:8080/api/v1/categories/1 \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Consumer Electronics",
    "description": "Updated description"
  }'

Delete Category:

curl -X DELETE http://localhost:8080/api/v1/categories/1

Part 4: GORM CRUD Cheatsheet

1. Basic CRUD Operations

Create

// Single record
user := models.User{Name: "John", Age: 25}
result := db.Create(&user)
 
// Check error
if result.Error != nil {
    // Handle error
}
 
// Get ID of created record
fmt.Println(user.ID)
 
// Create multiple records
users := []models.User{
    {Name: "John", Age: 25},
    {Name: "Jane", Age: 30},
}
db.Create(&users)

Read

// Get single record by primary key
var user models.User
db.First(&user, 1) // WHERE id = 1
 
// Get single record with condition
db.First(&user, "name = ?", "John")
 
// Get all records
var users []models.User
db.Find(&users)
 
// Get first record (ordered by primary key)
db.First(&user)
 
// Get last record
db.Last(&user)

Update

// Update single field
db.Model(&user).Update("name", "John Doe")
 
// Update multiple fields (struct)
db.Model(&user).Updates(models.User{
    Name: "John",
    Age:  26,
})
 
// Update multiple fields (map)
db.Model(&user).Updates(map[string]interface{}{
    "name": "John",
    "age":  26,
})
 
// Update all records
db.Model(&models.User{}).Where("age < ?", 18).Update("status", "minor")

Delete

// Delete single record
db.Delete(&user, 1)
 
// Delete with condition
db.Where("age < ?", 18).Delete(&models.User{})
 
// Soft delete (if using gorm.Model)
db.Delete(&user) // Sets DeletedAt
 
// Permanent delete
db.Unscoped().Delete(&user)

2. Query Conditions

Where Clauses

// Simple where
db.Where("name = ?", "John").Find(&users)
 
// Multiple conditions
db.Where("name = ? AND age >= ?", "John", 18).Find(&users)
 
// OR condition
db.Where("name = ?", "John").Or("name = ?", "Jane").Find(&users)
 
// NOT condition
db.Not("name = ?", "John").Find(&users)
 
// IN clause
db.Where("name IN ?", []string{"John", "Jane", "Bob"}).Find(&users)
 
// LIKE
db.Where("name LIKE ?", "%John%").Find(&users)
 
// Between
db.Where("age BETWEEN ? AND ?", 18, 30).Find(&users)

Struct & Map Conditions

// Struct conditions (only non-zero fields)
db.Where(&models.User{Name: "John", Age: 0}).Find(&users)
// WHERE name = 'John' (Age ignored because it's 0)
 
// Map conditions (all fields)
db.Where(map[string]interface{}{
    "name": "John",
    "age":  0,
}).Find(&users)
// WHERE name = 'John' AND age = 0

3. Selecting Specific Columns

// Select specific columns
var users []models.User
db.Select("name", "age").Find(&users)
 
// Select with custom query
db.Select("name, age, DATE(created_at) as date").Find(&users)
 
// Omit columns
db.Omit("password").Find(&users)
 
// Select into custom struct
type Result struct {
    Name string
    Age  int
}
var results []Result
db.Model(&models.User{}).Select("name", "age").Find(&results)

4. Ordering & Limiting

// Order by
db.Order("age desc").Find(&users)
db.Order("age desc, name").Find(&users)
 
// Limit
db.Limit(10).Find(&users)
 
// Offset
db.Offset(5).Limit(10).Find(&users)
 
// Pagination
page := 2
pageSize := 10
db.Offset((page - 1) * pageSize).Limit(pageSize).Find(&users)

5. Aggregation

// Count
var count int64
db.Model(&models.User{}).Where("age > ?", 18).Count(&count)
 
// Sum
var total int
db.Model(&models.Order{}).Select("sum(amount)").Scan(&total)
 
// Average
var avgAge float64
db.Model(&models.User{}).Select("avg(age)").Scan(&avgAge)
 
// Min/Max
var minAge, maxAge int
db.Model(&models.User{}).Select("min(age)").Scan(&minAge)
db.Model(&models.User{}).Select("max(age)").Scan(&maxAge)

6. Relationships

One-to-Many

// Models
type User struct {
    gorm.Model
    Name  string
    Posts []Post // Has many posts
}
 
type Post struct {
    gorm.Model
    Title  string
    UserID uint
    User   User // Belongs to user
}
 
// Create with association
user := User{
    Name: "John",
    Posts: []Post{
        {Title: "Post 1"},
        {Title: "Post 2"},
    },
}
db.Create(&user)
 
// Preload associations
var users []User
db.Preload("Posts").Find(&users)
 
// Find user with posts
var user User
db.Preload("Posts").First(&user, 1)

Many-to-Many

// Models
type User struct {
    gorm.Model
    Name  string
    Roles []Role `gorm:"many2many:user_roles;"`
}
 
type Role struct {
    gorm.Model
    Name  string
    Users []User `gorm:"many2many:user_roles;"`
}
 
// Create with association
user := User{
    Name: "John",
    Roles: []Role{
        {Name: "Admin"},
        {Name: "User"},
    },
}
db.Create(&user)
 
// Preload many-to-many
var users []User
db.Preload("Roles").Find(&users)
 
// Append association
var user User
var role Role
db.First(&user, 1)
db.First(&role, 1)
db.Model(&user).Association("Roles").Append(&role)
 
// Remove association
db.Model(&user).Association("Roles").Delete(&role)
 
// Clear all associations
db.Model(&user).Association("Roles").Clear()

One-to-One

type User struct {
    gorm.Model
    Name    string
    Profile Profile
}
 
type Profile struct {
    gorm.Model
    UserID uint
    Bio    string
}
 
// Create with association
user := User{
    Name: "John",
    Profile: Profile{Bio: "Software Developer"},
}
db.Create(&user)
 
// Preload
var user User
db.Preload("Profile").First(&user, 1)

7. Advanced Model Features

Custom Table Name

type User struct {
    gorm.Model
    Name string
}
 
func (User) TableName() string {
    return "custom_users"
}

Indexes

type User struct {
    gorm.Model
    Name  string `gorm:"index"`                    // Regular index
    Email string `gorm:"uniqueIndex"`              // Unique index
    Code  string `gorm:"index:idx_code,unique"`   // Named unique index
}

Default Values

type User struct {
    gorm.Model
    Name   string
    Status string `gorm:"default:active"`
    Age    int    `gorm:"default:18"`
}

Field Validation

type User struct {
    gorm.Model
    Name  string `gorm:"not null"`
    Email string `gorm:"uniqueIndex;not null"`
    Age   int    `gorm:"check:age >= 18"`
}

JSON Fields

type User struct {
    gorm.Model
    Name     string
    Settings datatypes.JSON
}
 
// Usage
settings := map[string]interface{}{
    "theme": "dark",
    "notifications": true,
}
user := User{
    Name:     "John",
    Settings: datatypes.JSON(settings),
}
db.Create(&user)

8. Transactions

// Manual transaction
tx := db.Begin()
 
user := models.User{Name: "John"}
if err := tx.Create(&user).Error; err != nil {
    tx.Rollback()
    return err
}
 
post := models.Post{Title: "My Post", UserID: user.ID}
if err := tx.Create(&post).Error; err != nil {
    tx.Rollback()
    return err
}
 
tx.Commit()
 
// Transaction with function
err := db.Transaction(func(tx *gorm.DB) error {
    if err := tx.Create(&user).Error; err != nil {
        return err
    }
 
    if err := tx.Create(&post).Error; err != nil {
        return err
    }
 
    return nil
})

9. Raw SQL

// Raw query
type Result struct {
    Name string
    Age  int
}
 
var results []Result
db.Raw("SELECT name, age FROM users WHERE age > ?", 18).Scan(&results)
 
// Execute raw SQL
db.Exec("UPDATE users SET status = ? WHERE age < ?", "minor", 18)
 
// Named arguments
db.Raw(
    "SELECT * FROM users WHERE name = @name AND age > @age",
    sql.Named("name", "John"),
    sql.Named("age", 18),
).Scan(&users)

10. Hooks (Lifecycle Callbacks)

type User struct {
    gorm.Model
    Name string
}
 
// Before create
func (u *User) BeforeCreate(tx *gorm.DB) error {
    // Generate UUID, validate data, etc.
    return nil
}
 
// After create
func (u *User) AfterCreate(tx *gorm.DB) error {
    // Send welcome email, log event, etc.
    return nil
}
 
// Before update
func (u *User) BeforeUpdate(tx *gorm.DB) error {
    return nil
}
 
// After update
func (u *User) AfterUpdate(tx *gorm.DB) error {
    return nil
}
 
// Before delete
func (u *User) BeforeDelete(tx *gorm.DB) error {
    return nil
}
 
// After delete
func (u *User) AfterDelete(tx *gorm.DB) error {
    return nil
}

11. Scopes (Reusable Queries)

// Define scope
func ActiveUsers(db *gorm.DB) *gorm.DB {
    return db.Where("status = ?", "active")
}
 
func AdultUsers(db *gorm.DB) *gorm.DB {
    return db.Where("age >= ?", 18)
}
 
// Use scopes
var users []User
db.Scopes(ActiveUsers, AdultUsers).Find(&users)
 
// Scope with parameters
func AgeGreaterThan(age int) func(db *gorm.DB) *gorm.DB {
    return func(db *gorm.DB) *gorm.DB {
        return db.Where("age > ?", age)
    }
}
 
db.Scopes(AgeGreaterThan(21)).Find(&users)

12. Complex Example with Everything

package models
 
type User struct {
    gorm.Model
    Name     string    `gorm:"not null;index"`
    Email    string    `gorm:"uniqueIndex;not null"`
    Age      int       `gorm:"check:age >= 18"`
    Status   string    `gorm:"default:active"`
    Posts    []Post
    Profile  Profile
    Roles    []Role    `gorm:"many2many:user_roles;"`
}
 
type Post struct {
    gorm.Model
    Title    string    `gorm:"not null"`
    Content  string
    UserID   uint
    User     User
    Tags     []Tag     `gorm:"many2many:post_tags;"`
}
 
type Profile struct {
    gorm.Model
    UserID   uint      `gorm:"uniqueIndex"`
    Bio      string
    Avatar   string
}
 
type Role struct {
    gorm.Model
    Name     string    `gorm:"uniqueIndex;not null"`
    Users    []User    `gorm:"many2many:user_roles;"`
}
 
type Tag struct {
    gorm.Model
    Name     string    `gorm:"uniqueIndex;not null"`
    Posts    []Post    `gorm:"many2many:post_tags;"`
}
 
// Usage
func GetUserWithEverything(db *gorm.DB, id uint) (*User, error) {
    var user User
    result := db.
        Preload("Posts.Tags").
        Preload("Profile").
        Preload("Roles").
        First(&user, id)
 
    if result.Error != nil {
        return nil, result.Error
    }
 
    return &user, nil
}

Summary

You now know:

  • Go fundamentals and syntax
  • Essential Go concepts for API development
  • How to build REST APIs with Gin framework
  • Complete GORM CRUD operations
  • Database relationships and advanced queries
  • Project structure and best practices

🚀 Next Steps:

  • Add authentication (JWT)
  • Add middleware (logging, CORS)
  • Add pagination
  • Add file uploads
  • Deploy your API

**Happy coding! 🎉