-
Conventions: Standard Go project layout, semantic versioning, conventional commits,
golangci-lint, unit + integration tests,docker-composefor deps,.envfor secrets (dev only). -
Focus: HTTP fundamentals, persistence, caching, background jobs, basic auth, logging, metrics, tests.
-
Cross-cutting tech: net/http or Gin/Fiber, PostgreSQL (pgx/GORM), SQLite for quick start, Redis, MinIO (S3), Swagger/OpenAPI, Wire (optional DI), testify, httptest,
golang-migrate,airorreflexfor live reload.
Notes CRUD REST API
- Summary: A simple notes service exposing RESTful CRUD with pagination and filtering.
- Goals: Routing, handlers, repo layer, migrations, DTO vs model, input validation, errors.
- Stack: Gin (or chi), PostgreSQL + pgx, golang-migrate, zap/zerolog, OpenAPI.
- Milestones:
- Bootstrap server + healthz
- CRUD endpoints + validation (go-playground/validator)
- DB migrations + repository layer (context, timeouts)
- Structured logs + error handling + 12-factor config
- OpenAPI spec + swagger UI
- Unit + integration tests (spin up Postgres via docker compose)
- Stretch: ETag/If-Modified-Since; cursor pagination; graceful shutdown hooks.
- Deliverables: API + README with run commands, OpenAPI JSON/YAML, test report.
This section contains the complete plan and system design for the Notes CRUD REST API. It outlines requirements, API design, data model, architecture, tooling, project structure, and a step-by-step execution plan with acceptance criteria. No code is included here.
- Build a clean, maintainable REST API for notes with pagination, filtering, and sorting.
- Prioritize correctness, observability, and good Go practices (layering, context, validation, migrations).
- Deliver OpenAPI docs, unit + integration tests, and Docker Compose for local dev.
- Out of scope initially: auth, multi-tenancy, full-text search (considered as stretch goals).
- Create, read, update, delete a note.
- List notes with pagination (page/page_size), optional text search (title/content), tag filtering, and sorting by created_at/updated_at asc/desc.
- Health endpoint for readiness/liveness checks.
- Consistent error payloads and HTTP status code mapping.
- Performance: Local P50 < 20ms, P95 < 100ms for CRUD paths with Postgres.
- Reliability: Graceful shutdown; context deadlines for DB calls; idempotent delete.
- Observability: Structured logs, Prometheus metrics, and basic traces.
- Security: Input validation, size limits, parameterized SQL, conservative CORS in dev only.
- Maintainability: Layered architecture, small interfaces, table-driven tests, linting.
Base URL: /v1
Note fields:
id (uuid),title (1..200),content (0..10000),tags (array[string] ≤ 10),is_archived (bool),created_at,updated_at(RFC3339)
Endpoints:
- POST
/v1/notes→ 201 Created; body:{ title, content, tags?, is_archived? } - GET
/v1/notes/{id}→ 200 OK or 404 - PATCH
/v1/notes/{id}→ partial update; 200 OK or 400/404 - DELETE
/v1/notes/{id}→ 204 No Content (idempotent) - GET
/v1/notes→ list withpage,page_size(≤100),q,tags,sort(created_at|updated_at),order(asc|desc)- Response:
{ data: Note[], pagination: { page, page_size, total, next_page? } }
- Response:
- GET
/healthz→{ status: "ok" }
Error shape: { "error": { "code": "VALIDATION_ERROR|NOT_FOUND|INTERNAL", "message": "...", "details": { } } }
Table: notes
id UUID PRIMARY KEYtitle TEXT NOT NULL CHECK (length(title) BETWEEN 1 AND 200)content TEXT NOT NULL CHECK (length(content) <= 10000)tags TEXT[] NOT NULL DEFAULT '{}'is_archived BOOLEAN NOT NULL DEFAULT FALSEcreated_at TIMESTAMPTZ NOT NULL DEFAULT now()updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
Indexes:
notes_updated_at_idxon(updated_at DESC)- Optional:
notes_tags_gin_idxGIN ontags(if tag filtering is used frequently) - Stretch:
tsvectorfor full-text search
Pagination:
- Start with page/size. Add keyset/cursor pagination as a stretch goal if needed.
Layers:
- HTTP (Gin): routing, middleware (request ID, recovery, logging), binding/validation, response shaping.
- Service: business rules, input sanitation, pagination defaults, orchestration.
- Repository: Postgres queries via pgx, transactions, context timeouts, error mapping.
- Infra: config, logger, metrics/tracing setup, DB pool, migrations.
Context & shutdown:
- Per-request context propagated to DB; timeouts on handlers and queries; graceful shutdown waits for in-flight requests.
Observability:
- Logs: structured (zerolog) with request_id, path, status, latency.
- Metrics: HTTP request counter and latency histogram; DB query duration histogram; build info.
- Tracing: minimal spans for request and DB operations via OpenTelemetry.
Error handling:
- Central mapping from domain/validation errors to HTTP status and standard error envelope.
-
PostgreSQL vs MySQL
- Postgres offers arrays (for tags), JSONB, rich indexing (GIN), robust transactional semantics, and native full-text (tsvector) for future stretch goals. These features simplify filters and future search more than MySQL equivalents.
-
pgx vs GORM
- pgx gives precise SQL control, predictable performance, and teaches core SQL patterns. For a beginner backend, explicit queries improve understanding and reduce ORM magic. GORM could be added later if speed of development becomes a priority.
-
Gin vs chi/Fiber
- Gin is fast, popular, and has built-in binding/validation helpers and a rich middleware ecosystem. chi is a strong alternative; we choose Gin for wider examples/tutorials.
-
Migrations: golang-migrate
- Mature, up/down SQL, CI-friendly, supports many drivers.
-
Logging: zerolog (zap also fine)
- Structured JSON logs with low overhead; easy to add fields for context.
-
Validation: go-playground/validator
- De facto standard with tag-driven rules; integrates with Gin binding.
-
API Docs: OpenAPI (swaggo or hand-authored spec)
- Ensures contract clarity; enables Swagger UI for manual testing in dev.
-
Observability: OpenTelemetry + Prometheus
- Standard traces/metrics; easy local scraping via
/metrics.
- Standard traces/metrics; easy local scraping via
notes-api/
- cmd/
- notes-api/ (main entrypoint)
- internal/
- config/ (env loading, defaults, validation)
- http/ (router setup, middleware, handler wiring)
- notes/
- handler.go (HTTP handlers)
- service.go (business logic)
- repository.go (pgx queries)
- models.go (domain models, DTOs)
- db/
- migrations/ (SQL up/down files)
- pool.go (pgxpool init)
- observability/
- logging.go
- metrics.go
- tracing.go
- version/
- version.go
- deployments/
- docker-compose.yml (Postgres, app, optional Prometheus)
- docs/
- openapi.yaml -.env.example
- Makefile
- README.md
Configuration (12-factor):
- HTTP_ADDR (e.g., :8080)
- DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME, DB_SSLMODE
- DB_MAX_OPEN_CONNS, DB_MAX_IDLE_CONNS, DB_CONN_MAX_LIFETIME
- LOG_LEVEL (debug|info|warn|error)
- METRICS_ENABLED (bool), OTEL_EXPORTER_OTLP_ENDPOINT (optional)
- PAGINATION_DEFAULT_LIMIT, PAGINATION_MAX_LIMIT
Docker Compose (dev):
- Services: postgres (with volume, healthcheck), notes-api (depends_on), optional prometheus
- Ports: 5432 (db), 8080 (api)
- Repo bootstrap & tooling
- Init go module; add
Makefiletargets:lint,test,run,compose-up/down. - Add
.env.example; set upgolangci-lintconfig; set VSCode tasks (optional). - Acceptance:
make lintandmake testrun without errors.
- Config, logging, health
- Implement config loader with defaults; setup zerolog; request ID, recovery middleware;
/healthzendpoint. - Graceful shutdown on SIGINT with context timeout.
- Acceptance:
GET /healthzreturns{status:"ok"}; shutdown logs orderly.
- DB & migrations
- Write SQL for
notestable and indexes; integrategolang-migrate(CLI or via code) for dev. - Acceptance: migration applies cleanly; re-running is idempotent.
- Repository layer (pgx)
- Setup pgxpool; CRUD methods with context timeouts; map SQL errors to domain errors.
- Acceptance: repository unit tests pass against a test DB; timeouts enforced.
- Service layer
- Validate inputs (lengths, tags count); apply defaults; orchestrate repository; define domain errors.
- Acceptance: table-driven unit tests pass (valid/invalid paths).
- HTTP layer
- Routes for CRUD and list; binding/validation errors map to 400; not found → 404; success → 2xx with JSON envelope.
- Acceptance: handler tests (httptest) cover success and error paths.
- Pagination/filter/sort
- Enforce
page_sizebounds; supportqandtags; add necessary indexes. - Acceptance: integration tests confirm correct filtering and pagination metadata.
- OpenAPI docs
- Generate or author
openapi.yaml; serve Swagger UI in dev; ensure examples & schemas match responses. - Acceptance: spec validates and matches behavior.
- Observability
- Expose
/metrics; add HTTP request and DB histograms; minimal traces. - Acceptance: metrics visible; logs structured with request_id; traces export locally.
- Integration tests & CI
- Use docker-compose Postgres; run migrations; run API in test mode; black-box HTTP tests.
- Acceptance: CI pipeline runs lint, unit, integration tests successfully.
- Containerization & local run
- Multi-stage Dockerfile; compose service for app + db; healthchecks.
- Acceptance:
docker compose upexposes API; smoke tests pass.
- Performance sanity
- Quick load test (k6/Vegeta) for CRUD/list; profiling notes captured.
- Acceptance: meets NFRs; findings documented in README.
- Stretch goals
- ETag/If-None-Match for GETs; cursor pagination; improved shutdown hooks; request logging redaction.
- Acceptance: tests updated; docs reflect changes.
- Unit: service and repository with table-driven tests; mocks for repo when testing service.
- Integration: spin Postgres via compose, apply migrations, black-box API tests.
- Negative tests: invalid payloads, oversized fields, bad query params, missing IDs.
- Coverage goal: ≥ 70% across service/repo/handlers.
- Logging fields: request_id, method, path, status, latency_ms, db_time_ms.
- Metrics: http_requests_total, http_request_duration_seconds (histogram), db_query_duration_seconds (histogram), build_info.
- Traces: basic spans for request → service → repo; propagate W3C trace context.
- Validation: strict lengths and tag counts; reject unknown JSON fields; Content-Length limits.
- SQL safety: parameterized queries; least-privileged DB user.
- CORS: disabled unless explicitly required; allow localhost in dev only.
- Secrets: never commit; use
.envfor dev; document in.env.example.
- Migration drift → run migrations in CI and dev startup; keep SQL reviewed.
- Pagination scalability → add indexes; switch to keyset pagination if needed.
- Validation gaps → comprehensive table-driven tests and OpenAPI schema alignment.
- OpenAPI spec, README with run/test instructions and architecture summary.
- Docker Compose for Postgres + service; Makefile targets.
- Unit + integration tests with CI workflow outline.
- DTO: input/output binding structures.
- Repository: data-access layer with explicit SQL.
- Service: business logic orchestrating validation and persistence.