Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Modern Go Web application architecture guide. Use when creating new Go web projects, APIs, or microservices. Covers project structure, tech stack selection, and best practices based on Go standards.
.claude/skills/majiayu000-golang-web/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 35% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 85% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 58% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 81% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 219% | 0% |
> Delete unused code. Change directly. No compatibility layers.
go// ❌ BAD: Deprecated function kept around // Deprecated: Use NewUserService instead func CreateUserService() *UserService { ... } // ❌ BAD: Alias for renamed types type OldName = NewName // "for backwards compatibility" // ❌ BAD: Unused parameters func Process(_ context.Context, data Data) { ... } // ✅ GOOD: Just delete and update all usages func NewUserService(repo UserRepository) *UserService { ... }
> Use LiteLLM proxy. Don't call provider APIs directly.
go// adapters/llm/client.go package llm import ( "github.com/sashabaranov/go-openai" ) // Connect to LiteLLM proxy using OpenAI-compatible SDK func NewClient(cfg Config) *openai.Client { config := openai.DefaultConfig(cfg.APIKey) config.BaseURL = cfg.BaseURL // LiteLLM proxy URL return openai.NewClientWithConfig(config) }
bashmkdir myapp && cd myapp go mod init github.com/yourname/myapp # Install core dependencies go get github.com/gin-gonic/gin go get github.com/spf13/viper go get github.com/sirupsen/logrus go get gorm.io/gorm
| Layer | Recommendation | |-------|----------------| | HTTP Framework | Gin / Chi / Echo | | Configuration | Viper | | Logging | Logrus / Zap / Slog | | Database ORM | GORM / sqlx / sqlc | | Validation | go-playground/validator | | Testing | testify / go test |
> Always get latest. Never pin in templates.
bash# Always fetch latest go get -u github.com/gin-gonic/gin go get -u ./... # go.mod handles version locking # go.sum ensures reproducible builds
myapp/
├── cmd/
│ └── myapp/
│ └── main.go # Entry point, dependency wiring
├── configs/
│ └── config.go # Configuration struct + loader
├── internal/ # Private application code
│ ├── handlers/ # HTTP handlers
│ ├── services/ # Business logic
│ ├── repositories/ # Data access
│ ├── models/ # Domain models
│ ├── middleware/ # HTTP middleware
│ └── router/ # Route definitions
├── pkg/ # Public reusable packages
│ ├── errors/ # Error types
│ ├── logger/ # Logging setup
│ ├── response/ # Unified response format
│ └── database/ # Database connection
├── config.yaml # Configuration file
├── Makefile # Build automation
├── Dockerfile
└── go.modWire all dependencies here. No business logic.
go// cmd/myapp/main.go func main() { // Load config cfg := configs.Load() // Initialize infrastructure db := database.New(cfg.Database) cache := cache.New(cfg.Redis) logger := logger.New(cfg.Log) // Initialize repositories userRepo := repositories.NewUserRepository(db) // Initialize services userService := services.NewUserService(userRepo) // Initialize handlers userHandler := handlers.NewUserHandler(userService) // Setup router r := router.Setup(cfg, userHandler) // Start server with graceful shutdown server.Run(r, cfg.Server) }
go// internal/handlers/user.go type UserHandler struct { service services.UserService } func NewUserHandler(s services.UserService) *UserHandler { return &UserHandler{service: s} } func (h *UserHandler) Create(c *gin.Context) { var input CreateUserInput if err := c.ShouldBindJSON(&input); err != nil { response.Error(c, errors.ErrInvalidParams) return } user, err := h.service.Create(c.Request.Context(), input) if err != nil { response.Error(c, err) return } response.Success(c, user) }
go// internal/services/user.go type UserService interface { Create(ctx context.Context, input CreateUserInput) (*models.User, error) GetByID(ctx context.Context, id string) (*models.User, error) } type userService struct { repo repositories.UserRepository } func NewUserService(repo repositories.UserRepository) UserService { return &userService{repo: repo} } func (s *userService) Create(ctx context.Context, input CreateUserInput) (*models.User, error) { existing, _ := s.repo.FindByEmail(ctx, input.Email) if existing != nil { return nil, errors.ErrUserExists } user := &models.User{ ID: uuid.New().String(), Email: input.Email, Name: input.Name, } return s.repo.Save(ctx, user) }
go// internal/repositories/user.go type UserRepository interface { FindByID(ctx context.Context, id string) (*models.User, error) FindByEmail(ctx context.Context, email string) (*models.User, error) Save(ctx context.Context, user *models.User) (*models.User, error) Delete(ctx context.Context, id string) error } type userRepository struct { db *gorm.DB } func NewUserRepository(db *gorm.DB) UserRepository { return &userRepository{db: db} } func (r *userRepository) FindByID(ctx context.Context, id string) (*models.User, error) { var user models.User if err := r.db.WithContext(ctx).First(&user, "id = ?", id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, nil } return nil, err } return &user, nil }
go// pkg/errors/errors.go type AppError struct { Code int `json:"code"` Message string `json:"message"` Cause error `json:"-"` } func (e *AppError) Error() string { return e.Message } func (e *AppError) Unwrap() error { return e.Cause } func New(code int, message string) *AppError { return &AppError{Code: code, Message: message} } func Wrap(err error, code int, message string) *AppError { return &AppError{Code: code, Message: message, Cause: err} } // Predefined errors var ( ErrInternal = New(500, "internal server error") ErrInvalidParams = New(400, "invalid parameters") ErrNotFound = New(404, "resource not found") ErrUnauthorized = New(401, "unauthorized") ErrUserExists = New(409, "user already exists") )
go// pkg/response/response.go type Response struct { Code int `json:"code"` Message string `json:"message"` Data interface{} `json:"data,omitempty"` } func Success(c *gin.Context, data interface{}) { c.JSON(http.StatusOK, Response{ Code: 0, Message: "success", Data: data, }) } func Error(c *gin.Context, err error) { var appErr *errors.AppError if errors.As(err, &appErr) { c.JSON(appErr.Code/100, Response{ Code: appErr.Code, Message: appErr.Message, }) return } c.JSON(http.StatusInternalServerError, Response{ Code: 500, Message: "internal server error", }) }
go// configs/config.go type Config struct { Server ServerConfig `mapstructure:"server"` Database DatabaseConfig `mapstructure:"database"` Redis RedisConfig `mapstructure:"redis"` Log LogConfig `mapstructure:"log"` LLM LLMConfig `mapstructure:"llm"` } type LLMConfig struct { BaseURL string `mapstructure:"base_url"` APIKey string `mapstructure:"api_key"` DefaultModel string `mapstructure:"default_model"` } func Load() *Config { viper.SetConfigFile("config.yaml") viper.AutomaticEnv() viper.SetEnvPrefix("APP") viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) // Defaults viper.SetDefault("server.port", 8080) viper.SetDefault("llm.base_url", "http://localhost:4000") viper.SetDefault("llm.default_model", "gpt-4o") viper.ReadInConfig() var cfg Config viper.Unmarshal(&cfg) return &cfg }
go// pkg/server/server.go func Run(handler http.Handler, cfg ServerConfig) { srv := &http.Server{ Addr: fmt.Sprintf(":%d", cfg.Port), Handler: handler, ReadTimeout: cfg.ReadTimeout, WriteTimeout: cfg.WriteTimeout, } go func() { if err := srv.ListenAndServe(); err != http.ErrServerClosed { log.Fatalf("Server error: %v", err) } }() quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() srv.Shutdown(ctx) }
makefile.PHONY: build run test lint clean APP_NAME=myapp build: go build -o bin/$(APP_NAME) ./cmd/$(APP_NAME) run: go run ./cmd/$(APP_NAME) dev: air test: go test -v ./... lint: golangci-lint run clean: rm -rf bin/ tidy: go mod tidy upgrade: go get -u ./... go mod tidy
markdown## Project Setup - [ ] Go 1.21+ installed - [ ] Standard directory structure (cmd/internal/pkg) - [ ] go.mod initialized - [ ] Makefile created ## Architecture - [ ] Dependencies wired in main.go - [ ] Handlers → Services → Repositories layers - [ ] Interfaces defined at usage site - [ ] No circular dependencies ## Infrastructure - [ ] Configuration with Viper - [ ] Structured logging - [ ] Custom error types - [ ] Unified response format - [ ] Graceful shutdown ## Quality - [ ] Tests for services - [ ] golangci-lint configured - [ ] go vet passes - [ ] Race detection tested
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-14 | fail→fail | 13,201 | 7,400 | -44% | 1 | 1 | 0% | 2,059 | 4,466 | +117% | 0 | 0 | — |
case-01 | fail→pass | 24,664 | 18,782 | -24% | 1 | 1 | 0% | 5,059 | 6,831 | +35% | 0 | 0 | — |
case-02 | fail→pass | 17,336 | 16,694 | -4% | 1 | 1 | 0% | 3,645 | 6,749 | +85% | 0 | 0 | — |
case-03 | fail→pass | 15,795 | 4,364 | -72% | 1 | 1 | 0% | 2,388 | 3,763 | +58% | 0 | 0 | — |
case-04 | fail→pass | 15,290 | 10,457 | -32% | 1 | 1 | 0% | 2,810 | 5,084 | +81% | 0 | 0 | — |
case-05 | pass→pass | 15,893 | 9,789 | -38% | 1 | 1 | 0% | 2,917 | 5,003 | +72% | 0 | 0 | — |
case-06 | fail→pass | 7,247 | 4,409 | -39% | 1 | 1 | 0% | 1,167 | 3,727 | +219% | 0 | 0 | — |
case-07 | fail→pass | 17,926 | 15,013 | -16% | 1 | 1 | 0% | 3,637 | 5,499 | +51% | 0 | 0 | — |
case-08 | fail→pass | 17,458 | 19,549 | +12% | 1 | 1 | 0% | 3,400 | 7,019 | +106% | 0 | 0 | — |
case-09 | fail→pass | 16,244 | 11,987 | -26% | 1 | 1 | 0% | 2,954 | 5,474 | +85% | 0 | 0 | — |
case-10 | pass→pass | 13,552 | 11,293 | -17% | 1 | 1 | 0% | 2,425 | 5,293 | +118% | 0 | 0 | — |
case-11 | pass→pass | 13,763 | 13,962 | +1% | 1 | 1 | 0% | 2,252 | 5,375 | +139% | 0 | 0 | — |
case-12 | fail→pass | 11,300 | 7,146 | -37% | 1 | 1 | 0% | 1,923 | 4,294 | +123% | 0 | 0 | — |
case-13 | fail→pass | 14,190 | 8,178 | -42% | 1 | 1 | 0% | 2,809 | 4,758 | +69% | 0 | 0 | — |
case-15 | fail→pass | 15,674 | 8,222 | -48% | 1 | 1 | 0% | 2,421 | 4,505 | +86% | 0 | 0 | — |
case-16 | pass→pass | 14,869 | 13,357 | -10% | 1 | 1 | 0% | 2,858 | 5,786 | +102% | 0 | 0 | — |
case-17 | fail→pass | 16,075 | 11,580 | -28% | 1 | 1 | 0% | 2,694 | 4,930 | +83% | 0 | 0 | — |
case-18 | fail→pass | 14,445 | 6,612 | -54% | 1 | 1 | 0% | 2,172 | 4,079 | +88% | 0 | 0 | — |
case-19 | fail→pass | 11,978 | 7,699 | -36% | 1 | 1 | 0% | 2,249 | 4,518 | +101% | 0 | 0 | — |
case-20 | pass→pass | 10,304 | 9,855 | -4% | 1 | 1 | 0% | 1,813 | 4,417 | +144% | 0 | 0 | — |
case-21 | pass→pass | 8,454 | 12,645 | +50% | 1 | 1 | 0% | 1,728 | 5,507 | +219% | 0 | 0 | — |
case-22 | pass→pass | 6,107 | 7,605 | +25% | 1 | 1 | 0% | 1,167 | 4,189 | +259% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +64 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.