internal/skills/content/gin/SKILL.md
Gin framework guardrails, patterns, and best practices for AI-assisted development. Use when working with Gin projects, or when the user mentions Gin framework. Provides middleware patterns, routing, validation, error handling, and REST API guidelines.
npx skillsauth add ar4mirez/samuel ginInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Applies to: Gin 1.9+, REST APIs, Microservices, Web Applications Language Guide: @.claude/skills/go-guide/SKILL.md
Gin is a high-performance HTTP web framework written in Go featuring a martini-like API with performance up to 40x faster. It is the most popular Go web framework, ideal for building REST APIs and microservices.
Use Gin when:
Consider alternatives when:
/api/v1)gin.Context for request-scoped data onlygin.ReleaseMode in productiongin.Default() in production without understanding its middlewaremyproject/
├── cmd/
│ └── api/
│ └── main.go # Entry point, server setup, graceful shutdown
├── internal/
│ ├── config/
│ │ └── config.go # Configuration from env vars
│ ├── handler/
│ │ ├── handler.go # Handler registry struct
│ │ ├── user.go # User handlers
│ │ └── auth.go # Auth handlers
│ ├── middleware/
│ │ ├── auth.go # JWT/Bearer auth middleware
│ │ ├── cors.go # CORS middleware
│ │ └── logger.go # Request logging middleware
│ ├── model/
│ │ ├── user.go # Domain model + request/response DTOs
│ │ └── response.go # Standardized response wrappers
│ ├── repository/
│ │ ├── repository.go # Repository registry (interfaces)
│ │ └── user.go # User repository implementation
│ ├── service/
│ │ ├── service.go # Service registry (interfaces)
│ │ └── user.go # User business logic
│ └── router/
│ └── router.go # Route definitions and grouping
├── pkg/
│ ├── validator/
│ │ └── validator.go # Custom validators
│ └── response/
│ └── response.go # Shared response helpers
├── migrations/
├── .env.example
├── go.mod
├── go.sum
├── Makefile
└── README.md
Layer responsibilities:
handler/ — HTTP concerns only: parse request, call service, write responseservice/ — Business logic, validation, orchestrationrepository/ — Data access, database queriesmodel/ — Domain types, request/response DTOs, validation tagsmiddleware/ — Cross-cutting: auth, logging, CORS, rate limitingrouter/ — Route registration, grouping, middleware attachmentfunc Setup(handlers *handler.Handlers, mw *middleware.Middleware) *gin.Engine {
r := gin.New()
// Global middleware
r.Use(gin.Recovery())
r.Use(middleware.Logger())
r.Use(middleware.CORS())
// Health check (always public)
r.GET("/health", func(c *gin.Context) {
c.JSON(200, gin.H{"status": "ok"})
})
// API v1 routes
v1 := r.Group("/api/v1")
{
// Public routes
auth := v1.Group("/auth")
{
auth.POST("/login", handlers.Auth.Login)
auth.POST("/refresh", handlers.Auth.Refresh)
}
// Protected routes
users := v1.Group("/users")
{
users.POST("", handlers.User.CreateUser) // Public
users.Use(mw.Auth()) // Auth from here down
users.GET("", handlers.User.GetUsers)
users.GET("/me", handlers.User.GetCurrentUser)
users.GET("/:id", handlers.User.GetUser)
users.PATCH("/:id", handlers.User.UpdateUser)
users.DELETE("/:id", mw.AdminOnly(), handlers.User.DeleteUser)
}
}
return r
}
Routing conventions:
gin.New() (not gin.Default()) and add middleware explicitlymw.AdminOnly())/health endpointfunc (m *Middleware) Auth() gin.HandlerFunc {
return func(c *gin.Context) {
header := c.GetHeader("Authorization")
if header == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized,
model.NewErrorResponse("missing authorization header"))
return
}
parts := strings.Split(header, " ")
if len(parts) != 2 || parts[0] != "Bearer" {
c.AbortWithStatusJSON(http.StatusUnauthorized,
model.NewErrorResponse("invalid authorization header"))
return
}
claims, err := m.authService.ValidateToken(parts[1])
if err != nil {
c.AbortWithStatusJSON(http.StatusUnauthorized,
model.NewErrorResponse("invalid token"))
return
}
// Set user context for downstream handlers
c.Set("user_id", claims.UserID)
c.Set("is_admin", claims.IsAdmin)
c.Next()
}
}
func (m *Middleware) AdminOnly() gin.HandlerFunc {
return func(c *gin.Context) {
isAdmin, exists := c.Get("is_admin")
if !exists || !isAdmin.(bool) {
c.AbortWithStatusJSON(http.StatusForbidden,
model.NewErrorResponse("admin access required"))
return
}
c.Next()
}
}
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
query := c.Request.URL.RawQuery
c.Next()
if query != "" {
path = path + "?" + query
}
log.Printf("[GIN] %3d | %13v | %15s | %-7s %s",
c.Writer.Status(), time.Since(start),
c.ClientIP(), c.Request.Method, path)
}
}
Gin uses binding struct tags for request validation (backed by go-playground/validator).
// Create request — all fields required
type CreateUserRequest struct {
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required,min=8"`
FirstName string `json:"first_name" binding:"required,min=1,max=100"`
LastName string `json:"last_name" binding:"required,min=1,max=100"`
}
// Update request — all fields optional (pointer types)
type UpdateUserRequest struct {
FirstName *string `json:"first_name" binding:"omitempty,min=1,max=100"`
LastName *string `json:"last_name" binding:"omitempty,min=1,max=100"`
IsActive *bool `json:"is_active"`
}
func (h *UserHandler) CreateUser(c *gin.Context) {
var req model.CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, model.NewErrorResponse(err.Error()))
return
}
user, err := h.userService.Create(c.Request.Context(), &req)
if err != nil {
handleServiceError(c, err)
return
}
c.JSON(http.StatusCreated, model.NewSuccessResponse(user.ToResponse()))
}
Binding conventions:
ShouldBindJSON (not BindJSON) to control error responses yourselffunc (h *UserHandler) GetUsers(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
perPage, _ := strconv.Atoi(c.DefaultQuery("per_page", "20"))
users, total, err := h.userService.GetAll(c.Request.Context(), page, perPage)
if err != nil {
c.JSON(http.StatusInternalServerError, model.NewErrorResponse(err.Error()))
return
}
responses := make([]*model.UserResponse, len(users))
for i, user := range users {
responses[i] = user.ToResponse()
}
c.JSON(http.StatusOK, model.NewPaginatedResponse(responses, page, perPage, total))
}
type Response struct {
Success bool `json:"success"`
Message string `json:"message,omitempty"`
Data interface{} `json:"data,omitempty"`
Error string `json:"error,omitempty"`
}
type PaginatedResponse struct {
Success bool `json:"success"`
Data interface{} `json:"data"`
Meta *PageMeta `json:"meta"`
}
type PageMeta struct {
Page int `json:"page"`
PerPage int `json:"per_page"`
Total int64 `json:"total"`
TotalPages int `json:"total_pages"`
}
func NewSuccessResponse(data interface{}) *Response {
return &Response{Success: true, Data: data}
}
func NewErrorResponse(message string) *Response {
return &Response{Success: false, Error: message}
}
Define domain errors in the service layer, map them to HTTP status in handlers:
// service layer
var (
ErrUserNotFound = errors.New("user not found")
ErrUserAlreadyExists = errors.New("user already exists")
ErrInvalidCredentials = errors.New("invalid credentials")
)
// handler helper
func handleServiceError(c *gin.Context, err error) {
switch {
case errors.Is(err, service.ErrUserNotFound):
c.JSON(http.StatusNotFound, model.NewErrorResponse(err.Error()))
case errors.Is(err, service.ErrUserAlreadyExists):
c.JSON(http.StatusConflict, model.NewErrorResponse(err.Error()))
case errors.Is(err, service.ErrInvalidCredentials):
c.JSON(http.StatusUnauthorized, model.NewErrorResponse(err.Error()))
default:
c.JSON(http.StatusInternalServerError, model.NewErrorResponse("internal server error"))
}
}
Error handling rules:
errors.Is for sentinel errors, errors.As for typed errorsResponse structurefunc main() {
cfg, err := config.Load()
if err != nil {
log.Fatalf("Failed to load config: %v", err)
}
if cfg.Environment == "production" {
gin.SetMode(gin.ReleaseMode)
}
// Wire layers: repo -> service -> handler
db, err := initDB(cfg.DatabaseURL)
if err != nil {
log.Fatalf("Failed to connect to database: %v", err)
}
repos := repository.NewRepositories(db)
services := service.NewServices(repos, cfg)
handlers := handler.NewHandlers(services)
r := router.Setup(handlers, middleware.NewMiddleware(cfg))
srv := &http.Server{
Addr: ":" + cfg.Port,
Handler: r,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Server failed: %v", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("Server forced to shutdown: %v", err)
}
}
# Initialize project
go mod init myproject
# Install dependencies
go mod tidy
# Run development server
go run cmd/api/main.go
# Build binary
go build -o bin/api cmd/api/main.go
# Run tests
go test ./...
go test -v -cover ./...
# Run with race detection
go test -race ./...
# Lint
golangci-lint run
# Generate Swagger docs (with swaggo)
swag init -g cmd/api/main.go
# Database migrations (using golang-migrate)
migrate -path migrations -database "$DATABASE_URL" up
migrate -path migrations -database "$DATABASE_URL" down
// Core
github.com/gin-gonic/gin v1.9.1
// Auth
github.com/golang-jwt/jwt/v5 v5.0.0
golang.org/x/crypto v0.14.0
// Database
gorm.io/gorm v1.25.5
gorm.io/driver/postgres v1.5.4
// Config
github.com/joho/godotenv v1.5.1
// Testing
github.com/stretchr/testify v1.8.4
// Docs
github.com/swaggo/gin-swagger v1.6.0
github.com/swaggo/swag v1.16.2
For detailed patterns and full implementation examples, see:
development
Zig language guardrails, patterns, and best practices for AI-assisted development. Use when working with Zig files (.zig), build.zig, or when the user mentions Zig. Provides comptime patterns, allocator conventions, C interop guidelines, and testing standards specific to this project's coding standards.
tools
WordPress framework guardrails, patterns, and best practices for AI-assisted development. Use when working with WordPress projects, or when the user mentions WordPress. Provides theme development, plugin architecture, REST API, blocks, and security guidelines.
tools
Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs. Use when testing web apps, automating browser interactions, or debugging frontend issues.
tools
Suite of tools for creating elaborate, multi-component web applications using modern frontend technologies (React, Tailwind CSS, shadcn/ui). Use for complex projects requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX pages.