A full-stack microfinance management platform built with a namespaced PHP JSON API backend and a React 18 (Vite) single-page application frontend, backed by a MySQL / MariaDB relational schema.
The application supports the day-to-day operations of a microfinance institution: members, committees, loans and loan payments, installments, savings accounts and transactions, branch management, role-based field-officer workflows, an admin dashboard with KPIs, and personal profile / password management.
The React SPA is served from the same Apache document root as the JSON API (/MicroFinance/), sharing PHP session cookies for authentication.
- Features
- Tech stack
- Architecture
- Project structure
- Getting started
- Database setup
- JSON API
- Frontend routes
- JSON envelope
- CORS
- Deployment
- Testing
- Contributing
- Author
- License
- Create, view, edit, and deactivate members with full demographic data
- Search members by name or member code
- View per-member loans, savings, and recent transactions
- Bulk filter by branch, status, gender, and join date
- Create and edit committees scoped to a branch
- Assign / remove members from a committee
- View committee details and member roster
- Create loans for members with principal, interest rate, and term
- Record loan payments and update loan status (
active,closed, etc.) - Track total disbursed, paid, and outstanding
- Loan health / repayment-progress indicators on the dashboard
- Schedule and record installments
- Savings accounts with deposit / withdraw flows
- Per-member savings history and global savings transaction log
- List of loans with outstanding balances
- Overdue-loan view (loans past maturity)
- Collection reports
- KPI cards: total members, active members, total loans, total paid, total due, total savings, total collection, overdue loans
- Loans-vs-collection line chart for the last 6 months
- Top borrower card
- Recent transactions and recent members lists
- View own profile (full name, username, role, branch, avatar)
- Edit profile (name, phone, email, branch)
- Change password (with current-password verification and minimum length)
- Upload / replace avatar (JPEG / PNG / GIF / WebP)
- Role-gated view: admins and branch managers see the field officers list
- Global search across members, committees, and officers (Topbar)
- Role-specific notification feed in the Topbar
- PHP-native session cookies (no JWT, no tokens)
- Server-side session regeneration on login to prevent fixation
- HTTP-only + SameSite=Lax cookies
- Tri-state React auth context (
loading | unauthenticated | authenticated) - 401 responses globally drop the client into the unauthenticated state
- PHP 8.x with
declare(strict_types=1) mysqlifor database access (no PDO, no ORM)password_hash/password_verify(bcrypt) for credentials- Native PHP sessions with custom cookie params
- A PSR-style
App\namespace autoloaded byspl_autoload_register(backend/app/Bootstrap/autoload.php) - A small custom JSON front controller (
backend/public/index.php) that walks the route manifest inbackend/app/Routes/api.php - Apache
mod_rewritefor clean URLs - No Composer dependencies β the backend is built entirely on core PHP
- React 18.3 + react-router-dom 6.24 (SPA routing,
BrowserRouter basename="/MicroFinance") - Vite 5.3 as the dev server and bundler (
@vitejs/plugin-react) - axios 1.7 with
withCredentials: truefor cookie-based auth - Context API (
AuthContext) + aProtectedRoutewrapper for route gating - Plain CSS in
frontend/src/styles.css(no CSS-in-JS, no styled-components) - CDN-loaded Font Awesome 6, Google Fonts (Inter), and Chart.js at runtime
- Tailwind CSS 4.3 at the project root for the legacy static CSS pipeline
- A standalone Vite entry point (
frontend/installments.html) used as a multi-page-app escape hatch
- MySQL / MariaDB (
MicroFinanceschema) - Connection via
App\Config\Database(env-driven) and the legacybackend/app/Config/db.php
ββββββββββββββββββββββββββββββ session cookie ββββββββββββββββββββββββββββββββββββββββ
β React SPA (Vite, :5173) β ββββββββββββββββββββββββΆ β PHP JSON API (Apache, /MicroFinance) β
β axios, withCredentials β ββββββββββββββββββββββββ β /MicroFinance/backend/public/api β
ββββββββββββββββββββββββββββββ {success, data} ββββββββββββββββββββββββββββββββββββββββ
β
βΌ
MySQL / MariaDB
schema: MicroFinance
- Frontend lives in
frontend/. In development it runs onhttp://localhost:5173and proxies legacy/MicroFinance/api/*.phpand/MicroFinance/uploadscalls to Apache onhttp://localhost. - Backend lives in
backend/. The JSON API is mounted at/MicroFinance/backend/public/api/*via the project-root.htaccessandbackend/public/.htaccess. - A single project-root
.htaccessrewrites/MicroFinance/tofrontend/dist/index.html(the React SPA shell) and forwards/MicroFinance/backend/public/api/*to the PHP front controller, so the SPA and API share one URL space. - Authentication is session-cookie based (PHP native sessions). The React app calls the API with
withCredentials: trueso cookies are shared between the SPA and the JSON API on the same origin.
MicroFinance/
βββ .htaccess β Project-root routing: SPA + API under /MicroFinance/*
βββ package.json β Tailwind toolchain (project-root dev/build)
βββ frontend/ β React 18 + Vite SPA
β βββ .env.example β VITE_API_BASE_URL
β βββ index.html β SPA entry HTML
β βββ installments.html β Standalone Vite entry (multi-page escape hatch)
β βββ package.json
β βββ vite.config.js β Dev server :5173, proxy, build inputs
β βββ src/
β βββ main.jsx β ReactDOM root, BrowserRouter basename, AuthProvider
β βββ App.jsx β Route table
β βββ styles.css β UI styles
β βββ api/ β axios clients (auth, members, loans, committees, savings,
β β installments, due-system, profile, branches, field-officers)
β βββ context/AuthContext.jsx β Session state (loading | unauthenticated | authenticated)
β βββ hooks/useAuth.js
β βββ components/
β β βββ ProtectedRoute.jsx
β β βββ LoadingScreen.jsx
β β βββ layout/ β AppLayout, Sidebar, Topbar
β β βββ members/ β MemberForm, MembersTable, MembersFilters
β β βββ committees/
β β βββ loans/
β β βββ installments/
β βββ pages/ β Page components (see "Frontend routes" below)
β
βββ backend/ β PHP JSON API
β βββ public/
β β βββ index.php β JSON front controller
β β βββ .htaccess β Routes non-files to index.php
β βββ app/
β βββ Bootstrap/
β β βββ autoload.php β spl_autoload_register for App\ namespace
β β βββ bootstrap.php β session, error/exception handlers, CORS
β βββ Config/ β app.php, cors.php, Database.php, db.php
β βββ Controllers/JsonApi/ β 13 namespaced JSON controllers (see "JSON API")
β βββ Middleware/ β AuthMiddleware, CorsMiddleware
β βββ Helpers/ β JsonResponse, Request, Logger
β βββ Routes/api.php β Full route manifest (single source of truth)
β βββ Views/ β Legacy HTML layouts (header, sidebar, topbar, footer)
β βββ Database/
β βββ migrations/ β (empty, scaffolded)
β βββ seeders/ β (empty, scaffolded)
β
βββ api/ β Legacy PHP shims for search/notifications (used by Topbar)
βββ assets/ β Tailwind-built CSS + legacy JS modules
βββ uploads/ β User-uploaded files (avatars)
βββ backend/tests/ β Hand-rolled curl integration tests
β βββ verify_account_api.php
β βββ verify_profile_fixes.php
βββ backend/storage/logs/api.log β API runtime log
- XAMPP (Apache + MySQL / MariaDB), with Apache serving this repository at
http://localhost/MicroFinance/ - PHP 8.x with the
mysqliextension - Node.js 18+ and npm
- A working
MicroFinanceMySQL schema (see Database setup)
No build step. The JSON API is served by Apache directly from backend/public/. The URL surface is:
http://localhost/MicroFinance/backend/public/api
If mod_rewrite is unavailable, the front controller also accepts a ?route= query-string fallback:
http://localhost/MicroFinance/backend/public/index.php?route=/api/dashboard-stats
cd frontend
cp .env.example .env # set VITE_API_BASE_URL if your backend URL differs
npm install
npm run devThe dev server starts on http://localhost:5173. It proxies /MicroFinance/api/*.php (legacy JSON shims) and /MicroFinance/uploads to Apache, so cookies set by the API are visible to the SPA in development.
cd frontend
npm run buildStatic files are emitted to frontend/dist/. Once built, they are served by Apache at http://localhost/MicroFinance/ via the project-root .htaccess.
The repository does not ship SQL migrations or seed files yet β backend/app/Database/migrations/ and backend/app/Database/seeders/ are scaffolded but empty.
Connection defaults (env-driven) are defined in backend/app/Config/Database.php:
| Variable | Default |
|---|---|
DB_HOST |
127.0.0.1 |
DB_PORT |
3306 |
DB_USER |
root |
DB_PASSWORD |
(empty) |
DB_DATABASE |
MicroFinance |
The legacy procedural controller connection (backend/app/Config/db.php) hardcodes the same defaults and sets the timezone to Asia/Dhaka.
Tables referenced by the application (inferred from queries in the codebase): users, branches, committees, members, loans, loan_payments, loan_installments, savings, savings_transactions. See PROJECT_OVERVIEW.md for the inferred schema.
The users table is expected to contain at least the columns used by the auth controller: user_id, username, password_hash, full_name, role, is_active, avatar.
The full route manifest lives in backend/app/Routes/api.php. Controllers are namespaced App\Controllers\JsonApi\* and are PSR-style autoloaded from backend/app/Controllers/JsonApi/.
| Method | Path | Controller | Action | Auth |
|---|---|---|---|---|
POST |
/api/auth/login |
AuthController |
login | |
POST |
/api/auth/logout |
AuthController |
logout | β |
GET |
/api/auth/session |
AuthController |
session | |
GET |
/api/dashboard-stats |
DashboardController |
stats | β |
GET |
/api/profile |
ProfileController |
show | β |
PUT |
/api/profile |
ProfileController |
update | β |
POST |
/api/profile/avatar |
ProfileController |
uploadAvatar | β |
PUT |
/api/profile/password |
ProfileController |
changePassword | β |
GET |
/api/branches |
BranchesController |
index | β |
GET |
/api/field-officers |
FieldOfficersController |
index | β |
GET |
/api/field-officers/summary |
FieldOfficersController |
summary | β |
GET |
/api/search |
SearchController |
index | β |
GET |
/api/notifications |
NotificationsController |
index | β |
POST |
/api/notifications/{id}/read |
NotificationsController |
markRead | β |
GET |
/api/members |
MembersController |
index | β |
POST |
/api/members |
MembersController |
store | β |
GET |
/api/members/search |
MembersController |
search | β |
GET |
/api/members/{id} |
MembersController |
show | β |
PUT |
/api/members/{id} |
MembersController |
update | β |
DELETE |
/api/members/{id} |
MembersController |
destroy | β |
GET |
/api/members/{id}/transactions |
MembersController |
transactions | β |
GET |
/api/members/{id}/loans |
MembersController |
loans | β |
GET |
/api/members/{id}/savings |
MembersController |
savings | β |
GET |
/api/committees |
CommitteesController |
index | β |
POST |
/api/committees |
CommitteesController |
store | β |
GET |
/api/committees/{id} |
CommitteesController |
show | β |
PUT |
/api/committees/{id} |
CommitteesController |
update | β |
DELETE |
/api/committees/{id} |
CommitteesController |
destroy | β |
GET |
/api/committees/{id}/members |
CommitteesController |
members | β |
POST |
/api/committees/{id}/members |
CommitteesController |
addMember | β |
DELETE |
/api/committees/{id}/members/{memberId} |
CommitteesController |
removeMember | β |
GET |
/api/loans |
LoansController |
index | β |
POST |
/api/loans |
LoansController |
store | β |
GET |
/api/loans/{id} |
LoansController |
show | β |
PUT |
/api/loans/{id} |
LoansController |
update | β |
DELETE |
/api/loans/{id} |
LoansController |
destroy | β |
POST |
/api/loans/{id}/status |
LoansController |
updateStatus | β |
POST |
/api/loans/{id}/payments |
LoansController |
recordPayment | β |
GET |
/api/installments |
InstallmentsController |
index | β |
POST |
/api/installments |
InstallmentsController |
store | β |
GET |
/api/installments/{id} |
InstallmentsController |
show | β |
PUT |
/api/installments/{id} |
InstallmentsController |
update | β |
DELETE |
/api/installments/{id} |
InstallmentsController |
destroy | β |
GET |
/api/savings |
SavingsController |
index | β |
POST |
/api/savings |
SavingsController |
store | β |
GET |
/api/savings/member/{memberId} |
SavingsController |
byMember | β |
POST |
/api/savings/deposits |
SavingsController |
deposit | β |
POST |
/api/savings/withdrawals |
SavingsController |
withdraw | β |
GET |
/api/savings/transactions |
SavingsController |
transactions | β |
GET |
/api/savings/{id} |
SavingsController |
show | β |
PUT |
/api/savings/{id} |
SavingsController |
update | β |
DELETE |
/api/savings/{id} |
SavingsController |
destroy | β |
GET |
/api/due-system |
DueSystemController |
index | β |
GET |
/api/due-system/overdue |
DueSystemController |
overdue | β |
GET |
/api/due-system/report |
DueSystemController |
report | β |
GET |
/api/due-system/{id} |
DueSystemController |
show | β |
POST |
/api/due-system/{id}/collect |
DueSystemController |
collect | β |
Static sub-paths (/api/savings/member/{id}, /api/savings/deposits, /api/savings/transactions, /api/due-system/overdue, /api/due-system/report, /api/members/search) are matched before the {id} placeholders so they are not captured as IDs.
All endpoints marked with β
require an authenticated session. Requests without a valid session cookie receive 401 UNAUTHORIZED. The session cookie is set on POST /api/auth/login and cleared on POST /api/auth/logout.
The React Topbar also calls two legacy .php shims that bypass the namespaced API:
GET /MicroFinance/api/search.php?q=β¦GET /MicroFinance/api/notifications.php
These are thin wrappers around the controllers in backend/app/Controllers/Api/ and are proxied to Apache by the Vite dev server.
The full route table lives in frontend/src/App.jsx. All routes except /login are gated by <ProtectedRoute> (which redirects to /login while unauthenticated).
| Path | Page |
|---|---|
/login |
LoginPage |
/ |
DashboardPage |
/installments |
InstallmentsPage |
/members |
MembersPage |
/members/new |
MemberFormPage (create) |
/members/:id |
MemberProfilePage |
/members/:id/edit |
MemberFormPage (edit) |
/committees |
CommitteesPage |
/committees/new |
CommitteeFormPage (create) |
/committees/:id |
CommitteeViewPage |
/committees/:id/edit |
CommitteeFormPage (edit) |
/committees/:id/members |
CommitteeMembersPage |
/loans |
LoansPage |
/loans/new |
LoanFormPage (create) |
/loans/:id |
LoanViewPage |
/loans/:id/edit |
LoanFormPage (edit) |
/loans/:id/payment |
LoanPaymentPage |
/savings |
SavingsPage |
/savings/new |
SavingsFormPage |
/savings/deposit |
SavingsTransactionPage (deposit) |
/savings/withdraw |
SavingsTransactionPage (withdraw) |
/savings/transactions |
SavingsTransactionsPage |
/due-system |
DueSystemPage |
/due-system/overdue |
DueSystemOverduePage |
/due-system/report |
DueSystemReportPage |
/profile |
ProfilePage |
/profile/edit |
EditProfilePage |
/profile/change-password |
ChangePasswordPage |
/field-officers |
FieldOfficersPage |
* |
NotFoundPage |
Because the SPA is served from /MicroFinance/ with BrowserRouter basename="/MicroFinance", all of the above are reachable at http://localhost/MicroFinance/<route>.
Every API response uses a uniform envelope.
Success:
{
"success": true,
"data": { "...": "..." },
"meta": {}
}Failure:
{
"success": false,
"error": {
"code": "INVALID_CREDENTIALS",
"message": "Invalid username or password.",
"details": {}
}
}Standard error codes: UNAUTHORIZED, VALIDATION_ERROR, NOT_FOUND, METHOD_NOT_ALLOWED, INVALID_CREDENTIALS, NOT_IMPLEMENTED, INTERNAL_SERVER_ERROR.
The PHP error and exception handlers in backend/app/Bootstrap/bootstrap.php convert PHP errors into ErrorExceptions and return a 500 INTERNAL_SERVER_ERROR JSON response β never raw HTML.
The API is reachable from the React dev server with credentialed cookies. The allowed-origin list lives in backend/app/Config/cors.php and defaults to:
http://localhost
http://localhost:80
http://localhost:5173
http://localhost:3000
http://127.0.0.1
http://127.0.0.1:80
http://127.0.0.1:5173
http://127.0.0.1:3000
The list can be overridden with the CORS_ALLOWED_ORIGINS environment variable.
CorsMiddleware emits:
Access-Control-Allow-Origin: <origin>
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type, X-Requested-With
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Vary: Origin
OPTIONS preflight is short-circuited with 204 No Content.
The intended deployment shape is:
- Place the repository under the Apache document root at
MicroFinance/so the URLs match the routes (/MicroFinance/,/MicroFinance/backend/public/api,/MicroFinance/assets/...). - Ensure
mod_rewriteis enabled andAllowOverride Allis set on the document root. - Build the SPA:
cd frontend && npm install && npm run build. The output goes tofrontend/dist/. - Configure the MySQL/MariaDB connection (
DB_HOST,DB_USER,DB_PASSWORD,DB_DATABASE,DB_PORT) via environment variables read byApp\Config\Database. - Configure
CORS_ALLOWED_ORIGINSif the SPA is hosted on a different origin than the API. - The project-root
.htaccessalready wires everything together: it servesfrontend/dist/index.htmlfor SPA routes, forwards/MicroFinance/backend/public/api/*to the PHP front controller, and serves/MicroFinance/assets/*fromfrontend/dist/assets/.
A single Apache VirtualHost is sufficient β there is no separate reverse proxy or build step required once npm run build has been run.
The backend/tests/ directory contains two hand-rolled curl-based integration scripts:
backend/tests/verify_account_api.phpβ exercises login β session β profile β profile update β password change.backend/tests/verify_profile_fixes.phpβ exercises the profile page, edit profile, avatar upload, field-officers role gating, and branches endpoint.
Both scripts echo a PASS: x, FAIL: y summary on completion. They are invoked from the command line, not through PHPUnit:
php backend/tests/verify_account_api.php
php backend/tests/verify_profile_fixes.phpPHPUnit test scaffolding is not present.
- Fork the repository.
- Create a topic branch:
git checkout -b feat/<short-description>. - Make your changes. Keep the JSON envelope and the route-manifest style consistent.
- Run the integration scripts in
backend/tests/to verify nothing regressed. cd frontend && npm run buildto confirm the SPA builds cleanly.- Open a pull request describing the change and linking any related issues.
Please do not commit frontend/dist/, frontend/node_modules/, or backend/storage/logs/*.log β these are already in .gitignore.
Md Munna Sardar β github.com/Mdmunnasardar
ISC β see the project package.json for the declared license. A standalone LICENSE file is not yet bundled with the repository.