Ziro is a PHP-first API and SPA framework skeleton with a regulated project layout, a bundled asset pipeline, and a cleaner HTTP lifecycle.
Framework boundaries:
public/is the only web rootbootstrap/app.phpbuilds the kernelroutes/api.phpowns API route definitionsapp/contains controllers, middleware, entities, and servicesresources/assets/contains frontend source filessystem/tools/builder/contains the asset compilerdatabase/migrations/is the migration targetstorage/holds cache, logs, and runtime files
Runtime flow:
Ziro\System\Http\Requestnormalizes headers, query params, and JSON bodiesZiro\System\Http\Responsereturns response objects instead of terminating executionZiro\System\Kernelruns global middleware, route middleware, then controller actionsZiro\Middleware\CorsMiddlewarehandles preflight and API CORS headers
Backend prerequisites:
- PHP
^8.0 ext-pdoext-mbstring- Composer
Install backend dependencies:
composer installFrontend prerequisites:
- Node.js 18+
- npm
Install builder dependencies:
cd system/tools/builder
npm install
cd ../../..Create .env from .env.example and define:
APP_NAME=Ziro
APP_ENV=local
APP_DEBUG=false
APP_JWT_SECRET=replace-me
APP_API_KEYS=abc1234,rxyz789
APP_CORS_ALLOW_ORIGIN=*
APP_CORS_ALLOW_METHODS=GET, POST, PUT, PATCH, DELETE, OPTIONS
APP_CORS_ALLOW_HEADERS=Content-Type, Authorization, X-Api-Key, X-Requested-With
APP_CORS_ALLOW_CREDENTIALS=true
DB_PDO_DSN=mysql:host=127.0.0.1;dbname=ziro;charset=utf8mb4;port=3306
DB_USER=root
DB_PASSWORD=Start the PHP server:
zi serveThat serves:
/api/*throughpublic/index.php- static assets from
public/ - SPA routes through
public/index.html
Run the asset pipeline in watch mode:
zi build:assets --devBuild assets once:
zi build:assetsBuild production assets with hashed filenames:
zi build:assets --prodLow-level npm scripts still exist, but they are implementation details for the builder. The preferred framework interface is the zi launcher.
Validate framework layout:
zi structure:validateThe validator enforces required framework paths and rejects legacy misuse such as placing route definitions under app/Routes.
The validator enforces required framework paths and rejects legacy misuse such as keeping API routes in app/Routes/api.php.
Canonical locations:
- API routes:
routes/api.php - bootstrap:
bootstrap/app.php - migrations:
database/migrations - cache:
storage/cache - logs:
storage/logs - frontend sources:
resources/ - compiled assets:
public/
These older conventions should be treated as obsolete:
- PHP
^7.4 || ^8.0in older docs. The actual framework requirement is PHP^8.0. - Defining API routes in
app/Routes/api.php. The canonical route file isroutes/api.php. - Using
php system/core/CLI/zi.php ...as the primary workflow in documentation. The preferred project-root interface iszi .... - Using
npm run dev,npm run build, ornpm run build:prodas the primary framework workflow. The preferred interface iszi build:assets .... - Returning framework responses by directly exiting from helpers or middleware. Controllers and middleware should return
Responseobjects.
The backend was tightened around these rules:
- controllers receive
Requestinstead of reading globals directly - middleware returns
Responseobjects instead of callingexit - JWT verification uses structured parsing and constant-time signature checks
- API keys and CORS behavior come from configuration
- route definitions are separated from application classes
Ziro now includes explicit CORS middleware for browser and third-party clients.
This covers:
- origin policy
- allowed methods
- allowed headers
- credential support
- preflight
OPTIONSrequests
All CORS behavior is configured from .env.
Framework-managed PHP errors and uncaught exceptions are written to:
storage/logs/php-error.log
The bootstrap configures:
log_errors=1error_log=storage/logs/php-error.log- exception logging for uncaught throwables
- fatal shutdown logging for parse/runtime fatal errors
Use APP_DEBUG=true in .env if you want PHP error display enabled during development.
The frontend pipeline compiles resources/assets/ into public/assets/:
- JavaScript is bundled with
esbuild - CSS is flattened and processed through
postcss - templates are copied into
public/assets/templates manifest.jsonrecords output asset paths
Production should preserve the same split:
/api/*->public/index.php- existing files -> serve directly
- other routes ->
public/index.html
Nginx example:
location /api/ {
try_files $uri /index.php?$query_string;
}
location / {
try_files $uri $uri/ /index.html;
}See nginx.conf.example.
zi serve
zi build:assets --dev
zi build:assets --prod
zi make:controller UserController
zi make:model User
zi make:migration create_users_table
zi cache:clear
zi structure:validateuse Ziro\Controllers\Api\AuthController;
use Ziro\Controllers\Api\UserController;
$router->get('/api/v1/user', [UserController::class, 'profile'])
->middleware(['rate_limit']);
$router->post('/api/v1/login', [AuthController::class, 'login'])
->middleware(['json_only', 'rate_limit']);MIT