| name | system-design |
| description | System design and code review skill for fullstack Go+React apps. Use when designing architecture or reviewing code against design specifications. |
System Design
Purpose
This skill provides the knowledge base for designing and reviewing fullstack applications built with Go (backend) and React (frontend) with PostgreSQL as the data layer. It covers two operating modes:
- Design Mode (Phase 1): Produce a complete system design document from a PRD -- API contracts, database schema, component architecture, and Go project structure.
- Review Mode (Phase 4): Verify implemented code conforms to the design document, flagging deviations, anti-patterns, and missing pieces.
Key Patterns
1. REST API Design (OpenAPI/Swagger)
Endpoint Naming Conventions:
- Use plural nouns for resource collections:
/api/v1/users, /api/v1/projects
- Use path parameters for specific resources:
/api/v1/users/{id}
- Nest related resources max 2 levels deep:
/api/v1/projects/{id}/tasks
- Use query parameters for filtering, sorting, pagination:
?status=active&sort=created_at&order=desc&page=1&limit=20
- API versioning in path prefix:
/api/v1/, /api/v2/
- Use kebab-case for multi-word paths:
/api/v1/user-profiles
HTTP Methods:
| Method | Purpose | Idempotent | Request Body | Success Code |
|---|
| GET | Retrieve resource(s) | Yes | No | 200 |
| POST | Create resource | No | Yes | 201 |
| PUT | Full replace | Yes | Yes | 200 |
| PATCH | Partial update | Yes | Yes | 200 |
| DELETE | Remove resource | Yes | No | 204 |
Standard Status Codes:
200 OK -- Successful GET, PUT, PATCH
201 Created -- Successful POST (include Location header)
204 No Content -- Successful DELETE
400 Bad Request -- Validation errors, malformed input
401 Unauthorized -- Missing or invalid authentication
403 Forbidden -- Authenticated but not authorized
404 Not Found -- Resource does not exist
409 Conflict -- Duplicate resource, version conflict
422 Unprocessable Entity -- Semantically invalid input
500 Internal Server Error -- Unexpected server failure
Standard Error Response Schema:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Human-readable description",
"details": [
{
"field": "email",
"message": "must be a valid email address"
}
]
}
}
Pagination Response Envelope:
{
"data": [...],
"pagination": {
"page": 1,
"limit": 20,
"total": 142,
"total_pages": 8
}
}
2. Database Schema Design (PostgreSQL)
Table Conventions:
- Table names: plural, snake_case (
users, project_tasks)
- Column names: snake_case (
created_at, user_id)
- Every table MUST have:
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
- Soft delete via
deleted_at TIMESTAMPTZ column (nullable, NULL = active)
- Foreign keys always have explicit
ON DELETE clause (CASCADE, SET NULL, or RESTRICT)
- Create indexes on: foreign keys, columns used in WHERE clauses, columns used in ORDER BY, unique constraints
Migration Conventions:
- Sequential numbered files:
001_create_users.up.sql, 001_create_users.down.sql
- Every UP migration has a corresponding DOWN migration
- DOWN migrations must be reversible (drop what UP created)
- Use
golang-migrate/migrate or pressly/goose format
- Never modify existing migrations in production -- create new ones
Common Patterns:
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER set_updated_at
BEFORE UPDATE ON {table_name}
FOR EACH ROW
EXECUTE FUNCTION update_updated_at();
CREATE INDEX idx_{table}_active ON {table} (id) WHERE deleted_at IS NULL;
Relationship Patterns:
- One-to-many: FK on the "many" side (
user_id UUID REFERENCES users(id))
- Many-to-many: Junction table with composite PK (
user_roles with user_id + role_id)
- One-to-one: FK with UNIQUE constraint
- Self-referential: FK referencing same table (
parent_id UUID REFERENCES categories(id))
3. React Component Architecture
Component Hierarchy:
App
Layout
Header (auth state, navigation)
Sidebar (navigation menu)
Main
Page (route-level, fetches data)
Container (state logic, data transformation)
Presentational (UI rendering, props only)
Component Classification:
- Page Components (
pages/): Route entry points. Own data fetching via hooks. Pass data down.
- Container Components (
containers/): Business logic, state management, API calls. Minimal JSX.
- Presentational Components (
components/): Pure rendering. Props in, JSX out. No side effects.
- Layout Components (
layouts/): Page structure (header, sidebar, footer). Render children.
State Management Strategy:
- Server state: React Query / TanStack Query (caching, refetching, optimistic updates)
- Local UI state:
useState / useReducer (form inputs, toggles, modals)
- Global app state: React Context (auth, theme, feature flags) -- NOT for server data
- URL state: React Router search params (filters, pagination, sort)
Props Flow Rules:
- Props flow DOWN only. Never pass setters more than 2 levels.
- If prop drilling exceeds 2 levels, extract to Context or compose with render props.
- Define explicit TypeScript interfaces for all props.
- Use
children for composition over configuration.
File Naming:
- Components: PascalCase (
UserProfile.tsx, TaskList.tsx)
- Hooks: camelCase with
use prefix (useAuth.ts, useTasks.ts)
- Utils: camelCase (
formatDate.ts, validators.ts)
- Types: PascalCase in dedicated files (
types.ts or User.types.ts)
4. Go Project Structure
Standard Layout (handler -> service -> repository):
cmd/
server/
main.go # Entry point: config, DI, server startup
internal/
config/
config.go # Environment/config loading
handler/
user_handler.go # HTTP handlers (parse request, call service, write response)
user_handler_test.go
middleware.go # Auth, logging, CORS, recovery middleware
service/
user_service.go # Business logic (validation, orchestration, rules)
user_service_test.go
repository/
user_repository.go # Database access (queries, transactions)
user_repository_test.go
model/
user.go # Domain models (structs, enums, constants)
errors.go # Domain error types
dto/
user_dto.go # Request/response DTOs (separate from domain models)
validation.go # DTO validation rules
router/
router.go # Route definitions, middleware chaining
pkg/
response/
response.go # Standard JSON response helpers
pagination/
pagination.go # Pagination parsing and response
migrations/
001_create_users.up.sql
001_create_users.down.sql
api/
openapi.yaml # OpenAPI 3.0 specification
Layer Responsibilities:
| Layer | Does | Does NOT |
|---|
| Handler | Parse HTTP request, validate input, call service, write HTTP response | Contain business logic, access DB directly |
| Service | Business rules, validation, orchestrate repositories, error wrapping | Know about HTTP, parse requests, write responses |
| Repository | SQL queries, scan results into models, manage transactions | Contain business logic, know about HTTP |
| Model | Define domain types, constants, domain errors | Import from handler/service/repository |
| DTO | Define API request/response shapes, input validation | Contain business logic |
Dependency Direction:
handler -> service -> repository
| | |
v v v
dto model model
Handlers depend on services. Services depend on repositories. All depend on models. Never reverse this flow.
Interface Pattern (dependency injection):
type UserRepository interface {
GetByID(ctx context.Context, id uuid.UUID) (*model.User, error)
Create(ctx context.Context, user *model.User) error
Update(ctx context.Context, user *model.User) error
Delete(ctx context.Context, id uuid.UUID) error
List(ctx context.Context, filter UserFilter) ([]model.User, int, error)
}
type userRepository struct {
db *sql.DB
}
func NewUserRepository(db *sql.DB) UserRepository {
return &userRepository{db: db}
}
Error Handling:
- Define domain errors in
model/errors.go
- Services wrap repository errors with domain context
- Handlers map domain errors to HTTP status codes
- Never expose internal error details to clients
var (
ErrNotFound = errors.New("resource not found")
ErrAlreadyExists = errors.New("resource already exists")
ErrForbidden = errors.New("access denied")
)
5. API Contract Definition
Request/Response Type Rules:
- Every endpoint has explicitly defined request and response types
- Request types: only fields the client sends (no
id, no created_at)
- Response types: what the client receives (includes
id, timestamps)
- List endpoints return paginated envelope, not raw arrays
- Use
omitempty on optional fields in Go structs
- Validate requests at the handler/DTO layer, not in services
Go DTO Patterns:
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 uuid.UUID `json:"id"`
Email string `json:"email"`
Name string `json:"name"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ListResponse[T any] struct {
Data []T `json:"data"`
Pagination Pagination `json:"pagination"`
}
TypeScript API Types (frontend mirror):
interface CreateUserRequest {
email: string;
name: string;
password: string;
}
interface UserResponse {
id: string;
email: string;
name: string;
created_at: string;
updated_at: string;
}
interface PaginatedResponse<T> {
data: T[];
pagination: {
page: number;
limit: number;
total: number;
total_pages: number;
};
}
6. Code Review Patterns
Conformance Checking (Design vs Implementation):
- Every endpoint in the design doc MUST exist in the router
- Every DB table in the design doc MUST have a migration file
- Every React component in the design doc MUST exist as a file
- Request/response types MUST match the design doc field-for-field
- HTTP methods and status codes MUST match the design doc
Anti-Pattern Detection:
| Anti-Pattern | What to Flag | Correct Pattern |
|---|
| Fat handler | Business logic in handler | Move to service layer |
| Anemic service | Service just passes through to repo | Add validation/rules to service |
| Direct DB in handler | sql.DB used in handler | Use repository interface |
| God component | React component > 200 lines | Split into container + presentational |
| Prop drilling | Props passed > 2 levels | Use Context or composition |
| Raw SQL strings | Unparameterized queries | Use parameterized queries ($1, $2) |
| Missing error handling | Unchecked err returns | Always check and handle errors |
| N+1 queries | Query in a loop | Use JOINs or batch queries |
| Hardcoded config | Connection strings in code | Use environment variables |
| Missing indexes | FK columns without indexes | Add indexes on FK columns |
Severity Levels:
- CRITICAL: Security vulnerability, data loss risk, broken functionality. MUST fix before merge.
- HIGH: Design deviation, missing validation, incorrect status code. Should fix before merge.
- MEDIUM: Anti-pattern, suboptimal structure, missing index. Fix in this PR or next.
- LOW: Style issue, naming convention, minor improvement. Suggestion only.
Conventions
- API-first design: Define endpoints and contracts BEFORE writing code.
- One migration per change: Each schema change gets its own numbered migration pair.
- Interface-driven Go: Define interfaces in consumers, implement in providers.
- Strict layer separation: Handlers never import repositories. Services never import
net/http.
- TypeScript strict mode: All React projects use
"strict": true in tsconfig.
- Explicit error types: No generic error strings. Define typed errors in
model/errors.go.
- Context propagation: Every Go function that does I/O takes
context.Context as first parameter.
- UUID primary keys: Always use UUIDs, never auto-increment integers, for all public-facing IDs.
- Timestamps are UTC: All
TIMESTAMPTZ values stored and transmitted in UTC.
- JSON field naming: Use
snake_case in JSON (Go tags + TypeScript interfaces must match).
Knowledge Strategy
- Patterns to capture: Successful API designs, reusable SQL migration templates, component tree patterns that scaled well, Go error handling chains that worked cleanly.
- Examples to collect: Complete design documents that passed review, review reports that caught real issues, migration sequences that handled complex schema evolution.
- Update permission: Agents may freely add/update files in
references/. Changes to SKILL.md or scripts/ require user approval.