Golang Gin Web Framework Crash Course: Go REST API

Chapter 1: Introduction to Go & Gin

1. What is Go (Golang)?

Go (also called Golang) is:

  • A programming language developed by Google.
  • Known for simplicity, speed, and concurrency (handling multiple tasks at once).
  • Compiled and statically typed (fast and reliable).
  • Excellent for web development, APIs, backend services, and cloud apps.

Why use Go?

  • Simple syntax (easy to learn).
  • Super fast performance.
  • Built-in concurrency (via goroutines).
  • Huge standard library.
  • Backed by Google.

2. Installing Go

Step-by-step:

  1. Download Go:
    Go to the official site: https://go.dev/dl/
  2. Install it:
    Follow the instructions for your OS (Windows, macOS, Linux).
  3. Verify installation:
    Open a terminal or command prompt and run: go version You should see something like: go version go1.21.3 linux/amd64
  4. Set up workspace:
    Create a project folder: mkdir gin-tutorial cd gin-tutorial go mod init github.com/yourname/gin-tutorial

3. What is Gin Framework?

Gin is:

  • A lightweight, fast, and elegant web framework for Go.
  • Used to build APIs and web apps.
  • Inspired by Express (Node.js), Flask (Python).

Why use Gin?

  • Extremely fast.
  • Easy routing.
  • Middleware support.
  • Built-in JSON handling.
  • Ideal for REST APIs.

4. Installing Gin

In your project folder, run:

go get -u github.com/gin-gonic/gin

This installs Gin in your project.

5. Hello World in Gin

Create a file called main.go:

package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default() // Creates a router with default middleware (logger + recovery)
    
    r.GET("/", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "Hello, World!",
        })
    })

    r.Run() // Default runs on :8080
}

Run the app:

go run main.go

Open your browser and go to:

http://localhost:8080

You should see:

{"message":"Hello, World!"}

Congratulations! You’ve created your first Gin web server.


Chapter 2: Your First Gin App

package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()
    
    r.GET("/", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "Hello, World!",
        })
    })

    r.Run()
}

Step-by-step Explanation for above code

package main

This tells Go that this is the main package and it should run this file as an executable program.

Every Go application starts from package main.

import "github.com/gin-gonic/gin"

This imports the Gin framework into your code.

  • github.com/gin-gonic/gin is the official Gin package.
  • You must run go get -u github.com/gin-gonic/gin to download it first.

func main() { ... }

This is the main function — the entry point of your Go program.
Go will start execution from here.

r := gin.Default()

This creates a new Gin router (r stands for “router”).

  • gin.Default() gives you a router with:
    • Logger middleware → logs all incoming requests.
    • Recovery middleware → handles panics so your app doesn’t crash.

You can also do r := gin.New() if you don’t want default middleware.

r.GET("/", func(c *gin.Context) { ... })

This tells Gin:

  • HTTP method: GET
  • Route: / (root path)
  • Handler function: What to do when someone visits this route.
func(c *gin.Context) {
    ...
}

This is an anonymous function (a function without a name).
It handles the request and sends a response.

c.JSON(200, gin.H{ "message": "Hello, World!" })

This line sends a JSON response.

  • 200: HTTP status code for success.
  • gin.H{}: A shortcut for creating a map in Go.
    Equivalent to map[string]interface{}{}

So it sends this JSON:

{
  "message": "Hello, World!"
}

r.Run()

This starts your server.

  • Default listens on port 8080.
  • You can customize the port like r.Run(":3000").

Running Your App

  1. Run this command in your terminal: go run main.go
  2. Open your browser or Postman and visit: http://localhost:8080
  3. Output: { "message": "Hello, World!" }

Summary

  • You created your first web API using Gin.
  • Learned how routing and responses work.
  • Built a server that returns a JSON message.

Chapter 3: Routing in Gin

What is Routing?

Routing is how your app decides which code to run when a user goes to a specific URL or makes a request (like GET, POST, etc.).

1. Basic Routes (GET, POST, PUT, DELETE)

GET route – used to get data

r.GET("/hello", func(c *gin.Context) {
    c.JSON(200, gin.H{"message": "Hello from GET!"})
})

POST route – used to send data (like creating something)

r.POST("/submit", func(c *gin.Context) {
    c.JSON(200, gin.H{"message": "Data submitted successfully"})
})

PUT route – used to update data

r.PUT("/update", func(c *gin.Context) {
    c.JSON(200, gin.H{"message": "Data updated"})
})

DELETE route – used to delete data

r.DELETE("/delete", func(c *gin.Context) {
    c.JSON(200, gin.H{"message": "Data deleted"})
})

2. Route Parameters (Dynamic URLs)

You can pass dynamic values in the URL like:

r.GET("/user/:name", func(c *gin.Context) {
    name := c.Param("name") // Extract "name" from URL
    c.JSON(200, gin.H{"user": name})
})

Example URL:

http://localhost:8080/user/rahul

Response:

{
  "user": "rahul"
}

3. Query Parameters (e.g., ?name=value)

Used when user sends data like:

/search?query=go

Example:

r.GET("/search", func(c *gin.Context) {
    query := c.Query("query") // get value from ?query=
    c.JSON(200, gin.H{"search_query": query})
})

Visit:

http://localhost:8080/search?query=gin

Output:

{
  "search_query": "gin"
}

4. Grouped Routes (organize your API)

You can group routes under a common path:

api := r.Group("/api")
{
    api.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{"message": "pong"})
    })

    api.GET("/users", func(c *gin.Context) {
        c.JSON(200, gin.H{"users": []string{"Alice", "Bob"}})
    })
}

Now you can visit:

  • /api/ping
  • /api/users

5. Handling 404 (Page Not Found)

Custom 404 handler:

r.NoRoute(func(c *gin.Context) {
    c.JSON(404, gin.H{"error": "Page not found"})
})

Now any invalid route will return:

{
  "error": "Page not found"
}

Summary

  • You now know how to handle GET, POST, PUT, DELETE routes.
  • You can use path (/user/:id) and query (?name=xyz) parameters.
  • You can group routes for cleaner code.
  • You can handle invalid routes.

Chapter 4: Request & Response Handling

1. Handling JSON Request Body (POST)

When the client sends JSON data, you can bind it to a Go struct.

Example: Define a struct for incoming data

type Login struct {
    Username string `json:"username"`
    Password string `json:"password"`
}

Receive JSON in POST request

r.POST("/login", func(c *gin.Context) {
    var json Login

    if err := c.ShouldBindJSON(&json); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    // Check credentials
    if json.Username == "admin" && json.Password == "1234" {
        c.JSON(200, gin.H{"status": "Login successful"})
    } else {
        c.JSON(401, gin.H{"status": "Unauthorized"})
    }
})

Client sends (JSON body):

{
  "username": "admin",
  "password": "1234"
}

2. Handling Form Data (application/x-www-form-urlencoded)

For HTML form submissions.

HTML Form example:

<form action="/submit" method="POST">
  <input name="name" />
  <input name="email" />
  <button type="submit">Send</button>
</form>

Server-side code:

r.POST("/submit", func(c *gin.Context) {
    name := c.PostForm("name")
    email := c.PostForm("email")

    c.JSON(200, gin.H{
        "name":  name,
        "email": email,
    })
})

3. Sending Different Types of Responses

JSON

c.JSON(200, gin.H{"message": "Success"})

String

c.String(200, "Plain text response")

HTML

r.LoadHTMLFiles("templates/index.html")

r.GET("/html", func(c *gin.Context) {
    c.HTML(200, "index.html", gin.H{
        "title": "Hello HTML",
    })
})

4. Binding with Structs + Validation

You can also add validation rules to struct fields.

Struct:

type RegisterForm struct {
    Name  string `form:"name" binding:"required"`
    Email string `form:"email" binding:"required,email"`
}

Use ShouldBind for form or query data:

r.POST("/register", func(c *gin.Context) {
    var form RegisterForm

    if err := c.ShouldBind(&form); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    c.JSON(200, gin.H{"message": "Registered", "name": form.Name})
})

Summary

  • You can now receive JSON data using ShouldBindJSON.
  • You can read form data using PostForm.
  • You can send back JSON, strings, or HTML.
  • You learned how to bind & validate user input using structs.

Chapter 5: Middleware in Gin

What is Middleware?

Middleware is a function that runs before (or after) a route handler.

Use cases:

  • Logging requests
  • Authenticating users
  • Setting headers
  • Handling CORS
  • Error recovery

1. Using Built-in Middleware

Gin comes with helpful middleware built-in.

Logger Middleware

Automatically logs every request.

r := gin.Default() // Includes Logger and Recovery middleware

If you want to use manually:

r := gin.New()
r.Use(gin.Logger()) // Logs all requests

Recovery Middleware

Prevents your server from crashing if a panic happens.

r.Use(gin.Recovery())

2. Writing Custom Middleware

You can write your own middleware as a function:

func CustomLogger() gin.HandlerFunc {
    return func(c *gin.Context) {
        t := time.Now()

        // Before request
        log.Println("Request started")

        c.Next() // Process the request

        // After request
        latency := time.Since(t)
        log.Printf("Request ended in %v\n", latency)
    }
}

Use it in your router

r.Use(CustomLogger())

3. Middleware Per Route or Group

You can apply middleware to a specific route:

r.GET("/ping", CustomLogger(), func(c *gin.Context) {
    c.JSON(200, gin.H{"message": "pong"})
})

Or to a group of routes:

admin := r.Group("/admin")
admin.Use(CustomLogger()) // Only for /admin routes

admin.GET("/dashboard", func(c *gin.Context) {
    c.JSON(200, gin.H{"status": "Admin dashboard"})
})

4. Example: Simple Auth Middleware

func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.GetHeader("Authorization")

        if token != "secret123" {
            c.JSON(401, gin.H{"error": "Unauthorized"})
            c.Abort()
            return
        }

        c.Next()
    }
}

Apply to a route:

r.GET("/secure", AuthMiddleware(), func(c *gin.Context) {
    c.JSON(200, gin.H{"message": "Welcome to the secure route!"})
})

If the request doesn’t have the right token, it will return 401.

Summary

  • Middleware runs before (and after) the route handler.
  • Gin includes Logger and Recovery middleware.
  • You can create and register your own middleware.
  • Middleware can be applied globally, per group, or per route.
  • You created a simple Auth middleware for protected routes.

Chapter 6: Structuring Your Project for Production

Why Use a Folder Structure?

For small apps, everything can be in main.go. But for large apps, this becomes a mess.
A good structure makes your code clean, testable, and easier to maintain.

Suggested Folder Structure

/your-app
│
├── main.go               Entry point
├── go.mod                Dependencies
│
├── /routes                 All routes grouped
│   └── router.go
│
├── /controllers          Logic for handling requests
│   └── user_controller.go
│
├── /models                Structs and DB logic
│   └── user.go
│
├── /config                  Configuration, env, DB setup
│   └── config.go
│
├── /middleware         Custom middleware
│   └── auth.go
│
└── /utils                     Helper functions
    └── token.go

Explanation of Each Folder

1. main.go

Starts the server.

package main

import (
    "your-app/routes"
)

func main() {
    r := routes.SetupRouter()
    r.Run(":8080")
}

2. /routes/router.go

Handles route groups.

package routes

import (
    "github.com/gin-gonic/gin"
    "your-app/controllers"
)

func SetupRouter() *gin.Engine {
    r := gin.Default()

    userGroup := r.Group("/users")
    {
        userGroup.GET("/", controllers.GetUsers)
        userGroup.POST("/", controllers.CreateUser)
    }

    return r
}

3. /controllers/user_controller.go

Business logic.

package controllers

import (
    "github.com/gin-gonic/gin"
    "net/http"
)

func GetUsers(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{"message": "All users"})
}

func CreateUser(c *gin.Context) {
    c.JSON(http.StatusCreated, gin.H{"message": "User created"})
}

4. /models/user.go

Define your data models here (e.g., for GORM).

package models

type User struct {
    ID    uint   `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

5. /config/config.go

Set up database or load .env.

package config

import (
    "gorm.io/gorm"
    "gorm.io/driver/sqlite"
)

var DB *gorm.DB

func Connect() {
    database, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    if err != nil {
        panic("Failed to connect to database!")
    }

    DB = database
}

6. /middleware/auth.go

Your custom middleware.

package middleware

import (
    "github.com/gin-gonic/gin"
)

func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.GetHeader("Authorization")
        if token != "secret123" {
            c.JSON(401, gin.H{"error": "Unauthorized"})
            c.Abort()
            return
        }
        c.Next()
    }
}

Summary

  • You now have a modular folder structure.
  • You can keep your code clean, scalable, and organized.
  • Makes it easy to test and expand your app later.

Chapter 7: Connecting to a Database using GORM

We’ll use GORM, the most popular ORM (Object Relational Mapper) in Golang, which helps us work with databases using Go structs instead of raw SQL.

1. What is GORM?

GORM allows you to:

  • Create tables from Go structs
  • Insert, update, delete records
  • Query the database easily
  • Handle relationships (one-to-many, etc.)

2. Install GORM and SQLite/PostgreSQL

You can use SQLite for learning. Run this in terminal:

go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite

If you prefer PostgreSQL:

go get -u gorm.io/driver/postgres

3. Setup Database Connection (/config/database.go)

For SQLite:

package config

import (
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
)

var DB *gorm.DB

func ConnectDatabase() {
    database, err := gorm.Open(sqlite.Open("myapp.db"), &gorm.Config{})
    if err != nil {
        panic("Failed to connect to database!")
    }

    DB = database
}

For PostgreSQL (alternative):

import (
    "gorm.io/driver/postgres"
)

dsn := "host=localhost user=postgres password=yourpass dbname=mydb port=5432 sslmode=disable"
database, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})

4. Create a Model (/models/user.go)

package models

type User struct {
    ID    uint   `gorm:"primaryKey"`
    Name  string
    Email string
}

5. Migrate Model to Database

In main.go or config/database.go:

import "your-app/models"

DB.AutoMigrate(&models.User{})

This will automatically create a users table.

6. Insert and Read Data (in /controllers/user_controller.go)

Create User:

func CreateUser(c *gin.Context) {
    var user models.User

    if err := c.ShouldBindJSON(&user); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    config.DB.Create(&user)
    c.JSON(201, user)
}

Get All Users:

func GetUsers(c *gin.Context) {
    var users []models.User
    config.DB.Find(&users)
    c.JSON(200, users)
}

7. Summary

  • Installed and connected to GORM
  • Created and migrated a model
  • Inserted and queried data using GORM
  • Used Gin + GORM together for real DB interaction

Chapter 8: CRUD Operations using Gin + GORM

We’ll build 4 endpoints for User:

  • Create (POST)
  • Read (GET)
  • Update (PUT)
  • Delete (DELETE)

Model Recap

Your user model (/models/user.go):

package models

type User struct {
    ID    uint   `gorm:"primaryKey" json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

Controller File: /controllers/user_controller.go

Let’s add all CRUD functions here.

1. Create User (POST /users)

func CreateUser(c *gin.Context) {
    var user models.User

    if err := c.ShouldBindJSON(&user); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    config.DB.Create(&user)
    c.JSON(201, user)
}

2. Get All Users (GET /users)

func GetUsers(c *gin.Context) {
    var users []models.User
    config.DB.Find(&users)
    c.JSON(200, users)
}

3. Get Single User by ID (GET /users/:id)

func GetUserByID(c *gin.Context) {
    id := c.Param("id")
    var user models.User

    if err := config.DB.First(&user, id).Error; err != nil {
        c.JSON(404, gin.H{"error": "User not found"})
        return
    }

    c.JSON(200, user)
}

4. Update User (PUT /users/:id)

func UpdateUser(c *gin.Context) {
    id := c.Param("id")
    var user models.User

    if err := config.DB.First(&user, id).Error; err != nil {
        c.JSON(404, gin.H{"error": "User not found"})
        return
    }

    var input models.User
    if err := c.ShouldBindJSON(&input); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    user.Name = input.Name
    user.Email = input.Email

    config.DB.Save(&user)
    c.JSON(200, user)
}

5. Delete User (DELETE /users/:id)

func DeleteUser(c *gin.Context) {
    id := c.Param("id")
    var user models.User

    if err := config.DB.First(&user, id).Error; err != nil {
        c.JSON(404, gin.H{"error": "User not found"})
        return
    }

    config.DB.Delete(&user)
    c.JSON(200, gin.H{"message": "User deleted"})
}

Route File: /routes/router.go

Make sure your routes are defined:

userGroup := r.Group("/users")
{
    userGroup.POST("/", controllers.CreateUser)
    userGroup.GET("/", controllers.GetUsers)
    userGroup.GET("/:id", controllers.GetUserByID)
    userGroup.PUT("/:id", controllers.UpdateUser)
    userGroup.DELETE("/:id", controllers.DeleteUser)
}

Summary

  • You’ve created a full set of CRUD routes using Gin + GORM
  • Now your app can Create, Read, Update, and Delete real database records

Chapter 9: Using Environment Variables and .env Files

Why use environment variables?

  • Never hardcode DB passwords or secrets in code.
  • Easily switch configs between development, staging, production.

Step 1: Install godotenv

Use this package to load variables from a .env file:

go get github.com/joho/godotenv

Step 2: Create a .env File in the Root of Your Project

DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=yourpassword
DB_NAME=mydb

Step 3: Load .env in main.go

At the top of main.go:

import (
    "log"
    "github.com/joho/godotenv"
)

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

Step 4: Use os.Getenv to Access Env Vars

In config/database.go:

import (
    "fmt"
    "os"
    "gorm.io/driver/postgres"
    "gorm.io/gorm"
)

func ConnectDatabase() {
    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"),
    )

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

    DB = database
}

Summary

  • Installed godotenv
  • Created .env for secrets
  • Loaded env vars and used them in your DB config

Chapter 10: Middleware in Gin (Logging, CORS, Authentication)

What is Middleware?

Middleware is a function that runs before or after your route handlers. It can:

  • Modify requests or responses
  • Perform authentication
  • Log requests
  • Handle CORS (Cross-Origin Resource Sharing)

1. Built-in Logger Middleware

Gin provides a default logger that logs requests:

r := gin.Default() // Includes Logger & Recovery middleware automatically

Or explicitly:

r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery())

2. Writing Custom Middleware

Example: A simple Auth middleware that checks for an API key:

func APIKeyAuth() gin.HandlerFunc {
    return func(c *gin.Context) {
        apiKey := c.GetHeader("X-API-KEY")
        if apiKey != "mysecretkey" {
            c.JSON(401, gin.H{"error": "Unauthorized"})
            c.Abort()
            return
        }
        c.Next()
    }
}

Use it:

r := gin.Default()

// Apply globally
r.Use(APIKeyAuth())

// Or apply to a route group
authorized := r.Group("/", APIKeyAuth())
{
    authorized.GET("/protected", protectedHandler)
}

3. CORS Middleware (Handling Cross-Origin Requests)

Use the popular third-party package:

go get github.com/gin-contrib/cors

Example:

import "github.com/gin-contrib/cors"

r := gin.Default()

r.Use(cors.New(cors.Config{
    AllowOrigins:     []string{"https://example.com"},
    AllowMethods:     []string{"GET", "POST", "PUT", "DELETE"},
    AllowHeaders:     []string{"Origin", "Content-Type", "X-API-KEY"},
    AllowCredentials: true,
}))

Summary

  • Middleware intercepts HTTP requests/responses.
  • Gin has built-in Logger & Recovery middleware.
  • You can write your own middleware for Auth or other purposes.
  • Use third-party middleware for CORS easily.

Chapter 11: Validating Input and Binding JSON in Gin

What is Binding?

Binding means converting incoming JSON (or form data) into Go structs automatically.

Gin supports:

  • JSON
  • XML
  • Form data (query, POST form)
  • Multipart forms (file uploads)

Example Model with Validation Tags

Let’s say you want to validate a User input:

type User struct {
    Name  string `json:"name" binding:"required"`       // must be present
    Email string `json:"email" binding:"required,email"` // must be valid email
}

Binding JSON with Validation in Controller

func CreateUser(c *gin.Context) {
    var user User

    if err := c.ShouldBindJSON(&user); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    // Proceed with creating user
    c.JSON(201, gin.H{"message": "User created", "user": user})
}

If validation fails, Gin returns an error automatically with details.

Common Binding Tags

TagMeaning
requiredField must be present
emailMust be valid email format
min=3Minimum length/number
max=100Maximum length/number
len=5Exact length
gte=10Greater than or equal to 10
lte=20Less than or equal to 20

Validate Query Params or Form Data

For query parameters:

type SearchQuery struct {
    Page int `form:"page" binding:"required,gte=1"`
    Q    string `form:"q" binding:"required"`
}

func SearchHandler(c *gin.Context) {
    var query SearchQuery

    if err := c.ShouldBindQuery(&query); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    c.JSON(200, gin.H{"query": query})
}

Summary

  • Use binding tags on structs for validation
  • Use ShouldBindJSON for JSON request body validation
  • Use ShouldBindQuery for URL query param validation
  • Gin automatically returns detailed errors for invalid input

Chapter 12: Testing Your Gin APIs

Why Test?

  • Ensure your API works as expected.
  • Catch bugs early.
  • Make refactoring safe.

Basic Setup

Create a new test file for your handlers, e.g., user_controller_test.go.

1. Import testing packages

import (
    "net/http"
    "net/http/httptest"
    "strings"
    "testing"

    "github.com/gin-gonic/gin"
    "github.com/stretchr/testify/assert"
)
  • httptest helps simulate HTTP requests.
  • assert is from stretchr/testify to make test assertions cleaner.

Install testify:

go get github.com/stretchr/testify

2. Example: Testing CreateUser Handler

func TestCreateUser(t *testing.T) {
    router := gin.Default()
    router.POST("/users", CreateUser)

    // Prepare JSON payload
    jsonPayload := `{"name": "John", "email": "[email protected]"}`

    req, _ := http.NewRequest("POST", "/users", strings.NewReader(jsonPayload))
    req.Header.Set("Content-Type", "application/json")

    w := httptest.NewRecorder()

    router.ServeHTTP(w, req)

    assert.Equal(t, 201, w.Code)
    assert.Contains(t, w.Body.String(), "John")
}

3. Testing with Invalid Input

func TestCreateUser_InvalidInput(t *testing.T) {
    router := gin.Default()
    router.POST("/users", CreateUser)

    // Missing required fields
    jsonPayload := `{"name": "", "email": "not-an-email"}`

    req, _ := http.NewRequest("POST", "/users", strings.NewReader(jsonPayload))
    req.Header.Set("Content-Type", "application/json")

    w := httptest.NewRecorder()

    router.ServeHTTP(w, req)

    assert.Equal(t, 400, w.Code)
    assert.Contains(t, w.Body.String(), "error")
}

4. Running Tests

Run all tests with:

go test ./...

Summary

  • Use httptest to simulate requests.
  • Use assert for clean test checks.
  • Write tests for valid and invalid cases.
  • Run tests frequently.

Chapter 13: Deploying Your Gin App with Docker

What is Docker?

  • Docker packages your app with all dependencies into a container.
  • Containers run consistently anywhere (dev, staging, production).
  • Simplifies deployment and scaling.

Step 1: Create a Dockerfile

In your project root, create a file named Dockerfile (no extension):

# Use official Golang image as build environment
FROM golang:1.20-alpine AS builder

WORKDIR /app

# Copy go.mod and go.sum and download dependencies
COPY go.mod go.sum ./
RUN go mod download

# Copy source code
COPY . .

# Build the Go app as a statically linked binary
RUN go build -o main .

# Use a minimal image for final container
FROM alpine:latest

WORKDIR /root/

# Copy the binary from builder stage
COPY --from=builder /app/main .

# Expose port your app listens on
EXPOSE 8080

# Run the binary
CMD ["./main"]

Step 2: Build the Docker Image

Run this in your project root:

docker build -t my-gin-app .

Step 3: Run the Container Locally

docker run -p 8080:8080 my-gin-app

Now your app is accessible at http://localhost:8080

Step 4: Docker Compose (Optional for Multi-Services)

Create docker-compose.yml to run your app with a DB:

version: '3'
services:
  app:
    build: .
    ports:
      - "8080:8080"
    depends_on:
      - db
  db:
    image: postgres:14
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: yourpassword
      POSTGRES_DB: mydb
    ports:
      - "5432:5432"

Run:

docker-compose up

Summary

  • Created a multi-stage Dockerfile for a small, efficient container
  • Built and ran your Gin app in Docker
  • Optionally set up Postgres DB with Docker Compose

Chapter 14: Gin Best Practices and Performance Tips

1. Use Middleware Wisely

  • Use built-in middleware (Logger, Recovery) to handle logging and panic recovery.
  • Add custom middleware for auth, rate limiting, or request validation.
  • Avoid heavy processing in middleware to keep requests fast.

2. Handle Errors Gracefully

  • Return clear, consistent error messages with proper HTTP status codes.
  • Use Gin’s c.AbortWithStatusJSON to stop request handling and respond early.

Example:

if err != nil {
    c.AbortWithStatusJSON(400, gin.H{"error": err.Error()})
    return
}

3. Validate All Inputs

  • Always validate user input using Gin’s binding and validation.
  • Prevent invalid or malicious data from entering your system.

4. Use Context Timeouts for External Calls

  • When calling databases or APIs, set timeouts to avoid hanging requests.

Example:

ctx, cancel := context.WithTimeout(c.Request.Context(), 5*time.Second)
defer cancel()

result, err := db.QueryContext(ctx, ...)

5. Avoid Global Variables

  • Pass dependencies (DB, config) via structs or Gin context instead of globals.
  • This helps with testing and maintenance.

6. Structure Your Project

Example structure:

/cmd
  /app
    main.go
/internal
  /database
    database.go
  /handlers
    user.go
  /models
    user.go
  /middleware
    auth.go
/config
  config.go

7. Optimize JSON Serialization

  • Use json:"-" to omit fields you don’t want in API responses.
  • Use struct tags for custom JSON field names.

8. Use Gin Release Mode in Production

Before running your app in production, set:

gin.SetMode(gin.ReleaseMode)

This disables debug logs and speeds up your app.

9. Secure Your API

  • Use HTTPS (TLS) in production.
  • Use authentication tokens (JWT, API keys).
  • Limit request rates to prevent abuse.

Summary

  • Use middleware thoughtfully.
  • Validate input and handle errors well.
  • Structure your project for clarity.
  • Use context timeouts and avoid globals.
  • Secure and optimize your app for production.

Congratulations! You’ve completed the beginner-friendly Gin tutorial.

Gin Web Framework Crash Course PDF