backend-go
SKILL.md
Backend Go Conventions
This skill provides specific conventions for Go backend development.
When to Use
- Use this skill when working on Go projects
- Use this skill when creating new handlers, services, or repositories
- This skill builds upon
project-standardsskill
Instructions
1. Project Layout (Standard Go Layout)
project/
├── cmd/
│ └── api/
│ └── main.go # Entry point
├── internal/
│ ├── handler/ # HTTP handlers (Controllers)
│ ├── service/ # Business logic
│ ├── repository/ # Database access
│ ├── model/ # Domain models
│ └── dto/ # Request/Response objects
├── pkg/ # Shared libraries
├── api/ # OpenAPI/Swagger specs
├── config/ # Configuration
└── go.mod
2. Architecture (Clean/Hexagonal)
- Handler (Controller): Receives requests, validates, calls Service.
- Service (Usecase): Contains Business Logic.
- Repository: Interacts with the Database.
Important: Interfaces should be defined where they are USED (Consumer), not where they are provided (Producer).
// In service package (consumer defines interface)
type UserRepository interface {
FindByID(ctx context.Context, id string) (*model.User, error)
Create(ctx context.Context, user *model.User) error
}
type UserService struct {
repo UserRepository
}
func NewUserService(repo UserRepository) *UserService {
return &UserService{repo: repo}
}
3. Error Handling
No Panic
Only panic during app setup failure (e.g., db disconnect).
// Bad - panicking in business logic
func GetUser(id string) User {
user, err := repo.Find(id)
if err != nil {
panic(err) // Never do this!
}
return user
}
// Good - return errors
func GetUser(id string) (User, error) {
user, err := repo.Find(id)
if err != nil {
return User{}, fmt.Errorf("get user %s: %w", id, err)
}
return user, nil
}
Wrap Errors
Use fmt.Errorf with %w to preserve context.
if err != nil {
return fmt.Errorf("create user failed: %w", err)
}
4. Naming Convention
- Package: Short, lowercase, no underscores (e.g.,
user,auth). - Interface: Ends with
er(e.g.,Reader,Writer,UserRepository). - Variable: camelCase, acronyms all uppercase (
userID,httpClient). - Exported: PascalCase for exported, camelCase for unexported.
// Good
package user
type Repository interface {
FindByID(ctx context.Context, id string) (*User, error)
}
var defaultTimeout = 30 * time.Second // unexported
var DefaultClient = &http.Client{} // exported
5. Context Handling
Always pass context as the first parameter.
func (s *UserService) GetUser(ctx context.Context, id string) (*User, error) {
// Check context cancellation
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
return s.repo.FindByID(ctx, id)
}
6. HTTP Handler Example
func (h *UserHandler) GetUser(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
user, err := h.service.GetUser(r.Context(), id)
if err != nil {
if errors.Is(err, ErrNotFound) {
h.respondError(w, http.StatusNotFound, "user not found")
return
}
h.respondError(w, http.StatusInternalServerError, "internal error")
return
}
h.respondJSON(w, http.StatusOK, Response{
Success: true,
Data: user,
})
}
7. Testing
Use table-driven tests.
func TestUserService_GetUser(t *testing.T) {
tests := []struct {
name string
id string
want *User
wantErr bool
}{
{
name: "valid user",
id: "123",
want: &User{ID: "123", Email: "test@example.com"},
},
{
name: "not found",
id: "999",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Test implementation
})
}
}
8. Dependencies
- Framework: Gin or Echo
- ORM: GORM or SQLC
- Config: Viper
- Logger: Zap or Zerolog
Weekly Installs
2
Repository
teodevlor/agent…it-skillFirst Seen
Jan 26, 2026
Security Audits
Installed on
cline2
gemini-cli2
codex2
cursor2
mcpjam1
openhands1