Skip to content
Back to skills

Tpl Backend Go Chi Gorm

ASecurity

Template do pack (backend/04-go-chi-gorm.md). Orienta o agente em APIs, servicos e arquitetura backend alinhado a esse contexto.

  • 11 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 8, 2026
databasesgosqlapidatabasebackend

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 8, 2026

npx -y skills add artubss/SKILLS-CLAUDE-CODE --skill tpl-backend-go-chi-gorm --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Tpl Backend Go Chi Gorm?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Tpl Backend Go Chi Gorm
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/artubss-tpl-backend-go-chi-gorm/badge)](https://www.skillsdirectory.com/skills/artubss-tpl-backend-go-chi-gorm)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: tpl-backend-go-chi-gorm
description: Template do pack (backend/04-go-chi-gorm.md). Orienta o agente em APIs, servicos e arquitetura backend alinhado a esse contexto.
metadata:
  version: 1.0.0
  source_template: backend/04-go-chi-gorm.md
  generated_by: install_pack_templates_as_claude_skills
---

# PROJECT: Go REST API (Chi + GORM)

Skill gerado a partir do pack `templates-claude-code`. Arquivo de origem: `backend/04-go-chi-gorm.md`. Use como baseline e adapte ao projeto antes de mudancas grandes.

## Conteudo do template

## STACK

| Technology | Version | Purpose |
|------------|---------|---------|
| Go | 1.22+ | Language + runtime |
| Chi | v5 | HTTP router + middleware |
| GORM | v2 | ORM + migrations |
| PostgreSQL | 16 | Primary database |
| pgx/v5 | 5.x | PostgreSQL driver |
| golang-jwt/jwt | v5 | JWT tokens |
| bcrypt | stdlib | Password hashing |
| testify | v1.9+ | Assertions + mocking |
| slog | stdlib (1.21+) | Structured logging |
| golangci-lint | 1.58+ | Linting suite |
| godotenv | 1.5+ | .env loader for dev |

---

## PROJECT STRUCTURE

```
cmd/
└── api/
    └── main.go           # Entry point: setup + server.ListenAndServe
internal/
├── config/
│   └── config.go         # Config struct + Load() from env
├── server/
│   └── server.go         # Chi router setup, middleware chain, routes
├── handlers/
│   └── users/
│       ├── handler.go    # HTTP handler methods (thin, calls service)
│       └── handler_test.go
├── services/
│   └── users/
│       ├── service.go    # Business logic interface + implementation
│       └── service_test.go
├── repositories/
│   └── users/
│       ├── repository.go # GORM queries implementing interface
│       └── repository_test.go
├── models/
│   └── user.go           # GORM model structs
├── middleware/
│   ├── auth.go           # JWT verification middleware
│   ├── logger.go         # Request logging middleware
│   └── recover.go        # Panic recovery middleware
├── dto/
│   └── users/
│       ├── request.go    # Input structs with validate tags
│       └── response.go   # Output structs (no sensitive fields)
└── database/
    └── database.go       # GORM db connection + AutoMigrate
go.mod
go.sum
.golangci.yml
Makefile
```

---

## ARCHITECTURE RULES

- **Handler → Service → Repository** — never skip levels
- Define interfaces in the **consumer** package (service defines its repo interface)
- Handlers receive `*Handler` with service interface injected via constructor
- Services receive `Repository` interface — not the concrete struct
- No global state — pass dependencies explicitly (no `db` package-level variable)
- All exported functions have godoc comments (`// FunctionName does X`)
- JSON tags on all struct fields — never rely on Go's default capitalization
- Time fields: always `time.Time` with `json:"created_at"` (UTC, no timezone stored)

---

## GO-SPECIFIC RULES

### Error Handling Pattern

**Rule:** Never ignore errors. Wrap with context. Return early on error.

```go
// Good — wrap errors with context
func (s *UserService) GetByID(ctx context.Context, id string) (*models.User, error) {
    user, err := s.repo.FindByID(ctx, id)
    if err != nil {
        if errors.Is(err, gorm.ErrRecordNotFound) {
            return nil, ErrUserNotFound // sentinel error
        }
        return nil, fmt.Errorf("UserService.GetByID: %w", err)
    }
    return user, nil
}

// Handler (convert service errors to HTTP responses)
func (h *UserHandler) GetUser(w http.ResponseWriter, r *http.Request) {
    id := chi.URLParam(r, "id")
    user, err := h.svc.GetByID(r.Context(), id)
    if err != nil {
        if errors.Is(err, service.ErrUserNotFound) {
            respondError(w, http.StatusNotFound, "user not found")
            return
        }
        slog.ErrorContext(r.Context(), "failed to get user", "error", err, "id", id)
        respondError(w, http.StatusInternalServerError, "internal error")
        return
    }
    respondJSON(w, http.StatusOK, toUserResponse(user))
}
```

### Interface Pattern

```go
// internal/repositories/users/repository.go
type UserRepository interface {
    FindByID(ctx context.Context, id string) (*models.User, error)
    FindByEmail(ctx context.Context, email string) (*models.User, error)
    Create(ctx context.Context, user *models.User) error
    Update(ctx context.Context, user *models.User) error
    Delete(ctx context.Context, id string) error
}
```

### Goroutine Safety Rules

- **Never** share mutable state between goroutines without a mutex or channel
- Use `context.Context` for cancellation — always accept `ctx context.Context` as first param
- Goroutines spawned in handlers must be tracked (use `errgroup.Group` or waitgroup)
- Database operations: GORM's `*gorm.DB` is safe for concurrent use (connection pool)
- Never close over loop variables in goroutines — capture explicitly

### Chi Middleware Pattern

```go
// internal/server/server.go
func NewRouter(cfg *config.Config, userHandler *userhandler.Handler) *chi.Mux {
    r := chi.NewRouter()

    // Global middlewares (order matters)
    r.Use(middleware.RequestID)
    r.Use(middleware.RealIP)
    r.Use(loggerMiddleware.New(slog.Default()))
    r.Use(recoverMiddleware.New())
    r.Use(chimiddleware.Timeout(30 * time.Second))

    r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
    })

    r.Route("/api/v1", func(r chi.Router) {
        r.Post("/auth/login", userHandler.Login)
        r.Post("/auth/register", userHandler.Register)

        r.Group(func(r chi.Router) {
            r.Use(authmiddleware.Authenticate(cfg.JWTSecret))
            r.Get("/users/me", userHandler.Me)
            r.Patch("/users/me", userHandler.UpdateMe)
        })
    })

    return r
}
```

### JWT Auth Middleware

```go
// internal/middleware/auth.go
func Authenticate(secret string) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            header := r.Header.Get("Authorization")
            if !strings.HasPrefix(header, "Bearer ") {
                http.Error(w, `{"error":"unauthorized"}`, http.StatusUnauthorized)
                return
            }
            tokenStr := header[7:]
            token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
                if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
                    return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
                }
                return []byte(secret), nil
            })
            if err != nil || !token.Valid {
                http.Error(w, `{"error":"invalid or expired token"}`, http.StatusUnauthorized)
                return
            }
            claims := token.Claims.(jwt.MapClaims)
            ctx := context.WithValue(r.Context(), ctxKeyUserID, claims["sub"])
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}
```

### GORM Hooks

```go
// internal/models/user.go
func (u *User) BeforeCreate(tx *gorm.DB) error {
    u.ID = uuid.New().String()
    hashed, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
    if err != nil {
        return fmt.Errorf("failed to hash password: %w", err)
    }
    u.Password = string(hashed)
    return nil
}
```

---

## ROUTING TABLE

| Trigger | Method | Route | Middleware | Handler |
|---------|--------|-------|-----------|---------|
| Register | POST | `/api/v1/auth/register` | — | UserHandler.Register |
| Login | POST | `/api/v1/auth/login` | — | UserHandler.Login |
| Refresh token | POST | `/api/v1/auth/refresh` | — | UserHandler.Refresh |
| Get my profile | GET | `/api/v1/users/me` | Authenticate | UserHandler.Me |
| Update profile | PATCH | `/api/v1/users/me` | Authenticate | UserHandler.UpdateMe |
| List users | GET | `/api/v1/users` | Authenticate, RequireAdmin | UserHandler.List |
| Get user | GET | `/api/v1/users/{id}` | Authenticate, RequireAdmin | UserHandler.Get |
| Delete user | DELETE | `/api/v1/users/{id}` | Authenticate, RequireAdmin | UserHandler.Delete |
| Health check | GET | `/health` | — | inline |

---

## QUALITY GATES

Before opening a PR, verify ALL of the following:

- [ ] `golangci-lint run ./...` passes with zero warnings
- [ ] `go test ./... -race -count=1` passes — no race conditions
- [ ] `go vet ./...` clean
- [ ] Coverage: `go test ./... -coverprofile=cover.out` ≥ 75%
- [ ] `go build ./cmd/api/...` compiles without warnings
- [ ] No `interface{}` — use `any` (Go 1.18+) or specific types
- [ ] All context parameters are first argument: `func F(ctx context.Context, ...)`
- [ ] No `log.Fatal` outside of `main.go`
- [ ] Database errors wrapped with context: `fmt.Errorf("repo.FindByID: %w", err)`
- [ ] Struct fields have `json` tags — no default field name exposure

---

## FORBIDDEN

- **NEVER** use `panic()` for error handling — only for truly unrecoverable init failures
- **NEVER** use package-level `db` variables — inject via constructor
- **NEVER** use `interface{}` as a shortcut — define real types
- **NEVER** ignore returned errors: `_, _ = someFunc()` is forbidden
- **NEVER** use `time.Sleep` inside request handlers — use context deadlines
- **NEVER** log secrets, tokens, or passwords even in debug logs
- **NEVER** use `http.DefaultClient` for external requests (no timeout) — configure a client
- **NEVER** store pointers to loop variables in goroutines — capture by value
- **NEVER** use `os.Exit` outside `main.go`
- **NEVER** skip `-race` flag in CI test runs

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

View all in databases →

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.