RedAgos Server is the Laravel API backend for the RedAgos blood request and inventory management system. It provides authentication, user data, and the database foundation for donor profiles, facilities, blood inventory, requests, billing, and payments.
- PHP 8.3+
- Laravel 13
- Laravel Sanctum for API token authentication
- MariaDB or MySQL
- Composer
- PHPUnit for tests
RedAgos_server/
|-- app/
| |-- Http/Controllers/ # API controllers
| `-- Models/ # Eloquent models
|-- database/
| |-- factories/ # Test and seed model factories
| |-- migrations/ # One migration per table
| `-- seeders/ # Development seed data
|-- routes/
| |-- api.php # API routes
| `-- web.php # Web routes
|-- composer.json
`-- README.md
Make sure these are installed and available in your terminal:
php --version
composer --version
mysql --versionRecommended versions:
- PHP 8.3
- Composer 2.x
- MariaDB/MySQL running locally on port
3306
From the server project directory:
cd ~/RedAgos_server
composer install
cp .env.example .env
php artisan key:generateUpdate .env for your local database:
APP_NAME=RedAgos
APP_ENV=local
APP_DEBUG=true
APP_URL=http://127.0.0.1:8000
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=redagos_db
DB_USERNAME=root
DB_PASSWORD=If your MariaDB user does not allow root login from localhost, create a dedicated user and use that in .env:
CREATE DATABASE IF NOT EXISTS redagos_db;
CREATE USER IF NOT EXISTS 'redagos'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON redagos_db.* TO 'redagos'@'localhost';
FLUSH PRIVILEGES;Then update:
DB_USERNAME=redagos
DB_PASSWORD=passwordAfter changing .env, clear cached config:
php artisan config:clearRun migrations:
php artisan migrateSeed the development test user:
php artisan db:seedTo reset all tables and seed again:
php artisan migrate:fresh --seedUse migrate:fresh --seed only when you are okay deleting all existing local data.
The default DatabaseSeeder creates this local test user:
Email: test@example.com
Password: password
The current users table stores names as first_name and last_name, plus username and uuid. Keep the factory, seeder, and model fillable fields aligned with that schema.
Start the Laravel development server:
php artisan serveDefault API base URL:
http://127.0.0.1:8000/api
The Nuxt client should point to this value in its .env:
API_BASE_URL=http://127.0.0.1:8000/apiPOST /api/loginRequest body:
{
"email": "test@example.com",
"password": "password"
}Successful response includes the authenticated user and a Sanctum bearer token:
{
"user": {},
"token": "plain-text-token",
"token_type": "Bearer"
}GET /api/user
Authorization: Bearer <token>This route is protected by Sanctum.
Install dependencies:
composer installRun migrations:
php artisan migrateSeed data:
php artisan db:seedStart server:
php artisan serveRun tests:
php artisan testFormat code with Laravel Pint:
./vendor/bin/pintClear common caches:
php artisan optimize:clearThe blood-unit expiry sweep (inventory:expire-units) moves past-expiry units off the shelf. It is
registered in routes/console.php and runs at 00:30 Asia/Manila.
Installing the scheduler is a release requirement, not an optimisation. If nothing invokes it,
past-expiry units keep reporting as available and the API is confidently wrong about issuable
stock.
Server — one cron entry, which is all Laravel ever needs:
* * * * * cd /path/to/RedAgos_server && php artisan schedule:run >> /dev/null 2>&1On a managed platform (Laravel Cloud and similar), enable that platform's scheduler for the app
instead; it invokes schedule:run on the same minute cadence. Do not add a second cron.
Local development:
php artisan schedule:work # run the scheduler in the foreground
php artisan inventory:expire-units # or run the sweep by handVerify after deploying:
php artisan schedule:list # inventory:expire-units, 30 0 * * *, next due in Manila time
php artisan schedule:test # run a scheduled task on demandVerify it stayed running: the sweep writes an inventory.expiry_swept row to audit_logs on
every run, including ones that expire nothing. The absence of yesterday's row is proof the
scheduler is down, rather than proof it was a quiet day.
Domain migrations are intentionally split into one file per table to follow the Single Responsibility Principle. Keep each migration focused on one table and name it clearly, for example:
2026_07_06_000013_create_blood_requests_table.php
When adding tables with foreign keys, order the migration timestamps so parent tables run before child tables.
- The backend issues Sanctum personal access tokens from
/api/login. - The frontend stores the returned token in
localStorageas_token. - Protected frontend routes should send the token as
Authorization: Bearer <token>.
If MariaDB returns:
SQLSTATE[HY000] [1130] Host 'localhost' is not allowed to connect
Use a valid database user for localhost or 127.0.0.1, then clear Laravel config:
php artisan config:clearThe project user schema does not use a name column. Use:
first_name
last_name
username
uuid
email
password
If this happens while seeding, check database/factories/UserFactory.php, database/seeders/DatabaseSeeder.php, and app/Models/User.php.
Frontend client repository:
../RedAgos_client
Run the client separately with:
cd ~/RedAgos_client
npm run dev