A REST API for crowdfunding campaigns and donations, written in Go. Users can register, create campaigns, and donate through a payment gateway. Built with Gin, GORM, and PostgreSQL, with JWT authentication and Midtrans for payments.
- Users — register, login, email availability check, avatar upload
- Campaigns — list, detail, create, update, image upload (with ownership checks)
- Transactions — create donations, list per campaign and per user
- Payments — Midtrans Snap integration with a webhook for status updates
- Auth — stateless JWT, enforced via middleware
- UUID identifiers — every resource uses a non-sequential UUID, so IDs can't be guessed or enumerated from the API.
- Compile-time dependency injection with google/wire — no runtime reflection or service locator.
- Atomic payments — transaction status and campaign totals are updated inside a single database transaction.
- Cloud storage — uploads go to Cloudflare R2 (S3-compatible) with a local-disk fallback for development.
- Operability — graceful shutdown, request-id tracing, structured JSON logs, and database connection retry on startup.
The code is organised by domain. Each domain owns its entity, repository (database access), and service (business logic). HTTP handlers depend only on the service interfaces, and dependencies are wired together with google/wire.
config/ Environment configuration
database/ GORM connection setup
auth/ JWT generation and validation
user/ User domain (entity, repository, service)
campaign/ Campaign domain
transaction/ Transaction domain
payment/ Midtrans gateway
storage/ File uploads (Cloudflare R2 or local disk)
handler/ Gin HTTP handlers, auth middleware, request logging
helper/ Shared API response helpers
migrations/ SQL schema migrations
docs/ Generated Swagger/OpenAPI spec
wire.go Dependency graph (compile-time DI)
Request flow: handler → service (interface) → repository (interface) → PostgreSQL.
Because services and repositories are interfaces, the business logic is unit
tested with in-memory mocks and no database.
cp .env.example .env # set JWT_SECRET_KEY at minimum
docker compose up --buildThis starts PostgreSQL and the API. The service listens on http://localhost:8080.
If 8080 or 5432 are already in use, override HOST_APP_PORT / HOST_DB_PORT
in your .env.
Requires Go 1.25+ and a running PostgreSQL instance.
cp .env.example .env # adjust DB credentials and JWT_SECRET_KEY
make migrate-up DATABASE_URL="postgres://postgres:postgres@127.0.0.1:5432/crowdfunding?sslmode=disable"
make runMigrations use golang-migrate. If you don't have the CLI:
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latestAll configuration comes from environment variables (a local .env is loaded
when present).
| Variable | Description | Default |
|---|---|---|
APP_PORT |
HTTP port | 8080 |
DB_HOST |
PostgreSQL host | 127.0.0.1 |
DB_PORT |
PostgreSQL port | 5432 |
DB_USER |
PostgreSQL user | postgres |
DB_PASSWORD |
PostgreSQL password | (empty) |
DB_NAME |
Database name | crowdfunding |
JWT_SECRET_KEY |
Signing key for JWT (required) | — |
MIDTRANS_SERVER_KEY |
Midtrans server key | — |
MIDTRANS_CLIENT_KEY |
Midtrans client key | — |
MIDTRANS_ENABLED |
Turn the live checkout on/off | false |
SUPPORT_EMAIL |
Contact shown when in demo mode | hi@wikukarno.dev |
R2_ACCOUNT_ID |
Cloudflare R2 account id | — |
R2_ACCESS_KEY_ID |
R2 access key | — |
R2_SECRET_ACCESS_KEY |
R2 secret key | — |
R2_BUCKET |
R2 bucket name | — |
R2_PUBLIC_URL |
Public base URL for uploaded files | — |
Avatar and campaign images are uploaded to Cloudflare R2 (S3-compatible)
through the AWS SDK. When the R2_* variables are empty the app falls back to
local disk under ./images so development works without cloud credentials.
The live Midtrans checkout is gated behind MIDTRANS_ENABLED. When it's off
(the default), POST /transactions returns 503 and no pending row is written,
so a public demo never creates real Snap tokens. GET /config reports the
current state (and SUPPORT_EMAIL) so the web client can show a "contact
support to enable a live demo" prompt instead — keeping the server flag as the
single source of truth. As a safety net, MidtransConfig.Available() also stays
in demo mode when the flag is on but the keys are missing, so a half-configured
deploy can't advertise a checkout that would fail.
Interactive Swagger UI is available at http://localhost:8080/swagger/index.html
once the server is running. Regenerate the spec after changing annotations with
make docs.
Base path: /api/v1. Protected routes need an Authorization: Bearer <token>
header.
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /health |
Health check | |
| GET | /config |
Public client config (payments on/off, support email) | |
| POST | /users |
Register a user | |
| POST | /sessions |
Login | |
| POST | /email_checkers |
Check email availability | |
| POST | /avatars |
✓ | Upload avatar |
| GET | /users/fetch |
✓ | Fetch current user |
| GET | /campaigns |
List campaigns (?user_id= opt.) |
|
| GET | /campaigns/:id |
Campaign detail | |
| POST | /campaigns |
✓ | Create campaign |
| PUT | /campaigns/:id |
✓ | Update campaign |
| POST | /campaign-images |
✓ | Upload campaign image |
| GET | /campaigns/:id/transactions |
✓ | Transactions of a campaign |
| GET | /transactions |
✓ | Current user's transactions |
| POST | /transactions |
✓ | Create a donation |
| POST | /transactions/notification |
Midtrans payment webhook |
make test # go test -race ./...
make cover # coverage report per functionGo · Gin · GORM · PostgreSQL · JWT · google/wire · Swagger (swaggo) · Cloudflare R2 (S3) · Midtrans · Docker
MIT © Wiku Karno