본문으로 건너뛰기
cccskills
무료GitHub에 공개

golang-web

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.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

연동 도구RedisOpenAI
설치 방법 보기

포함된 파일(18)

  • SKILL.md10.9 KB
  • reference/architecture.md12.2 KB
  • reference/patterns.md11.8 KB
  • reference/tech-stack.md10.4 KB
  • templates/project/cmd/myapp/main.go1.3 KB
  • templates/project/config.yaml582 B
  • templates/project/configs/config.go2.1 KB
  • templates/project/go.mod235 B
  • templates/project/internal/handlers/user.go1.7 KB
  • templates/project/internal/models/user.go496 B
  • templates/project/internal/repositories/user.go1.6 KB
  • templates/project/internal/router/router.go823 B
  • templates/project/internal/services/user.go2.9 KB
  • templates/project/Makefile2.1 KB
  • templates/project/pkg/database/database.go1.5 KB
  • templates/project/pkg/errors/errors.go1.7 KB
  • templates/project/pkg/response/response.go1.9 KB
  • templates/project/pkg/server/server.go2.1 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Go Web Architecture

Core Principles

  • Standard layout — Follow cmd/internal/pkg convention
  • Explicit dependencies — Wire dependencies in main.go, no globals
  • Interface-driven — Define interfaces where you use them, not where you implement
  • Error wrapping — Wrap errors with context, use error codes
  • No backwards compatibility — Delete, don't deprecate. Change directly
  • LiteLLM for LLM APIs — Use LiteLLM proxy for all LLM integrations

No Backwards Compatibility

Delete unused code. Change directly. No compatibility layers.

// ❌ 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 { ... }

LiteLLM for LLM APIs

Use LiteLLM proxy. Don't call provider APIs directly.

// 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)
}

Quick Start

1. Initialize Project

mkdir 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

2. Apply Tech Stack

LayerRecommendation
HTTP FrameworkGin / Chi / Echo
ConfigurationViper
LoggingLogrus / Zap / Slog
Database ORMGORM / sqlx / sqlc
Validationgo-playground/validator
Testingtestify / go test

Version Strategy

Always get latest. Never pin in templates.

# Always fetch latest
go get -u github.com/gin-gonic/gin
go get -u ./...

# go.mod handles version locking
# go.sum ensures reproducible builds

3. Use Standard Structure

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.mod

Architecture Layers

cmd/ — Entry Point

Wire all dependencies here. No business logic.

// 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)
}

internal/ — Private Business Code

handlers/ — HTTP Layer

// 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)
}

services/ — Business Logic

// 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)
}

repositories/ — Data Access

// 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
}

pkg/ — Reusable Packages

errors/ — Error Handling

// 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")
)

response/ — Unified Response

// 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",
    })
}

Configuration

Viper + YAML + Environment Variables

// 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
}

Graceful Shutdown

// 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

Checklist

## 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

See Also

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Audit and organize AI coding-agent instructions, including AGENTS.md, CLAUDE.md, skills, and agent definitions. Use when the user asks to diagnose conflicting instructions, excessive approval pauses, broad skill triggers, duplicate guidance, or broken instruction references, or to clean up that instruction set. Ordinary code review, Markdown editing, or application debugging alone does not trigger this workflow. Inspection stays read-only; apply changes when authorized.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

majiayu000/spellbook2882026년 10월 22일 업데이트

Generate or update repository-specific AGENTS.md instruction files from real repo evidence. Use when asked to create, design, scaffold, split, or improve root or scoped AGENTS.md files for Codex/Claude/agent workflows, especially when a repo needs directory-specific rules, validation commands, generated-file boundaries, or a short agent onboarding router.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

majiayu000/spellbook2882026년 10월 22일 업데이트

REST/GraphQL/gRPC API design best practices. Use when designing APIs, defining contracts, handling versioning. Covers OpenAPI 3.2, GraphQL Federation, gRPC streaming.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

majiayu000/spellbook2882026년 10월 22일 업데이트

Mobile app UI design expert for iOS and Android. Use when designing app interfaces, creating design systems, ensuring accessibility, or following platform guidelines. Covers Material Design 3, Human Interface Guidelines, color theory, typography, and 2025 trends.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

majiayu000/spellbook2882026년 10월 22일 업데이트

End-to-end app feature inventory and user-story testing workflow with a canonical tracker. Use when the user asks to audit every feature, derive expected behavior from code, test user journeys, or explicitly fix and retest documented UX or logistical defects.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

majiayu000/spellbook2882026년 10월 22일 업데이트

Design architecture foundations before implementation. Use when asked to design or refactor architecture, choose Rust/Go crate, package, module, runtime, workflow, or service boundaries, compare mature project architecture, prevent stacked one-off PRs, audit migration debt in existing repos, or produce a spec before coding.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

majiayu000/spellbook2882026년 10월 22일 업데이트

majiayu000의 스킬 모두 보기

이 스킬의 문제 신고