Production-focused Go backend that models bank-style money movement using strict double-entry accounting.
It demonstrates:
- Atomic transactions with PostgreSQL
- Concurrency safety with serializable isolation + retry
- Ledger-based reconciliation
- JWT auth + account-level authorization
- API docs, health checks, and Dockerized deployment
The frontend is intentionally minimal — Next.js Typescript, — so the focus stays on the Go backend. See the frontend repo for details.
- Frontend: https://golangbank.app
- Frontend Repo: https://github.com/PaulBabatuyi/double-entry-bank
- API Docs: https://golangbank.app/swagger
- Health: https://golangbank.app/health
Dont forget to star and fork this project repo
This README is intentionally concise and implementation-focused.
For the full technical narrative and tutorial, read the FreeCodeCamp article: How to Build a Bank Ledger in Golang with PostgreSQL using Double-Entry Accounting

Each money movement writes balanced entries into the entries table:
- deposit: credit user account, debit settlement account
- withdrawal: debit user account, credit settlement account
- transfer: debit source account, credit destination account
Key constraints and behaviors implemented in code:
- single-sided entry rows (debit xor credit)
- account row locking (
FOR UPDATE) during balance-changing operations - serializable transactions with automatic retry on SQLSTATE
40001 - reconciliation query computes
SUM(credit) - SUM(debit)as source of truth
- Go 1.24+
- Router: go-chi/chi
- Database: PostgreSQL 16
- Query layer: sqlc
- Auth: JWT (go-chi/jwtauth)
- Logging: zerolog
- API docs: swaggo + http-swagger
- Testing: Go test + testify + race detector
- Runtime: Docker + docker-compose
Public:
POST /registerPOST /loginGET /healthGET /swagger/index.html
Protected (Bearer token required):
POST /accountsGET /accountsGET /accounts/{id}POST /accounts/{id}/depositPOST /accounts/{id}/withdrawPOST /transfersGET /accounts/{id}/entriesGET /accounts/{id}/reconcileGET /transactions/{id}
.
├── cmd/
│ └── main.go
├── internal/
│ ├── api/
│ ├── db/
│ └── service/
├── postgres/
│ ├── migrations/
│ ├── queries/
│ └── sqlc/
├── docs/
├── docker-compose.yml
├── docker-entrypoint
├── Dockerfile
├── Makefile
└── README.md
- Go 1.24+
- Docker + docker compose
- migrate CLI
- sqlc CLI
- swag CLI (only needed when regenerating docs)
Install tools:
go install github.com/golang-migrate/migrate/v4/cmd/migrate@latest
go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
go install github.com/swaggo/swag/cmd/swag@latestgit clone https://github.com/PaulBabatuyi/double-entry-bank-Go.git
cd double-entry-bank-Go
cp .env.example .env
# Set JWT_SECRET to at least 32 characters: openssl rand -base64 32
make postgres
make migrate-up
make sqlc
make serverOpen:
- Swagger: http://localhost:8080/swagger/index.html
- Health: http://localhost:8080/health
Recommended (requires Docker running):
make postgres
make testWith coverage report:
make coverageFull CI-style run including migrations:
make ci-testEnvironment used by tests:
TEST_DB_URL(defaults topostgresql://root:secret@localhost:5433/simple_ledger?sslmode=disable)
make postgres # Start PostgreSQL container
make migrate-up # Apply migrations
make migrate-down # Rollback last migration
make sqlc # Regenerate sqlc query code
make server # Run the API server
make test # Run tests with race detector
make coverage # Generate coverage report
make lint # Run golangci-lint
make ci-test # Full test run including migrations
make docker-build # Build Docker image locally
make docker-up # Start full stack with Docker Compose
make docker-down # Stop Docker Compose servicesRender deployment instructions are in DEPLOYMENT.md.
The container serves the backend API only. The frontend is deployed separately.
This repository is a practical fintech-backend demonstration covering:
- correctness under concurrency
- auditable money movement
- clear API boundaries
- production-minded deployment shape
If you are a recruiter or reviewer, start with this README and the Swagger UI. For the full technical narrative, read the FreeCodeCamp article linked above.