A backend REST API for managing fund disbursement requests, built with Go. The system supports authentication, disbursement creation, filtering, approval/rejection workflow, deletion of pending requests, pagination, and CSV export.
- JWT authentication with bcrypt password hashing
- Role-based authorization for approval/rejection actions
- Disbursement lifecycle:
PENDING→APPROVED/REJECTED - Pagination, search, and status filtering
- Business-rule validation for disbursement amounts
- CSV export
- Layered architecture: Handler → Service → Repository → MySQL
- Docker Compose for local development
- Automated tests and
go vetthrough GitHub Actions
- Go 1.24+
- Gin
- GORM
- MySQL 8.4
- JWT (
golang-jwt/jwt) - bcrypt
- Docker & Docker Compose
- GitHub Actions
HTTP Request
↓
Handler
↓
Service (business rules)
↓
Repository
↓
GORM
↓
MySQL
.
├── cmd/
│ └── main.go
├── internal/
│ ├── config/
│ │ ├── config.go
│ │ └── seed.go
│ ├── handlers/
│ │ ├── auth.go
│ │ ├── disbursement.go
│ │ ├── response.go
│ │ └── validation.go
│ ├── middleware/
│ │ └── auth.go
│ ├── models/
│ │ └── disbursement.go
│ ├── repository/
│ │ ├── disbursement.go
│ │ └── user.go
│ └── services/
│ ├── auth.go
│ ├── disbursement.go
│ └── disbursement_test.go
├── .env.example
├── .gitignore
├── Dockerfile
├── docker-compose.yaml
├── go.mod
└── README.md
For local development:
- Go 1.24+
- MySQL 8+
For Docker development:
- Docker Desktop
- Docker Compose
This is the recommended way to run the project because MySQL is included in the Compose stack.
-
Clone the repository and enter the directory.
-
Create
.envfrom the example:
cp .env.example .envPowerShell:
Copy-Item .env.example .env-
Open
.envand set a strong random value forJWT_SECRET. -
Build and start the application:
docker compose build
docker compose up -dOr in one command:
docker compose up -d --build- Check running containers:
docker compose psThe API will be available at:
http://localhost:8080
Stop the stack:
docker compose downTo remove the database volume as well:
docker compose down -v- Start MySQL and create the database:
CREATE DATABASE disbursement_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;- Create
.env:
cp .env.example .envPowerShell:
Copy-Item .env.example .env-
Configure the database and JWT secret in
.env. -
Download dependencies:
go mod download- Run the application:
go run ./cmdThe application automatically runs database migrations and seeds the demo users on startup.
Example configuration:
APP_PORT=8080
DB_HOST=mysql
DB_PORT=3306
DB_USER=root
DB_PASSWORD=root
DB_NAME=disbursement_db
JWT_SECRET=replace_with_a_long_random_secret
JWT_EXPIRES_IN_HOURS=24
GIN_MODE=debugJWT_SECRET is required. Do not commit .env or real credentials to the repository.
Login:
POST /api/auth/login
Content-Type: application/jsonRequest:
{
"username": "admin",
"password": "admin123"
}The response contains a JWT token. Send it using the standard Bearer authentication scheme:
Authorization: Bearer <token>The application seeds these demo accounts for local development:
| Username | Password | Role |
|---|---|---|
admin |
admin123 |
admin |
operator |
operator123 |
operator |
These credentials are for local/demo use only.
All disbursement endpoints require a valid JWT unless stated otherwise.
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /api/auth/login |
No | Authenticate user and issue JWT |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/disbursements |
List with pagination/filtering |
| POST | /api/disbursements |
Create a new pending disbursement |
| GET | /api/disbursements/:id |
Get disbursement detail |
| PATCH | /api/disbursements/:id/status |
Approve or reject; admin only |
| DELETE | /api/disbursements/:id |
Delete a pending disbursement |
| GET | /api/disbursements/export |
Export disbursements as CSV |
Default pagination:
GET /api/disbursements?page=1&limit=10Search recipient name:
GET /api/disbursements?search=budiFilter status:
GET /api/disbursements?status=PENDINGCombine filters:
GET /api/disbursements?page=1&limit=10&search=budi&status=PENDINGSupported statuses:
PENDING
APPROVED
REJECTED
POST /api/disbursements
Authorization: Bearer <token>
Content-Type: application/json{
"recipient_name": "Budi",
"bank_code": "BCA",
"account_number": "1234567890",
"amount": 150000,
"note": "Pembayaran reimbursement"
}The minimum disbursement amount is greater than 10,000.
Only users with the admin role can change the status.
Approve:
PATCH /api/disbursements/1/status
Authorization: Bearer <token>
Content-Type: application/json{
"status": "APPROVED",
"note": "Approved"
}Reject:
{
"status": "REJECTED",
"note": "Data rekening tidak valid"
}Only PENDING disbursements can be processed or deleted.
Export all disbursements with a selected status:
GET /api/disbursements/export?status=PENDING
Authorization: Bearer <token>The API returns a CSV attachment.
Success response:
{
"success": true,
"message": "Disbursement berhasil dibuat",
"data": {}
}List response:
{
"data": [],
"meta": {
"page": 1,
"limit": 10,
"total": 25,
"total_pages": 3
}
}Error response:
{
"success": false,
"message": "Request tidak valid",
"errors": {
"field": "pesan validasi"
}
}Format code:
gofmt -w .Run tests:
go test ./...Run static analysis:
go vet ./...GitHub Actions runs automatically for pushes and pull requests targeting main.
The workflow checks:
go mod downloadgo test ./...go vet ./...
- JWT secrets are loaded from environment variables and are not stored in source code.
- Passwords are stored using bcrypt hashes.
- Protected endpoints require a valid JWT.
- Approval/rejection actions require the
adminrole. - User-controlled SQL values are passed through GORM parameter binding.
.envis excluded from Git through.gitignore.
For security-related reports, see SECURITY.md.
This project is intentionally focused on demonstrating backend API development, authentication, authorization, business-rule handling, persistence, pagination, filtering, and export functionality.
Production-scale concerns such as refresh-token rotation, rate limiting, distributed locking, audit logging, observability, and asynchronous payment processing are outside the current scope.
MIT License. See LICENSE for details.