Site icon StudyGyaan

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:

Why use Go?

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:

Why use Gin?

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.

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”).

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

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

This tells Gin:

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.

So it sends this JSON:

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

r.Run()

This starts your server.

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


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:

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


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


Chapter 5: Middleware in Gin

What is Middleware?

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

Use cases:

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


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


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:

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


Chapter 8: CRUD Operations using Gin + GORM

We’ll build 4 endpoints for User:

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


Chapter 9: Using Environment Variables and .env Files

Why use environment variables?

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


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:

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


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:

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


Chapter 12: Testing Your Gin APIs

Why Test?

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"
)

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": "john@example.com"}`

    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


Chapter 13: Deploying Your Gin App with Docker

What is Docker?

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


Chapter 14: Gin Best Practices and Performance Tips

1. Use Middleware Wisely

2. Handle Errors Gracefully

Example:

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

3. Validate All Inputs

4. Use Context Timeouts for External Calls

Example:

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

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

5. Avoid Global Variables

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

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

Summary

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

Gin Web Framework Crash Course PDF

Exit mobile version