Backend API for NapraviMi — a two-sided marketplace connecting customers with craftsmen. Built in Go with Gin, organized as a modular monolith using vertical slices.
Live at api.napravimi.com. Frontend client: NapraviMi_web_client.
The application is a modular monolith. Each business capability lives in its own vertical slice under internal/modules/, and every slice follows the same internal shape:
usecase— business logiccontroller— HTTP handlersdto— request/response types (shared response shapes go incommon_dto.goper module)repository.go— aRepositoryinterface +GormRepositoryimplementation, injected into the module's services
Modules: auth, cart, crafts, craftsman_application, files, health, mail, order, payment, product, product_categories, users, utils.
Three cross-cutting patterns apply across the codebase:
- Repository pattern — every module talks to Postgres through a
Repositoryinterface backed by aGormRepositorystruct, never raw GORM calls scattered through business logic. - Response envelope —
internal/http/response(response.Data/response.Error/response.Empty) standardizes API responses instead of ad-hocc.JSON. - Factory + decorator DI for external gateways — external integrations (payment, mail) are built via a
New<X>Gateway(provider)factory and wrapped by decorators (e.g.payment.NewBreakerGateway(gateway, threshold, timeout)for circuit breaking) wired once inmain.goand injected as interfaces via the app container.
User roles: admin, user, craftsman (a moderator role is a planned future addition).
payment— gateway abstraction with a mock gateway for the current no-real-payment-processor prototype stage, wrapped in a circuit breaker decorator (breaker_gateway.go) to fail fast against a struggling payment provider. Seenotes/Circuit Breaker pattern.mdfor the state machine this implements.mail— factory + decorator gateway supporting Resend (production) and smtp4dev (local dev) providers.cart— supports splitting a single multi-craftsman cart into separate per-craftsman orders at checkout (cart/checkout).- Redis caching — versioned write-through pattern (
cache:version:*keys, bumped on writes) instead of delete-based invalidation, avoiding cache-stampede/race issues on invalidation. - Search — Cyrillic/accented-text-aware matching via
utils.NormalizeForSearch, backed by GIN-indexablesearch_keywordsarray columns onCraftandProductCategory.
Schema is managed with Atlas rather than GORM AutoMigrate:
- The desired schema is derived directly from GORM models via
cmd/atlasloader(gormschema.Load). - Versioned SQL migrations live in
migrations/, withatlas.sumas the integrity hash — this is the source of truth, not the Go structs at runtime. atlas.hcldefines two environments:local(a throwaway Dockerized Postgres used formigrate diff/migrate lint) anddeploy(targets$DATABASE_URL, pinned tosearch_path=publicfor Neon compatibility).- Reference-data seeds (crafts, product categories) are generated via
cmd/seedgenfromconfig.BuildSeedSQL(). Only the admin user is seeded at application runtime — everything else is data-driven through migrations.
Common commands (see Makefile for the full set and required env files):
make migrate-diff env=dev # generate a new migration from GORM model changes
make migrate-lint env=dev # lint pending migrations
make migrate-apply env=dev # apply migrations to a target database
make migrate-status env=dev
make migrate-baseline env=dev # one-time adoption for a pre-existing DBenv=<name> reads connection details from a gitignored .env.atlas.<name> file; alternatively export DATABASE_URL directly.
Three GitHub Actions workflows under .github/workflows/:
db-migrations.yml— runs on PRs touching migrations/entities/seed files. Spins up an ephemeral Neon branch, validates migration integrity, lints with Atlas (ifATLAS_TOKENis set), applies migrations to the branch, runs a drift check (GORM models vs. committed migrations), then tears the branch down.db-deploy-dev.yml— applies migrations to the Neondevelopmentbranch on push todevelopment.db-deploy.yml— applies migrations to production on push tomaster.
| Concern | Choice |
|---|---|
| HTTP framework | Gin |
| ORM | GORM |
| Database | PostgreSQL (hosted on Neon) |
| Migrations | Atlas |
| Cache | Redis (hosted on Upstash) |
| Document store | MongoDB (planned — see below) |
| Resend (prod) / smtp4dev (local) | |
| File storage | Cloudflare R2 (cdn.napravimi.com) |
| Deployment | Render (free tier), Docker |
| Scheduling | robfig/cron/v3 |
Requires Docker.
-
Create a
.envfile in the project root:ADMIN_EMAIL=sample@protonmail.com ADMIN_PASSWORD=Secure_pass1 ADMIN_USERNAME=admin BASE_URL=http://localhost:8080 CORS_ALLOWED_ORIGINS=http://localhost:4200 POSTGRES_HOST=postgres POSTGRES_USER=test_user POSTGRES_PASSWORD=test_password POSTGRES_DB=test_db_name POSTGRES_PORT=5432 REDIS_HOST=redis_cache REDIS_PORT=6379 APP_PORT=8080 APP_ENV=development JWT_SECRET=test_jwt_secret -
Build and run:
docker compose up --build
-
After any
docker compose down -v, run./app_init.shfrom the project root to clear the Go test cache and repopulate the database with seed data.
Cloudflare's bot detection (Turnstile) is disabled in local builds so the init script can bulk-insert users.
Design notes and implementation plans written during development (often with AI assistance) live in /notes, kept as reference and study material. Highlights: circuit breaker pattern, multi-craftsman checkout flow, Redis versioned cache invalidation, search optimization, payment error handling, and the (planned) end-to-end encrypted messaging and scheduled email digest features.