REST API for managing users, books, borrowing, and returns in a small library system. Built with Clean Architecture, role-based access control, and database-level invariants.
- JWT authentication with role-based access for
admin,librarian, andmember - Book CRUD with search, sorting, and pagination
- Borrow and return flow with transaction boundary in the service layer
- Stock and active-borrow invariants enforced in PostgreSQL
- Unit, integration, and E2E test layers
- GitHub Actions quality gate for lint and unit tests
- Go 1.24
- Gin (HTTP framework)
- GORM (ORM)
- PostgreSQL (with pg_trgm for search)
- Zerolog (structured logging)
- JWT (golang-jwt/v5)
- Testify + SQLMock (testing)
- golangci-lint (code quality)
- Docker + docker-compose
┌──────────────────────────────────────────────────┐
│ HTTP Layer │
│ Router → Middleware (Auth, RBAC) → Handler │
├──────────────────────────────────────────────────┤
│ Service Layer │
│ Business logic + transaction boundaries │
├──────────────────────────────────────────────────┤
│ Repository Layer │
│ GORM queries + database invariants │
├──────────────────────────────────────────────────┤
│ PostgreSQL (constraints, indexes) │
└──────────────────────────────────────────────────┘
library-management-api/
├── cmd/api/ # Application entrypoint
├── configs/ # Configuration loader
├── docs/adr/ # Architecture Decision Records
├── internal/
│ ├── dto/ # Request/response data transfer objects
│ ├── handler/ # HTTP handlers
│ ├── middleware/ # Auth, RBAC middleware
│ ├── models/ # Domain models
│ ├── repository/ # Data access layer
│ └── service/ # Business logic
├── pkg/ # Shared packages (auth, database, errors, response)
├── tests/
│ ├── unit/ # Unit tests (handlers, services, repos)
│ ├── integration/ # DB-backed + concurrency tests
│ └── e2e/ # End-to-end API tests
├── Dockerfile
├── docker-compose.yml
└── Makefile
cp .env.example .envmake docker-upmake runAPI base URL:
http://localhost:8080
Health endpoints:
GET /health
GET /ready
| Variable | Default | Notes |
|---|---|---|
APP_PORT |
8080 |
API port |
DB_HOST |
localhost |
PostgreSQL host |
DB_PORT |
5432 |
PostgreSQL port |
DB_USER |
postgres |
PostgreSQL user |
DB_PASSWORD |
password |
PostgreSQL password |
DB_NAME |
library_db |
PostgreSQL database |
DB_SSLMODE |
disable |
PostgreSQL SSL mode |
JWT_SECRET |
your-super-secret-jwt-key-change-in-production |
JWT signing secret |
JWT_EXPIRY |
24h |
Token expiry |
READ_TIMEOUT |
10s |
HTTP read timeout |
WRITE_TIMEOUT |
10s |
HTTP write timeout |
IDLE_TIMEOUT |
60s |
HTTP idle timeout |
MAX_BOOKS_PER_USER |
5 |
Borrow limit per user |
BORROW_DAYS |
14 |
Default due date offset |
FINE_PER_DAY |
1000 |
Overdue fine per day |
| Method | Path | Description |
|---|---|---|
POST |
/api/v1/auth/register |
Register a user |
POST |
/api/v1/auth/login |
Login and get JWT |
GET |
/health |
Liveness check |
GET |
/ready |
Readiness check with DB ping |
| Method | Path | Description |
|---|---|---|
GET |
/api/v1/books |
List books |
GET |
/api/v1/books/:id |
Get book detail |
POST |
/api/v1/books |
Create book (admin, librarian) |
PUT |
/api/v1/books/:id |
Update book (admin, librarian) |
DELETE |
/api/v1/books/:id |
Delete book (admin, librarian) |
POST |
/api/v1/borrow |
Borrow a book |
POST |
/api/v1/borrow/return |
Return a book |
GET |
/api/v1/borrow/my-books |
List current user borrows |
GET |
/api/v1/borrow/active |
List active borrows (admin, librarian) |
GET |
/api/v1/borrow/overdue |
List overdue borrows (admin, librarian) |
Success shape:
{
"success": true,
"message": "optional message",
"data": {},
"meta": {}
}Error shape:
{
"success": false,
"message": "book not found",
"error": {
"code": "not_found",
"message": "book not found"
}
}List query params:
| Param | Description |
|---|---|
page |
Positive integer, default 1 |
limit |
Positive integer, clamped per endpoint |
search |
Optional search string |
sort |
Optional endpoint-specific sort key |
List meta:
{
"page": 1,
"limit": 10,
"total": 42,
"total_pages": 5
}| Command | Description |
|---|---|
make run |
Run API locally |
make build |
Build binary |
make test |
Run unit and integration tests |
make test-unit |
Run unit tests |
make test-integration |
Run integration tests |
make test-e2e |
Run E2E tests |
make lint |
Run golangci-lint |
make vet |
Run go vet |
make quality |
Run lint, vet, and unit tests |
make docker-up |
Start PostgreSQL and pgAdmin |
make docker-down |
Stop Docker services |
| Layer | Command | Purpose |
|---|---|---|
| Unit | go test ./tests/unit/... -v |
Fast feedback for handlers, services, repositories, middleware |
| Integration | go test ./tests/integration/... -v |
DB-backed behavior and concurrency checks |
| E2E | go test ./tests/e2e/... -v |
Happy path against a running API |
- API server must already be running
- Database must already be available
- Default E2E base URL is
http://localhost:8080 - Override with
E2E_BASE_URL=http://host:portif needed
GitHub Actions workflow:
.github/workflows/quality-gates.yml
Required PR jobs:
lintunit-test
Optional manual job:
integration-test
- Changelog: CHANGELOG.md
- Release checklist: RELEASE.md
- PostgreSQL-specific constraints and indexes are applied only when the dialector is PostgreSQL.
pg_trgmis enabled gracefully. If extension creation fails, the app continues without trigram indexes.- Integration concurrency test reference: tests/integration/borrow_concurrency_test.go