一键导入
golang
Golang backend development skill for REST APIs with Chi router, PostgreSQL, and layered architecture. Use when implementing Go backend code.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Golang backend development skill for REST APIs with Chi router, PostgreSQL, and layered architecture. Use when implementing Go backend code.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
Node.js + Express + MongoDB backend development for MEAN stack apps. Use when implementing server code: models, routes, controllers, middleware, config, and database connections.
Code review for MEAN stack apps. Use when reviewing backend (Express + MongoDB) and frontend (React) code for quality, security, architecture compliance, performance, and best practices.
Express.js API architecture for MEAN stack apps. Use when designing REST endpoints, middleware chains, authentication flows, error handling contracts, and request/response schemas.
React frontend development for MEAN stack apps. Use when implementing React components, pages, hooks, API integration, routing, state management, and responsive UI.
MongoDB database architecture for MEAN stack apps. Use when designing collections, schemas, indexes, relationships, and data modeling with Mongoose ODM.
Product management for MEAN stack apps. Use when writing PRDs, defining features, acceptance criteria, or reviewing deliverables for MongoDB + Express + React + Node.js projects.
基于 SOC 职业分类
| name | golang |
| description | Golang backend development skill for REST APIs with Chi router, PostgreSQL, and layered architecture. Use when implementing Go backend code. |
This skill provides the knowledge and conventions for building production-grade Go REST APIs. It covers project structure, layered architecture (Handler -> Service -> Repository), database access with PostgreSQL, routing with Chi, and all supporting concerns (config, logging, error handling, validation, graceful shutdown). The go-dev agent uses this skill to implement backend API code that integrates with a React frontend.
project-root/
cmd/
server/
main.go # Entry point: config load, DI wiring, server start, graceful shutdown
internal/
config/
config.go # Struct with env tags, Load() function
handler/
health.go # Health check handler
{resource}.go # HTTP handlers per resource (one file per domain entity)
response.go # Shared JSON response helpers (Success, Error, Paginated)
service/
{resource}.go # Business logic per resource
repository/
{resource}.go # Database queries per resource
model/
{resource}.go # Domain structs, DB models
errors.go # Custom error types
middleware/
auth.go # JWT/session auth middleware
cors.go # CORS configuration
logging.go # Request logging middleware
recovery.go # Panic recovery middleware
request_id.go # Request ID injection
router/
router.go # Chi router setup, route registration, middleware chain
migrations/
000001_initial.up.sql # golang-migrate format: {sequence}_{name}.{direction}.sql
000001_initial.down.sql
sqlc/ # Optional: sqlc configuration and generated code
sqlc.yaml
query/
{resource}.sql
db/ # Generated code (do not edit)
go.mod
go.sum
Dockerfile
.env.example
The codebase follows strict layering. Dependencies flow inward only:
HTTP Request
-> Middleware (auth, logging, CORS, recovery)
-> Handler (parse request, validate, call service, write response)
-> Service (business logic, orchestrate repos, enforce rules)
-> Repository (database queries, no business logic)
-> PostgreSQL
Rules:
Every layer uses constructor injection. No global state, no init() for wiring.
// repository/user.go
type UserRepository struct {
db *pgxpool.Pool
}
func NewUserRepository(db *pgxpool.Pool) *UserRepository {
return &UserRepository{db: db}
}
// service/user.go
type UserService struct {
repo *repository.UserRepository
}
func NewUserService(repo *repository.UserRepository) *UserService {
return &UserService{repo: repo}
}
// handler/user.go
type UserHandler struct {
svc *service.UserService
}
func NewUserHandler(svc *service.UserService) *UserHandler {
return &UserHandler{svc: svc}
}
Wiring happens in cmd/server/main.go:
db := mustConnectDB(cfg)
userRepo := repository.NewUserRepository(db)
userSvc := service.NewUserService(userRepo)
userHandler := handler.NewUserHandler(userSvc)
r := router.New(userHandler, ...)
Chi is the preferred HTTP router. It is stdlib-compatible (net/http handlers).
package router
import (
"github.com/go-chi/chi/v5"
chimw "github.com/go-chi/chi/v5/middleware"
)
func New(userH *handler.UserHandler) chi.Router {
r := chi.NewRouter()
// Global middleware stack (order matters)
r.Use(chimw.RequestID)
r.Use(chimw.RealIP)
r.Use(middleware.Logger) // custom structured logging
r.Use(middleware.Recovery) // panic recovery
r.Use(middleware.CORS) // CORS for React frontend
r.Use(chimw.Timeout(30 * time.Second))
// Health check (no auth)
r.Get("/healthz", handler.HealthCheck)
// API routes
r.Route("/api/v1", func(r chi.Router) {
// Public routes
r.Post("/auth/login", authH.Login)
r.Post("/auth/register", authH.Register)
// Protected routes
r.Group(func(r chi.Router) {
r.Use(middleware.Auth)
r.Route("/users", func(r chi.Router) {
r.Get("/", userH.List)
r.Post("/", userH.Create)
r.Route("/{id}", func(r chi.Router) {
r.Get("/", userH.GetByID)
r.Put("/", userH.Update)
r.Delete("/", userH.Delete)
})
})
})
})
return r
}
Use pgxpool for connection pooling. Never use database/sql directly for PostgreSQL.
import "github.com/jackc/pgx/v5/pgxpool"
func ConnectDB(ctx context.Context, databaseURL string) (*pgxpool.Pool, error) {
config, err := pgxpool.ParseConfig(databaseURL)
if err != nil {
return nil, fmt.Errorf("parsing database URL: %w", err)
}
config.MaxConns = 25
config.MinConns = 5
config.MaxConnLifetime = time.Hour
pool, err := pgxpool.NewWithConfig(ctx, config)
if err != nil {
return nil, fmt.Errorf("creating connection pool: %w", err)
}
if err := pool.Ping(ctx); err != nil {
return nil, fmt.Errorf("pinging database: %w", err)
}
return pool, nil
}
Query option A: sqlc (preferred for type safety)
Define SQL in .sql files, generate Go code:
-- sqlc/query/user.sql
-- name: GetUserByID :one
SELECT id, email, name, created_at FROM users WHERE id = $1;
-- name: ListUsers :many
SELECT id, email, name, created_at FROM users ORDER BY created_at DESC LIMIT $1 OFFSET $2;
-- name: CreateUser :one
INSERT INTO users (email, name, password_hash) VALUES ($1, $2, $3) RETURNING id, email, name, created_at;
Query option B: GORM
Use only when project already has GORM or PRD specifies it. Prefer sqlc/pgx otherwise.
Use golang-migrate with file-based migrations.
migrate create -ext sql -dir migrations -seq {name}
migrate -path migrations -database "$DATABASE_URL" up
migrate -path migrations -database "$DATABASE_URL" down 1
Migration files are pure SQL. One up file, one down file per migration. Down migrations must be reversible.
Define domain error types in internal/model/errors.go:
package model
import "errors"
var (
ErrNotFound = errors.New("resource not found")
ErrConflict = errors.New("resource already exists")
ErrUnauthorized = errors.New("unauthorized")
ErrForbidden = errors.New("forbidden")
ErrBadRequest = errors.New("bad request")
ErrInternal = errors.New("internal server error")
)
// ValidationError carries field-level validation details.
type ValidationError struct {
Field string `json:"field"`
Message string `json:"message"`
}
type ValidationErrors []ValidationError
func (ve ValidationErrors) Error() string {
return fmt.Sprintf("%d validation error(s)", len(ve))
}
Always wrap errors with context using fmt.Errorf("doing thing: %w", err). Handlers map domain errors to HTTP status codes:
func mapErrorToStatus(err error) int {
switch {
case errors.Is(err, model.ErrNotFound):
return http.StatusNotFound
case errors.Is(err, model.ErrConflict):
return http.StatusConflict
case errors.Is(err, model.ErrUnauthorized):
return http.StatusUnauthorized
case errors.Is(err, model.ErrForbidden):
return http.StatusForbidden
case errors.Is(err, model.ErrBadRequest):
return http.StatusBadRequest
default:
return http.StatusInternalServerError
}
}
Use environment variables. Load with envconfig or viper.
package config
import "github.com/kelseyhightower/envconfig"
type Config struct {
Port int `envconfig:"PORT" default:"8080"`
DatabaseURL string `envconfig:"DATABASE_URL" required:"true"`
JWTSecret string `envconfig:"JWT_SECRET" required:"true"`
CORSOrigins string `envconfig:"CORS_ORIGINS" default:"http://localhost:3000"`
LogLevel string `envconfig:"LOG_LEVEL" default:"info"`
Environment string `envconfig:"ENVIRONMENT" default:"development"`
}
func Load() (*Config, error) {
var cfg Config
if err := envconfig.Process("", &cfg); err != nil {
return nil, fmt.Errorf("loading config: %w", err)
}
return &cfg, nil
}
Use dedicated request and response structs. Never bind directly to domain models.
// handler/user.go
type CreateUserRequest struct {
Email string `json:"email" validate:"required,email"`
Name string `json:"name" validate:"required,min=2,max=100"`
Password string `json:"password" validate:"required,min=8"`
}
type UserResponse struct {
ID string `json:"id"`
Email string `json:"email"`
Name string `json:"name"`
CreatedAt time.Time `json:"created_at"`
}
Validate with go-playground/validator:
import "github.com/go-playground/validator/v10"
var validate = validator.New()
func decodeAndValidate[T any](r *http.Request) (T, error) {
var req T
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return req, fmt.Errorf("%w: %s", model.ErrBadRequest, err.Error())
}
if err := validate.Struct(req); err != nil {
return req, toValidationErrors(err)
}
return req, nil
}
Use log/slog (stdlib, Go 1.21+). No external logging libraries needed.
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
slog.SetDefault(logger)
slog.Info("server starting", "port", cfg.Port, "env", cfg.Environment)
slog.Error("query failed", "err", err, "user_id", userID)
func main() {
// ... setup ...
srv := &http.Server{
Addr: fmt.Sprintf(":%d", cfg.Port),
Handler: r,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
go func() {
slog.Info("server listening", "addr", srv.Addr)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
slog.Error("server error", "err", err)
os.Exit(1)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
slog.Info("shutting down server")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
slog.Error("server shutdown error", "err", err)
}
pool.Close()
slog.Info("server stopped")
}
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /server ./cmd/server
FROM alpine:3.19
RUN apk --no-cache add ca-certificates
WORKDIR /app
COPY --from=builder /server .
COPY migrations/ ./migrations/
EXPOSE 8080
CMD ["./server"]
handler not handlers.user_repository.go.UserReader, Authenticator.NewXxx pattern.ErrXxx pattern.ctx.github.com/{org}/{repo}.go mod tidy after adding dependencies.go get package@latest for updates.Run golangci-lint run ./... before reporting. Key linters:
errcheck: all errors must be handled.govet: catches common mistakes.staticcheck: advanced static analysis.unused: no dead code.{name}_test.go in same package.testify/assert or stdlib testing only.pgxmock.httptest.NewRecorder() and httptest.NewRequest().// FunctionName does X. (starts with the name).doc.go or the primary file).references/. Changes to SKILL.md or scripts/ require user approval.