●WWDC — WWDC 2026 confirms Siri runs on Google Gemini; third-party handoff to ChatGPT is dropped, and Siri AI won't ship in the EU under the DMA at iOS 27●BILLING — 6 days until the Jun 15 change: Agent SDK, headless Claude Code, GitHub Actions, and third-party agents move to API-rate monthly credit●OUTAGE — claude.ai, Claude Code, and Cowork saw an outage (Jun). Scheduled runs are safest when built around fallbackModel and retries●DYNAMIC-WORKFLOWS — Dynamic workflows are on by default on Max/Team and the API, for codebase-wide bug hunts and independent verification●ULTRACODE — Claude Code's new ultracode setting sits in the effort menu, fixing effort to xhigh while Claude decides when to run a workflow●OPUS4.8 — Claude Opus 4.8 is settled in as the default across major plans, with stronger coding, agentic, and reasoning skills●WWDC — WWDC 2026 confirms Siri runs on Google Gemini; third-party handoff to ChatGPT is dropped, and Siri AI won't ship in the EU under the DMA at iOS 27●BILLING — 6 days until the Jun 15 change: Agent SDK, headless Claude Code, GitHub Actions, and third-party agents move to API-rate monthly credit●OUTAGE — claude.ai, Claude Code, and Cowork saw an outage (Jun). Scheduled runs are safest when built around fallbackModel and retries●DYNAMIC-WORKFLOWS — Dynamic workflows are on by default on Max/Team and the API, for codebase-wide bug hunts and independent verification●ULTRACODE — Claude Code's new ultracode setting sits in the effort menu, fixing effort to xhigh while Claude decides when to run a workflow●OPUS4.8 — Claude Opus 4.8 is settled in as the default across major plans, with stronger coding, agentic, and reasoning skills
Go (Golang), created by Google, is a compiled, statically-typed language that has become the go-to choice for microservices and REST API backends. Its simple syntax, fast compilation, and excellent concurrency support have made it one of the most popular backend languages among developers worldwide.
The combination of Go and Claude Code is particularly powerful for three reasons.
First, code generation accuracy. Go's strict type system means Claude Code generates code that compiles correctly on the first try far more often than with dynamically-typed languages. The simplicity of Go's type inference keeps generated code clean and predictable.
Second, standard library richness. Go includes networking, HTTP, JSON, and cryptography in its standard library. Claude Code can implement features without reaching for external dependencies, keeping generated code lean and maintainable.
Third, performance and portability. Go compiles to a single binary, making Docker images dramatically smaller than Node.js or Python equivalents. This simplicity makes CI/CD automation with Claude Code especially effective.
By the end of this guide, you will have a fully operational system with:
A high-performance REST API built with the Gin framework
Type-safe data access with GORM and PostgreSQL
JWT-based authentication and authorization
Docker multi-stage builds producing images under 15MB
GitHub Actions CI/CD pipeline with automated testing
Structured logging and health check endpoints
Prerequisites and Environment Setup
Required Environment
To follow this guide, you will need:
Claude Code (latest version) installed and working
go version# go version go1.22.4 darwin/arm64docker --version# Docker version 26.1.0git --version# git version 2.44.0
Setting Up CLAUDE.md
The single most impactful thing you can do before writing a line of code is configure CLAUDE.md to give Claude Code your project context. This dramatically improves generated code quality.
# Project: Go REST API## Tech Stack- Go 1.22+ / Gin v1.10 / GORM v2 / PostgreSQL 16## Architecture- Clean Architecture (Handler → Service → Repository)- Dependency Injection pattern## Code Conventions- Package names: lowercase only- Always wrap errors: fmt.Errorf("%w", err)- Logging: log/slog (standard library)- Testing: testify## Prohibitions- No global variables- No panic() — always return errors- No init() functions (except DI)
With this context, Claude Code will generate code that is consistent with your project's architecture from the very first prompt.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Master the full Claude Code workflow for Go project design — from Clean Architecture scaffolding to production-ready code generation
✦Understand type-safe API design patterns using Gin router and GORM with PostgreSQL for real-world backend services
✦Implement a complete deployment pipeline: Docker multi-stage builds (under 15MB images), GitHub Actions CI/CD with coverage reporting
Secure payment via Stripe · Cancel anytime
Project Structure Design
Clean Architecture in Go
Prompt Claude Code to scaffold the project structure:
Following the conventions in CLAUDE.md, design a project structure
for a user management API using Clean Architecture.
Create the directory layout and go.mod file.
cd go-apigo mod init github.com/yourusername/go-api# Web frameworkgo get github.com/gin-gonic/gin@v1.10.0# ORMgo get gorm.io/gorm@v1.25.10go get gorm.io/driver/postgres@v1.5.9# Environment variablesgo get github.com/joho/godotenv@v1.5.1# JWTgo get github.com/golang-jwt/jwt/v5@v5.2.1# Validationgo get github.com/go-playground/validator/v10@v10.22.0# Testinggo get github.com/stretchr/testify@v1.9.0go get github.com/DATA-DOG/go-sqlmock@v1.5.0
Domain Model and Repository Layer
Defining the Domain Model
Prompt Claude Code with:
Create internal/domain/user.go.
The user model needs:
- ID (UUID)
- Email (unique)
- PasswordHash
- Name
- CreatedAt / UpdatedAt / DeletedAt
Use GORM's soft delete support.
Generated internal/domain/user.go:
package domainimport ( "time" "github.com/google/uuid")// User is the core domain modeltype User struct { ID uuid.UUID `gorm:"type:uuid;primaryKey;default:gen_random_uuid()"` Email string `gorm:"uniqueIndex;not null;size:255"` PasswordHash string `gorm:"not null"` Name string `gorm:"not null;size:100"` CreatedAt time.Time UpdatedAt time.Time DeletedAt *time.Time `gorm:"index"`}// UserRepository defines the data access interfacetype UserRepository interface { Create(user *User) error FindByID(id uuid.UUID) (*User, error) FindByEmail(email string) (*User, error) Update(user *User) error Delete(id uuid.UUID) error List(offset, limit int) ([]*User, int64, error)}// Domain errorsvar ( ErrUserNotFound = &DomainError{Code: "USER_NOT_FOUND", Message: "User not found"} ErrUserAlreadyExists = &DomainError{Code: "USER_ALREADY_EXISTS", Message: "This email address is already in use"} ErrInvalidCredential = &DomainError{Code: "INVALID_CREDENTIAL", Message: "Invalid email or password"})// DomainError is the domain-layer error typetype DomainError struct { Code string Message string}func (e *DomainError) Error() string { return e.Message}
PostgreSQL Repository Implementation
// internal/repository/postgres/user_repository.gopackage postgresimport ( "errors" "fmt" "github.com/google/uuid" "gorm.io/gorm" "github.com/yourusername/go-api/internal/domain")type userRepository struct { db *gorm.DB}// NewUserRepository creates a new PostgreSQL user repositoryfunc NewUserRepository(db *gorm.DB) domain.UserRepository { return &userRepository{db: db}}func (r *userRepository) Create(user *domain.User) error { result := r.db.Create(user) if result.Error != nil { if isDuplicateError(result.Error) { return domain.ErrUserAlreadyExists } return fmt.Errorf("failed to create user: %w", result.Error) } return nil}func (r *userRepository) FindByID(id uuid.UUID) (*domain.User, error) { var user domain.User result := r.db.Where("id = ? AND deleted_at IS NULL", id).First(&user) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, domain.ErrUserNotFound } return nil, fmt.Errorf("failed to find user: %w", result.Error) } return &user, nil}func (r *userRepository) FindByEmail(email string) (*domain.User, error) { var user domain.User result := r.db.Where("email = ? AND deleted_at IS NULL", email).First(&user) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, domain.ErrUserNotFound } return nil, fmt.Errorf("failed to find user by email: %w", result.Error) } return &user, nil}func (r *userRepository) List(offset, limit int) ([]*domain.User, int64, error) { var users []*domain.User var total int64 r.db.Model(&domain.User{}).Where("deleted_at IS NULL").Count(&total) result := r.db.Where("deleted_at IS NULL"). Order("created_at DESC"). Offset(offset).Limit(limit). Find(&users) if result.Error != nil { return nil, 0, fmt.Errorf("failed to list users: %w", result.Error) } return users, total, nil}func isDuplicateError(err error) bool { return err != nil && ( errors.Is(err, gorm.ErrDuplicatedKey) || containsString(err.Error(), "duplicate key") || containsString(err.Error(), "UNIQUE constraint failed"))}
Service Layer and HTTP Handlers
Business Logic (Service Layer)
// internal/service/user_service.gopackage serviceimport ( "fmt" "log/slog" "github.com/google/uuid" "golang.org/x/crypto/bcrypt" "github.com/yourusername/go-api/internal/domain" "github.com/yourusername/go-api/pkg/jwt")type UserService struct { repo domain.UserRepository jwtManager *jwt.Manager logger *slog.Logger}func NewUserService(repo domain.UserRepository, jwtManager *jwt.Manager, logger *slog.Logger) *UserService { return &UserService{repo: repo, jwtManager: jwtManager, logger: logger}}// Register handles new user registrationfunc (s *UserService) Register(email, password, name string) (*domain.User, error) { // Cost 12 is the production recommendation for bcrypt hash, err := bcrypt.GenerateFromPassword([]byte(password), 12) if err != nil { return nil, fmt.Errorf("failed to hash password: %w", err) } user := &domain.User{ Email: email, PasswordHash: string(hash), Name: name, } if err := s.repo.Create(user); err != nil { return nil, err // Pass domain errors through } s.logger.Info("user registered", slog.String("user_id", user.ID.String()), slog.String("email", email), ) return user, nil}// Login authenticates a user and returns a JWT tokenfunc (s *UserService) Login(email, password string) (string, error) { user, err := s.repo.FindByEmail(email) if err != nil { // Return the same error regardless of whether the user exists // to prevent timing attacks return "", domain.ErrInvalidCredential } if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)); err != nil { s.logger.Warn("login failed", slog.String("email", email)) return "", domain.ErrInvalidCredential } token, err := s.jwtManager.Generate(user.ID, user.Email) if err != nil { return "", fmt.Errorf("failed to generate token: %w", err) } s.logger.Info("user logged in", slog.String("user_id", user.ID.String())) return token, nil}
HTTP Handlers (Gin)
Prompt Claude Code:
Create internal/handler/user_handler.go.
Implement these endpoints:
- POST /api/v1/auth/register
- POST /api/v1/auth/login
- GET /api/v1/users/:id (JWT required)
- GET /api/v1/users (JWT required, with pagination)
Use go-playground/validator for input validation.
Use a unified error response format.
Claude Code is particularly effective at generating table-driven tests, which are idiomatic in Go.
Prompt:
Create internal/service/user_service_test.go.
- Test Register: happy path, duplicate email, and invalid password
- Test Login: happy path, user not found, and wrong password
- Use a mock repository (interface-based)
- Use testify assert
Cause: A previous process is still holding the port. Common during hot-reload development with Claude Code.
Fix:
# Find and kill the process on port 8080lsof -ti:8080 | xargs kill -9
Claude Code Power Techniques for Go Development
Technique 1: Generate consistent error handling across all service methods
Go's explicit error handling can become repetitive. Use this prompt for consistent results:
Implement error handling for all service layer methods following this pattern:
- gorm.ErrRecordNotFound → domain.ErrUserNotFound
- Duplicate key errors → domain.ErrUserAlreadyExists
- All other errors → wrapped with fmt.Errorf
Technique 2: Table-driven tests for handlers
Create user_handler_test.go with table-driven tests using httptest.NewRecorder
for these endpoints:
- POST /api/v1/auth/register
- Success (201)
- Duplicate email (409)
- Validation error (400)
- Invalid JSON (400)
Organize test cases in a TestCases slice.
Technique 3: Auto-generate SQL migrations
Create SQL migrations in the migrations/ folder for the users table
(UUID primary key, email unique, created_at/updated_at/deleted_at).
Use the naming convention: 001_create_users_table.up.sql / .down.sql
Looking back
In this guide, we walked through the entire lifecycle of building a production-grade Go REST API with Claude Code — from project structure and Clean Architecture design, through JWT authentication, testing, Docker containerization, and GitHub Actions CI/CD.
Key takeaways:
Design: A well-written CLAUDE.md has the largest single impact on Claude Code's output quality. Define your architecture constraints before writing any code.
Implementation: Maintaining Clean Architecture layer separation (Handler → Service → Repository) keeps code testable and maintainable as the project grows.
Testing: Interface-based mock repositories enable fast, database-free unit tests — a pattern Claude Code generates reliably when prompted correctly.
Deployment: Multi-stage Docker builds with FROM scratch produce images under 15MB, dramatically simpler to deploy and scale than interpreted-language equivalents.
CI/CD: Automating golangci-lint static analysis and coverage measurement ensures code quality is continuously enforced rather than manually reviewed.
Go and Claude Code together offer a compelling combination: type-safe code generation, fast compilation, and minimal deployment footprint — suitable for individual developers and enterprise teams alike. As your next step, explore how Claude Code handles the frontend side in the Claude Code × Next.js 15 App Router production guide.
Share
Thank You for Reading
Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.