Table of Contents
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:
- Download Go:
Go to the official site: https://go.dev/dl/ - Install it:
Follow the instructions for your OS (Windows, macOS, Linux). - Verify installation:
Open a terminal or command prompt and run:go version
You should see something like:go version go1.21.3 linux/amd64
- 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 tomap[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
- Run this command in your terminal:
go run main.go
- Open your browser or Postman and visit:
http://localhost:8080
- 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
Tag | Meaning |
---|---|
required | Field must be present |
email | Must be valid email format |
min=3 | Minimum length/number |
max=100 | Maximum length/number |
len=5 | Exact length |
gte=10 | Greater than or equal to 10 |
lte=20 | Less 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 fromstretchr/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.