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
- Part 1: Go Fundamentals
- Part 2: Essential Go Concepts Cheatsheet
- Part 3: Building REST API with Gin & GORM
- 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:
- Visit https://golang.org
- Download installer for your OS
- Run installer
- Verify installation:
go version
Setting Up VSCode
Required Extensions:
-
Go Extension (by Go Team at Google)
- Syntax highlighting
- IntelliSense (auto-complete)
- Error detection
- Debugging support
-
Code Runner (Optional)
- Run code with Ctrl+Alt+N
- Quick testing without terminal
Installing Go Tools:
- Open VSCode Command Palette (Ctrl+Shift+P)
- Type "Go: Install/Update Tools"
- Select all tools and click OK
These tools include:
gopls
- Go Language Server (like TypeScript Language Server)dlv
- Debuggerstaticcheck
- 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
orapp.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 Type | JavaScript Equivalent | Example |
---|---|---|
string | string | "Hello" |
int | number | 42 |
float64 | number | 3.14 |
bool | boolean | true |
[]string | string[] | ["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
Specifier | Use | Example |
---|---|---|
%s | String | fmt.Printf("%s", "text") |
%d | Integer | fmt.Printf("%d", 42) |
%f | Float | fmt.Printf("%.2f", 3.14159) |
%t | Boolean | fmt.Printf("%t", true) |
%v | Any value | fmt.Printf("%v", anything) |
%+v | Struct with field names | fmt.Printf("%+v", person) |
%T | Type of value | fmt.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! 🎉