JiFramework is a lightweight, plug-and-play PHP framework built for developers who want to move fast without sacrificing structure. The philosophy is simple: include the autoloader, instantiate App, and everything is ready.
Why JiFramework?
- Zero boilerplate — two lines of code to have a fully working application with database, auth, cache, and logging ready.
- No template engine required — use plain PHP files as views. No learning curve, no extra dependencies.
- No CLI tools — everything works out of the box. No commands to run, no code generation steps.
- Lazy loading — components are only created when you access them. Unused services cost nothing.
- Powerful built-in components — QueryBuilder, Auth, Session, Cache, Validation, Rate Limiting, Encryption, File Manager, Router, Model layer, and more.
- Flexible routing — optional URL router with file-based or closure handlers. Use it or skip it.
- Production ready — structured logging, error handling, CSRF protection, IP/country blocking, and rate limiting included.
Requirements:
- PHP 7.4 or higher
- Composer (for autoloading)
- MySQL / MariaDB (optional — only needed if using database features)
The entire bootstrap is two lines:
<?php
require __DIR__ . '/vendor/autoload.php';
$app = new App();
App::__construct() automatically:
- Detects the project root directory
- Loads
config/jiconfig.phpif it exists - Initialises the error handler and logger
- Applies rate limiting and access control
No manual Config::initialize() call is needed.
Copy the example config to get started:
cp vendor/jiframework/jiframework/config/jiconfig.example.php config/jiconfig.php
The file must be placed at config/jiconfig.php relative to your project root (next to composer.json).
<?php
// config/jiconfig.php
return [
'app_mode' => 'development', // 'development' or 'production'
'timezone' => 'UTC',
'database' => [
'host' => 'localhost',
'dbname' => 'my_database',
'username' => 'root',
'password' => '',
'charset' => 'utf8mb4',
],
];
If the file is absent, the framework uses safe defaults and logs a warning to storage/Logs/app.log.
Recommended project layout:
your-project/
|-- config/
| `-- jiconfig.php (your configuration)
|-- models/ (optional: auto-loaded at boot)
| `-- User.php
|-- pages/ (optional: router file handlers)
| `-- home.php
|-- storage/ (auto-created by the framework)
| |-- Cache/
| |-- Logs/
| `-- RateLimit/
|-- vendor/
|-- composer.json
`-- index.php (your entry point)
The storage/ directory is created automatically. You do not need to create it manually.
Full list of supported jiconfig.php keys. You only need to include the keys you want to override — everything else falls back to the framework defaults.
App
app_mode— (string)developmentorproduction. Default:developmentadmin_email— (string) Admin contact address. Default:[email protected]timezone— (string) PHP timezone identifier. Default:UTCerror_template— (string|null) Absolute path to a custom HTML error page template. The variables$errorCodeand$errorMessageare available inside it. Default:null(uses built-in page)
Database
database— (array) Primary connection:host,database,username,password,driver,charset,collation,optionsdatabases— (array) Named extra connections keyed by name. Access via$app->db('name')
Router
router_enabled— (bool) Enable the URL router. Default:falserouter_base_path— (string) Base path for subdirectory installs, e.g./myapp. Default:""
Cache
cache_driver— (string)fileorsqlite. Default:filecache_path— (string) Custom path for file cache. Default:storage/Cache/FileCache/cache_database_path— (string) Custom path for SQLite cache file. Default:storage/Cache/DatabaseCache/ji_sqlite_cache.db
File Uploads
upload_directory— (string) Custom upload path. Default:storage/Uploads/allowed_image_types— (array) Accepted MIME types. Default:[image/jpeg, image/png, image/gif]max_file_size— (int) Max upload size in bytes. Default:5242880(5 MB)image_max_dimension— (int) Max image width/height in px. Default:800
Rate Limiting
rate_limit_enabled— (bool) Toggle rate limiting. Default:falserate_limit_requests— (int) Max requests per window. Default:500rate_limit_time_window— (int) Window length in seconds. Default:60rate_limit_ban_enabled— (bool) Ban repeat offenders. Default:truerate_limit_ban_duration— (int) Ban duration in seconds. Default:3600rate_limit_database_path— (string) Custom path for rate limit SQLite file. Default:storage/RateLimit/rate_limit.db
Logging
log_enabled— (bool) Enable file logging. Default:truelog_level— (string) Minimum level:DEBUG|INFO|NOTICE|WARNING|ERROR|CRITICAL|ALERT|EMERGENCY. Default:DEBUGlog_file_name— (string) Log filename. Default:app.loglog_file_path— (string) Custom log directory. Default:storage/Logs/log_max_file_size— (int) Max log file size in bytes before rotation. Default:5242880(5 MB)log_max_files— (int) Number of rotated log files to keep. Default:20
Access Control
ip_blocking_enabled— (bool) Block specific IPs. Default:falseip_block_list_path— (string) Path to IP block list JSON. Default:storage/AccessControl/ip_block_list.jsoncountry_blocking_enabled— (bool) Block by country code. Default:falsecountry_block_list_path— (string) Path to country block list JSON. Default:storage/AccessControl/country_block_list.jsonallow_vpn_proxy— (bool) Allow VPN/proxy traffic. Default:trueproxycheck_api_key— (string) API key for proxycheck.io. Default:""
IP Detection
These two keys control how the framework resolves the client IP address on every request. The resolved IP is used across the entire framework — by the rate limiter, access control, and any call to $app->request->getClientIp().
trusted_proxies— (array) IP addresses of trusted reverse proxies (load balancers, CDNs, Nginx). WhenREMOTE_ADDRmatches one of these, the real client IP is read fromX-Forwarded-For. Leave empty (default) when your PHP server receives connections directly. Default:[]debug_ip— (string|null) Override the detected client IP for local development. Useful for testing rate limiting and access control on localhost whereREMOTE_ADDRis always127.0.0.1. Only honoured whenapp_mode = development— silently ignored in production. Default:null
// Running behind Nginx or a load balancer:
'trusted_proxies' => ['127.0.0.1', '10.0.0.1'],
// Testing IP-based features on localhost:
'app_mode' => 'development',
'debug_ip' => '203.0.113.42',
Multi-Language
multi_lang— (bool) Enable multi-language support. Default:falsemulti_lang_method— (string) Detection method:urlorcookie. Default:urlmulti_lang_default_lang— (string) Fallback language code. Default:enmulti_lang_key— (string) URL/cookie key name. Default:langmulti_lang_dir— (string) Custom translations directory. Default:lang/
Session & CSRF
session_user_key— (string) Session key for user auth data. Default:_ji_user_sessionsession_admin_key— (string) Session key for admin auth data. Default:_ji_admin_sessioncsrf_token_key— (string) Session key for CSRF token store. Default:_ji_csrf_tokenflash_message_key— (string) Session key for flash messages. Default:_ji_flash_messagescsrf_token_expiry— (int) CSRF token lifetime in seconds. Default:43200(12 hours)csrf_token_limit— (int) Max simultaneous CSRF tokens per session. Default:100csrf_token_length— (int) Random bytes used to generate each token. Default:32
Auth Tables
auth_admin_table— (string) Admin table name. Default:adminauth_user_table— (string) Users table name. Default:usersauth_token_table— (string) Remember-me tokens table. Default:tokens
The framework ships with a ready-to-use .htaccess file at the project root. The right configuration depends on whether you are using router mode or file-based mode.
Router mode ON — recommended .htaccess
When router_enabled = true, all unknown requests must be funnelled through index.php. The file below handles routing and security in one place. This is the file that ships with the framework — you do not need to change anything.
# Disable directory listing
Options -Indexes
RewriteEngine On
# Block direct access to sensitive directories
RewriteRule ^(storage|src|config)/ - [F,L]
# Send all non-file, non-directory requests to index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [QSA,L]
# Block SQLite database files
<FilesMatch "\.db$">
Require all denied
</FilesMatch>
# Block cache files
<FilesMatch "\.cache$">
Require all denied
</FilesMatch>
# Block hidden files (.env, .gitignore, .htpasswd, etc.)
<FilesMatch "^\.(?!htaccess)">
Require all denied
</FilesMatch>
Static assets (CSS, JS, images) are served directly by Apache — only requests for paths that do not match a real file or directory are forwarded to index.php.
Router mode OFF — security-only .htaccess
When router_enabled = false (the default), PHP files are accessed directly by the browser. You do not need the routing rewrite rules, but you should still protect sensitive files and directories. Replace the shipped .htaccess with this version:
# Disable directory listing
Options -Indexes
RewriteEngine On
# Block direct access to sensitive directories
RewriteRule ^(storage|src|config)/ - [F,L]
# Block SQLite database files
<FilesMatch "\.db$">
Require all denied
</FilesMatch>
# Block cache files
<FilesMatch "\.cache$">
Require all denied
</FilesMatch>
# Block hidden files (.env, .gitignore, .htpasswd, etc.)
<FilesMatch "^\.(?!htaccess)">
Require all denied
</FilesMatch>
What each rule does
| Rule | Why it matters |
|---|---|
Options -Indexes |
Disables directory listing. Without this, Apache shows folder contents when no index file exists — exposing filenames, structure, and potentially sensitive files. |
^(storage|src|config)/ — [F,L] |
Blocks direct browser access to storage/ (logs, uploads, rate-limit DB), src/ (framework source), and config/ (jiconfig.php with database credentials). Returns 403 Forbidden. |
\.db$ |
Blocks download of SQLite .db files used by the rate limiter. Without this rule, anyone could download the database file directly. |
\.cache$ |
Blocks access to .cache files written by the file cache driver. |
^\.(?!htaccess) |
Blocks all hidden files (those starting with a dot) — .env, .gitignore, .htpasswd, etc. Critical for preventing credential leaks. .htaccess itself is excluded so Apache can read the rules without triggering a 403 loop. |
Nginx equivalent
If your server runs Nginx instead of Apache, add the following to your server block:
server {
# ... other config ...
# Block sensitive directories
location ~* ^/(storage|src)/ {
deny all;
}
# Block sensitive file types
location ~* \.(db|cache)$ {
deny all;
}
# Block hidden files
location ~ /\. {
deny all;
}
# Router mode ON only -- send all requests to index.php
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
}