chore: rename simple_bank to api_tmpl and clean up for template use
Rename the module, proto service, and all identifiers from simple_bank/ SimpleBank to api_tmpl/ApiTmpl, and regenerate the gRPC/gateway/swagger/ mock code accordingly. - Scrub real secrets (SMTP password, auth token secret) from api.env/ api_test.env, untrack them, and add api.env.example/api_test.env.example - Remove committed build artifacts (tmp/main, tmp/build-errors.log, dbml-error.log) and gitignore them going forward - Fix Dockerfile paths (build target, migrations copy) that referenced a stale project layout - Rewrite README to match the actual cmd/ + internal/ structure and add a "using this as a template" section
This commit is contained in:
1 parent
961bdd81d4
commit
c4ee0ce4dc
71 files changed
+744
-743
No files matched your search
@@ -1,171 +1,167 @@
|
||||
# simple_bank
|
||||
# api_tmpl
|
||||
|
||||
simple_bank is a small example backend service in Go that demonstrates a banking-style API with:
|
||||
- HTTP gateway (grpc-gateway) and gRPC server
|
||||
- PostgreSQL for persistent storage (SQLC generated queries)
|
||||
- Redis + Asynq for background task queue / worker processing
|
||||
- Migrations via golang-migrate
|
||||
- Email verification task example
|
||||
A Go backend service template with a working authentication flow (register, login, email
|
||||
verification, refresh tokens), built to be cloned and renamed for new projects rather than run
|
||||
as-is.
|
||||
|
||||
This repository is intended as a reference/demo for building a production-ish Go service with background jobs.
|
||||
Included out of the box:
|
||||
- Gin-based HTTP API (`internal/handlers/http`) with an equivalent gRPC + grpc-gateway API
|
||||
(`internal/handlers/grpc`, currently wired up but disabled in `main.go` — see below)
|
||||
- PostgreSQL persistence via `sqlc`-generated queries (`internal/db/sqlc`)
|
||||
- Redis + Asynq background task queue, with an example "send verification email" task
|
||||
- PASETO/JWT auth token issuance and middleware, with role-based access control
|
||||
- Migrations via `golang-migrate`
|
||||
- Swagger/OpenAPI docs, served both via `swaggo` (Gin) and `grpc-gateway`'s generated spec
|
||||
- Bruno API collection for manual testing (`Bruno/`)
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
## Using this as a template
|
||||
|
||||
- RESTful HTTP gateway (default `:8080`) backed by gRPC (default `:9090`)
|
||||
- gRPC API and generated protobufs (uses grpc-gateway)
|
||||
- Database operations generated with `sqlc`
|
||||
- Background job dispatching and processing with `asynq` (Redis)
|
||||
- Config-driven via environment variables
|
||||
- Example Send Verification Email background task
|
||||
1. Rename the module: replace `github.com/FernandoJVideira/api_tmpl` with your own module path
|
||||
throughout the repo, and update `module` in [go.mod](go.mod).
|
||||
2. Copy the env templates and fill in real values:
|
||||
```bash
|
||||
cp api.env.example api.env
|
||||
cp api_test.env.example api_test.env
|
||||
```
|
||||
At minimum, set a real `AUTH_TOKEN_SECRET` (32+ random characters) and, if you plan to use the
|
||||
email-verification worker task, real SMTP credentials. Neither `api.env` nor `api_test.env` is
|
||||
tracked in git (see `.gitignore`) — don't commit real secrets into their place.
|
||||
3. If you rename the module or the gRPC service, regenerate the protobuf/gateway/swagger code:
|
||||
```bash
|
||||
make proto
|
||||
make mock
|
||||
```
|
||||
This requires `protoc`, `protoc-gen-go`, `protoc-gen-go-grpc`, `protoc-gen-grpc-gateway`,
|
||||
`protoc-gen-openapiv2`, and `statik` on your `PATH` (`brew install protobuf`, then
|
||||
`go install` the rest — see [Makefile](Makefile) `proto` target for the exact plugins).
|
||||
4. Update `PROJ_NAME` / `SERVICE_NAME` in `api.env` and `api_test.env`, and the Swagger metadata
|
||||
in [cmd/api/main.go](cmd/api/main.go) (`@title`, `@description`, `@contact.email`) and
|
||||
[proto/service_api_tmpl.proto](proto/service_api_tmpl.proto).
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Go 1.20+ (or compatible)
|
||||
- PostgreSQL
|
||||
- Redis
|
||||
- make/migrate/other CLIs (optional for migrations)
|
||||
- Docker & Docker Compose (recommended for local dev)
|
||||
- Go 1.25+
|
||||
- PostgreSQL, Redis (or use `docker-compose.yml` for local instances)
|
||||
- [`sqlc`](https://sqlc.dev), [`golang-migrate`](https://github.com/golang-migrate/migrate),
|
||||
[`air`](https://github.com/air-verse/air) (hot reload) — all optional but expected by the
|
||||
`Makefile`
|
||||
- `protoc` + Go plugins (only needed if you touch `proto/*.proto` — see above)
|
||||
|
||||
---
|
||||
|
||||
## Quickstart (development)
|
||||
|
||||
1. Copy environment example (or use `api.env`):
|
||||
- Main variables you will want to set:
|
||||
- `DB_ADDR` (Postgres connection string)
|
||||
- `REDIS_ADDR` (Redis host:port)
|
||||
- `HTTP_SERVER_ADDR` (default `:8080`)
|
||||
- `GRPC_SERVER_ADDR` (default `:9090`)
|
||||
- `ENVIRONMENT` (e.g. `development` to enable console logs)
|
||||
|
||||
2. Start local infra with Docker Compose (if you want local Postgres + Redis):
|
||||
- (This repo includes a `docker-compose.yml` with Postgres and Redis)
|
||||
- Example:
|
||||
- docker-compose up -d
|
||||
- Confirm Postgres and Redis are reachable at the addresses in your env.
|
||||
|
||||
1. Copy env files and fill in secrets (see [Using this as a template](#using-this-as-a-template)).
|
||||
2. Start local Postgres + Redis:
|
||||
```bash
|
||||
make up
|
||||
```
|
||||
3. Run DB migrations:
|
||||
- Using golang-migrate CLI (example):
|
||||
- migrate -path db/migrations -database "postgres://postgres:postgres@localhost:5432/simple_bank?sslmode=disable" up
|
||||
- Adjust the `database` URL to match your `DB_ADDR` environment variable.
|
||||
```bash
|
||||
make migrate-up
|
||||
```
|
||||
4. Run the server with hot reload:
|
||||
```bash
|
||||
make server
|
||||
```
|
||||
Or without `air`:
|
||||
```bash
|
||||
go run ./cmd/api
|
||||
```
|
||||
5. Verify:
|
||||
- HTTP API: `http://localhost:8080/api/v1/...` (see routes in
|
||||
[internal/handlers/http/server.go](internal/handlers/http/server.go))
|
||||
- Swagger UI: `http://localhost:8080/swagger/index.html`
|
||||
- Redis Commander (from `docker-compose.yml`): `http://localhost:8081`
|
||||
|
||||
4. Download dependencies and build:
|
||||
- go mod download
|
||||
- go build -o simple_bank ./...
|
||||
|
||||
5. Run the service:
|
||||
- With the environment loaded (example):
|
||||
- export $(cat api.env | xargs) # or use your preferred env loader
|
||||
- ./simple_bank
|
||||
- Running the binary will:
|
||||
- start the task processor (asynq server) that consumes background jobs
|
||||
- start gRPC server (on `GRPC_SERVER_ADDR`)
|
||||
- start the HTTP gateway (on `HTTP_SERVER_ADDR`) in a goroutine/handler
|
||||
|
||||
- Alternatively, run directly with `go run`:
|
||||
- go run ./main.go
|
||||
|
||||
6. Verify:
|
||||
- HTTP gateway: http://localhost:8080 (or whatever `HTTP_SERVER_ADDR` you configure)
|
||||
- gRPC: connect at the configured `GRPC_SERVER_ADDR`
|
||||
- Redis dashboard / Redis Commander: if using docker-compose, a redis-commander service may be available
|
||||
By default `main.go` only starts the Gin HTTP server and the Asynq task processor — the gRPC
|
||||
server and grpc-gateway (`runGrpcServer` / `runGatewayServer` in
|
||||
[cmd/api/main.go](cmd/api/main.go)) are implemented but commented out. Uncomment them if you want
|
||||
the gRPC/gateway path running alongside the Gin server.
|
||||
|
||||
---
|
||||
|
||||
## Background Worker (Asynq)
|
||||
## Background worker (Asynq)
|
||||
|
||||
- Tasks are enqueued by the app and processed by an asynq server embedded in the application.
|
||||
- The worker configuration (queues and handlers) lives under `worker/`:
|
||||
- `worker.NewRedisTaskProcessor` creates the asynq server (`asynq.Server`) with queue weights and error handler.
|
||||
- `worker.NewRedisTaskDistributor` creates a client to enqueue tasks.
|
||||
- Example queue names: `CriticalQueue`, `DefaultQueue`.
|
||||
|
||||
Customizing logs and formatting for asynq:
|
||||
- The asynq server can be given a custom logger via `asynq.Config.Logger`. This project includes `worker/logger.go` which adapts logs to `zerolog`. Edit `worker/logger.go` if you want to change asynq log formatting/fields.
|
||||
- If you also want to capture/format other libraries' logs (for example libraries that use the standard `log` package), redirect stdlib logs to your `zerolog` instance at startup.
|
||||
- Tasks are enqueued by the app and processed by an Asynq server embedded in the same binary.
|
||||
- `internal/worker/distributer.go` — enqueues tasks (`TaskDistributor`).
|
||||
- `internal/worker/processor.go` — consumes tasks, registers handlers, configures queue weights.
|
||||
- `internal/worker/task_send_verify_email.go` — example task: sends a verification email after
|
||||
user registration.
|
||||
- `internal/worker/logger.go` adapts Asynq's logger to `zerolog`; edit it to change log
|
||||
formatting.
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables
|
||||
## Environment variables
|
||||
|
||||
Important variables (see `api.env` for full list):
|
||||
See `api.env.example` for the full list with defaults. Notable ones:
|
||||
|
||||
- Server:
|
||||
- `HTTP_SERVER_ADDR` (default `:8080`)
|
||||
- `GRPC_SERVER_ADDR` (default `:9090`)
|
||||
- `ENVIRONMENT` (`development` for console logs)
|
||||
- Database:
|
||||
- `DB_DRIVER` (e.g. `pgx`)
|
||||
- `DB_ADDR` (postgres connection string)
|
||||
- `MIGRATIONS_URL` (e.g. `file://db/migrations`)
|
||||
- Redis:
|
||||
- `REDIS_ADDR` (e.g. `localhost:6379`)
|
||||
- `REDIS_PASSWORD`
|
||||
- `REDIS_DB`
|
||||
- Auth / Tokens:
|
||||
- `AUTH_TOKEN_SECRET`, `AUTH_TOKEN_EXP`, `REFRESH_TOKEN_EXP`
|
||||
- Email:
|
||||
- `EMAIL_FROM`, `EMAIL_SMTP_PASSWORD` (used by the worker email task)
|
||||
- Rate limiter:
|
||||
- `RATE_LIMITER_REQS_COUNT`, `RATE_LIMITER_TIME_FRAME`, `RATE_LIMITER_ENABLED`
|
||||
- Server: `HTTP_SERVER_ADDR`, `GRPC_SERVER_ADDR`, `ENVIRONMENT`, `ALLOWED_ORIGINS`
|
||||
- Database: `DB_ADDR`, `MIGRATIONS_URL`
|
||||
- Redis: `REDIS_ADDR`, `REDIS_PASSWORD`, `REDIS_DB`
|
||||
- Auth: `AUTH_TOKEN_SECRET`, `AUTH_TOKEN_MIN_KEY_SIZE`, `AUTH_TOKEN_EXP`, `REFRESH_TOKEN_EXP`,
|
||||
`AUTH_TOKEN_ISS`, `AUTH_TOKEN_AUD`
|
||||
- Email: `EMAIL_FROM`, `EMAIL_FROM_NAME`, `EMAIL_SMTP_PASSWORD`
|
||||
- Rate limiter: `RATE_LIMITER_REQS_COUNT`, `RATE_LIMITER_TIME_FRAME`, `RATE_LIMITER_ENABLED`
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
- Unit tests:
|
||||
- go test ./...
|
||||
- Integration tests:
|
||||
- Ensure Postgres and Redis are running and configured as in `api.env` before running tests that depend on external services.
|
||||
```bash
|
||||
make test # go test -v -cover -short ./...
|
||||
```
|
||||
|
||||
`internal/db/sqlc` tests hit a real Postgres instance (`make up && make migrate-up` first).
|
||||
Everything else runs against mocks generated with `mockgen` (`make mock`).
|
||||
|
||||
---
|
||||
|
||||
## Development tips
|
||||
## Project layout
|
||||
|
||||
- To change queue weights, error handling or task handlers, edit `worker/processor.go`.
|
||||
- To change how tasks are enqueued, review `worker/distributer.go`.
|
||||
- To adjust logging format globally, modify `main.go` startup logger configuration and `worker/logger.go`.
|
||||
- Protobuf / gRPC: regenerate stubs if you change proto files (this project uses `pb/` and generated code).
|
||||
- `cmd/api/main.go` — application entrypoint (wires config, DB, Redis, servers)
|
||||
- `cmd/db/migrations`, `cmd/db/query` — SQL migrations and queries consumed by `sqlc`
|
||||
- `internal/handlers/http` — Gin HTTP handlers, routes, middleware
|
||||
- `internal/handlers/grpc` — gRPC server + grpc-gateway wiring
|
||||
- `internal/service` — business logic shared by both HTTP and gRPC handlers
|
||||
- `internal/db/sqlc` — generated DB access layer (`Store` interface + queries)
|
||||
- `internal/auth` — PASETO/JWT token makers
|
||||
- `internal/security` — password hashing
|
||||
- `internal/worker` — Asynq task distributor/processor
|
||||
- `internal/mailer` — email sending
|
||||
- `internal/config` — env-based configuration (`viper`)
|
||||
- `internal/val` — request validators
|
||||
- `internal/domain/role` — role constants for RBAC
|
||||
- `pb/` — generated protobuf/gRPC/gateway code (regenerate via `make proto`)
|
||||
- `docs/` — Swagger/OpenAPI specs and the `statik`-embedded swagger UI
|
||||
- `Bruno/` — Bruno collection for manually exercising the API
|
||||
|
||||
---
|
||||
|
||||
## Project layout (high level)
|
||||
## Makefile targets
|
||||
|
||||
- `main.go` — application entrypoint
|
||||
- `api/` — Gin HTTP handlers (HTTP server)
|
||||
- `gapi/` — gRPC server, grpc-gateway wiring
|
||||
- `worker/` — asynq task distributor & processor, custom logger
|
||||
- `db/` — SQLC generated code and migrations
|
||||
- `pb/` — generated protobuf/gRPC code
|
||||
- `docs/statik` — static files (swagger UI) bundled with the binary
|
||||
Run `make <target>`:
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- If migrations fail:
|
||||
- Confirm `DB_ADDR` is correct and database is reachable.
|
||||
- Check that `db/migrations` contains the migration files and paths are correct.
|
||||
- If Redis jobs don't process:
|
||||
- Confirm the app can reach Redis (`REDIS_ADDR`).
|
||||
- Ensure the process that runs the asynq server is running (the app starts it by default).
|
||||
- If logs appear in different formats:
|
||||
- Redirect stdlib logs into `zerolog` or update `worker/logger.go` to include more structured context.
|
||||
| Target | Description |
|
||||
|---|---|
|
||||
| `test` | Run unit/integration tests |
|
||||
| `mock` | Regenerate mocks (`Store`, `TaskDistributor`, services) |
|
||||
| `up` / `down` / `reload` | Manage local Docker Compose infra (Postgres, Redis) |
|
||||
| `server` | Run with hot reload (`air`) |
|
||||
| `migration <name>` | Create a new migration file |
|
||||
| `migrate-up` / `migrate-down` | Apply/rollback migrations |
|
||||
| `sqlc` | Regenerate `internal/db/sqlc` from SQL queries |
|
||||
| `proto` | Regenerate `pb/`, `docs/swagger`, `docs/statik` from `proto/*.proto` |
|
||||
| `dbdocs` / `dbschema` | Generate DB docs / SQL schema from `docs/db.dbml` |
|
||||
| `lint` | Run `golangci-lint` |
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
- Fork and open pull requests.
|
||||
- Keep changes small and focused; add/update unit tests where appropriate.
|
||||
|
||||
---
|
||||
|
||||
If you'd like, I can:
|
||||
- Add a short example curl snippet for the HTTP API endpoints.
|
||||
- Add a sample `docker-compose` snippet and explicit commands for local development.
|
||||
- Add a "Deploy" section with production tips (env secrets, TLS, metrics, etc.).
|
||||
Reference in new issue
Block a user