Go/
Lesson

AI generates working Go code but dumps everything in main.go. That's fine for prototyping. For anything you'll maintain, you need proper package structure. Go's conventions are strict enough that once you learn them, you can navigate any Go codebase, DockerWhat is docker?A tool that packages your application and all its dependencies into a portable container that runs identically on any machine., Kubernetes, Terraform, or your team's projects.

This lesson is about reading project structure, not writing it from scratch. When AI generates a Go project, you need to evaluate whether it organized things correctly.

The standard layout

myproject/
├── cmd/                    # Entry points for binaries
│   ├── server/
│   │   └── main.go         # go run ./cmd/server
│   └── cli/
│       └── main.go         # go run ./cmd/cli
├── internal/               # Private packages (compiler-enforced)
│   ├── config/
│   ├── database/
│   ├── handlers/
│   └── models/
├── pkg/                    # Public library code (optional)
│   └── validator/
├── go.mod
├── go.sum
├── Makefile
└── Dockerfile
AI pitfall
When you ask AI to "create a Go project," it puts everything in one package with one main.go file. For anything beyond a script, prompt: "Use cmd/ for the entry point and internal/ for business logic." AI understands this structure but doesn't use it unless asked.
02

The key directories

cmd/: where programs start

Each subdirectory under cmd/ is a separate binaryWhat is binary?A ready-to-run file produced by the compiler. You can send it to any computer and it just works - no install needed.. The main.go file should be thin, just wiring, no business logic.

// cmd/server/main.go
package main

import (
    "log"
    "myproject/internal/config"
    "myproject/internal/server"
)

func main() {
    cfg, err := config.Load()
    if err != nil {
        log.Fatal(err)
    }
    log.Fatal(server.Run(cfg))
}

internal/: compilerWhat is compiler?A program that translates code you write into a language your computer can execute. It also catches errors before your code runs.-enforced privacy

This is Go's killer feature for project organization. Anything under internal/ can only be imported by code in the parent moduleWhat is module?A self-contained file of code with its own scope that explicitly exports values for other files to import, preventing name collisions.. The compiler rejects external imports, not a convention, an actual compilation error.

DirectoryWhat goes hereExample
internal/configConfiguration loadingLoad(), env vars
internal/modelsData structuresUser, Order
internal/handlersHTTP handlersUserHandler, OrderHandler
internal/databaseDatabase accessConnect(), Migrate()
internal/middlewareHTTP middlewareAuth(), Logging()
internal/serviceBusiness logicUserService, OrderService
If another module tries to import myproject/internal/database, the build fails with: "use of internal package not allowed."

pkg/: public library code

Use pkg/ for code other projects should be able to import. Many projects skip this, if you're not building a library, you probably don't need it.

03

Package design rules

Package by function, not by type

// Bad: one type per package
package user
type User struct { ... }

// Good: group by responsibility
package models     // All data types
package handlers   // All HTTP handlers
package storage    // All database operations

Avoid circular dependencies

Go doesn't allow circular imports, package A can't import B if B imports A. This forces clean dependencyWhat is dependency?A piece of code written by someone else that your project needs to work. Think of it as a building block you import instead of writing yourself. graphs.

cmd/server
    ↓
internal/server
    ↓
internal/handlers
  ↙       ↘
service    models
  ↓
database
AI pitfall
AI generates helper packages called utils or common that everything imports. These become dumping grounds with no clear purpose. If you see AI creating a utils package, ask it to move each function to the package where it's actually used.
04

Go tooling essentials

ModuleWhat is module?A self-contained file of code with its own scope that explicitly exports values for other files to import, preventing name collisions. management

go mod init github.com/you/project  # Initialize module
go mod tidy                          # Add missing, remove unused deps
go mod download                      # Download dependencies
go get -u ./...                      # Update all dependencies
go list -m all                       # List all dependencies

Code quality tools

ToolWhat it doesCommand
go fmtAuto-format codego fmt ./...
go vetStatic analysis (format strings, unused results)go vet ./...
golangci-lintComprehensive linting (many linters in one)golangci-lint run
go test -raceDetect race conditionsgo test -race ./...

Makefile for automation

.PHONY: build test lint run

build:
	go build -o bin/server ./cmd/server

test:
	go test -v -race -cover ./...

lint:
	golangci-lint run

run:
	go run ./cmd/server

check: lint test  # Run before committing
05

DockerWhat is docker?A tool that packages your application and all its dependencies into a portable container that runs identically on any machine. for Go applications

Go compiles to a single static binaryWhat is binary?A ready-to-run file produced by the compiler. You can send it to any computer and it just works - no install needed., which makes Docker images tiny. The standard pattern is a multi-stage buildWhat is multi-stage build?A Dockerfile technique using multiple FROM instructions to separate build tools from the final lean production image.:

# Build stage
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 -o server ./cmd/server

# Final stage - scratch or alpine
FROM alpine:latest
RUN apk --no-cache add ca-certificates
COPY --from=builder /app/server /server
EXPOSE 8080
CMD ["/server"]
AI pitfall
AI generates Dockerfiles that skip the multi-stage pattern, resulting in images that include the entire Go toolchain (~1GB). The final image only needs the compiled binary (~10-20MB). Always use multi-stage builds for Go. Also, AI forgets CGO_ENABLED=0, which causes "not found" errors when the binary tries to load glibc in alpine/scratch images.
06

Configuration pattern

// internal/config/config.go
package config

import (
    "os"
    "strconv"
)

type Config struct {
    Port        int
    DatabaseURL string
    JWTSecret   string
    Debug       bool
}

func Load() (*Config, error) {
    port, _ := strconv.Atoi(getEnv("PORT", "8080"))

    cfg := &Config{
        Port:        port,
        DatabaseURL: getEnv("DATABASE_URL", ""),
        JWTSecret:   getEnv("JWT_SECRET", ""),
        Debug:       getEnv("DEBUG", "false") == "true",
    }

    if cfg.DatabaseURL == "" {
        return nil, fmt.Errorf("DATABASE_URL is required")
    }
    return cfg, nil
}

func getEnv(key, fallback string) string {
    if v := os.Getenv(key); v != "" {
        return v
    }
    return fallback
}
AI pitfall
AI generates config that silently uses default values for critical settings (database URL, secrets). If JWT_SECRET defaults to "", your auth is completely broken but the app starts without errors. Validate required config at startup and fail fast with clear error messages.
07

Production project checklist

When reviewing an AI-generated Go project structure:

CheckWhat it means
cmd/ exists with thin main.goBusiness logic isn't in main
internal/ for private packagesEncapsulation is enforced
No utils dumping groundFunctions are in relevant packages
go.mod references correct module pathImports will work
Multi-stage DockerfileSmall, secure container images
Required config validated at startupFail fast, not at midnight on production
Makefile with test + lint targetsCI/CD will work