marcuwynu23 / narciso
'Narciso' is a web library built on top of native PHP, designed to provide developers with a set of tools and functionalities to simplify and enhance web development tasks. It aims to streamline common web development processes such as handling HTTP requests, managing sessions, accessing databases,
Requires (Dev)
- phpunit/phpunit: ^9.5 || ^10.0
README
Lightweight PHP web library. Routing, middleware, CORS, rate limiting, security headers, and database — no framework required.
Table of Contents
- What Is Narciso?
- Use Cases
- Benefits
- Advantages Over Other Tools
- User Guide
- Installation
- Quick Start
- API Reference
- Configuration
- Example Output
- CI/CD Integration
- Development
- Architecture
- Contributing
- License
What Is Narciso?
Narciso is a lightweight PHP web library built on top of native PHP, inspired by FastAPI and Flask. It gives you a simple, expressive API for routing, middlewares, CORS, rate limiting, security headers, and database access — so you can build APIs and web apps quickly without a heavy framework.
What It Does
- Route — Map HTTP methods and URL patterns (including path parameters like
/users/:id) to handler callbacks - Protect — Add security headers, CORS, and rate limiting with one-line method calls
- Connect — Configure MySQL or SQLite in a single config array;
$app->dbis ready in every route - Respond — Return JSON, XML (auto-detected from query/header), or render PHP views
- Obfuscate — Hide or fake the
X-Powered-Byheader to keep the server technology private - Extend — Add custom middleware via a callable or the
MiddlewareInterface - Session — Start PHP sessions with
handleSession()
Why Use It?
| Problem | How Narciso Solves It |
|---|---|
| Heavy frameworks require boilerplate | Narciso is ~500 lines of PHP; drop it in and start coding |
| CORS and security headers are tedious | One call: $app->useCors([...]), $app->useSecurityHeaders() |
| Rate limiting needs middleware setup | $app->useRateLimit(100, 60) — 1 line, in-memory, per-IP |
| Database config varies between MySQL/SQLite | Single handleDatabase([...]) with type: mysql or type: sqlite |
| API format negotiation is boilerplate | $app->sendAPI($data) auto-detects JSON vs XML from Accept header or ?format= |
| Server signature leaks tech stack | $app->setPoweredBy(false) removes X-Powered-By entirely |
The Philosophy
- Minimal setup, maximum value. A working API server in 10 lines of code.
- Your process stays yours. No forced directory structure, no service container, no DI. Just PHP.
- Native-first. Built on
mysqli,SQLite3, and native PHP sessions — no runtime dependencies.
Use Cases
| Scenario | How Narciso Helps |
|---|---|
| JSON API backend | Define routes with $app->route(), return JSON with $app->json() or $app->sendAPI(). CORS and rate limiting built in. |
| Rapid prototype | Install via Composer, write a single PHP file, run with php -S. Zero config. |
| Microservice | Lightweight enough to deploy as a standalone service. Add security headers and rate limiting in two lines. |
| Simple web app with database | Connect MySQL or SQLite with handleDatabase(), render views with render(). |
| API gateway / proxy | Use the middleware pipeline to add logging, auth, and rate limiting before proxying requests. |
| Learning tool | Read the ~500-line source to understand how routing, middleware, and request handling work in PHP. |
Benefits for Developers
- ~10 second setup —
composer require marcuwynu23/narciso - No runtime dependencies — Zero Composer dependencies at runtime
- Familiar API — Inspired by Flask and FastAPI; route handlers receive
($app, $params) - Path parameters —
/users/:idsyntax like Express.js - Middleware onion — Add as many middlewares as needed; they wrap in order
- JSON/XML auto-detection —
sendAPI()readsAcceptheader or?format=query param - Built-in security — Security headers, CORS, rate limiting ship with the library
- Technology obfuscation — Remove or fake
X-Powered-BywithsetPoweredBy() - PHP 7.4+ compatible — Works on legacy and modern PHP
- Fully tested — PHPUnit suite with 36+ tests covering routing, middleware, database, and API
Advantages Over Other Tools
| Aspect | Narciso | Laravel | Slim | Symfony | Handwritten |
|---|---|---|---|---|---|
| Setup time | ~10 seconds | Minutes | ~30 seconds | Minutes | Ongoing effort |
| Runtime dependencies | 0 | 50+ | 5 | 80+ | 0 |
| Learning curve | Low | High | Medium | High | N/A |
| File size (source) | ~500 lines | 10,000s | ~2,000 | 100,000s | Varies |
| Database abstraction | mysqli / SQLite3 | Eloquent | PDO | Doctrine | Custom |
| CORS middleware | Built-in | Package | Package | Bundle | Custom |
| Rate limiting | Built-in | Package | Package | Bundle | Custom |
| Security headers | Built-in | Middleware | Package | Bundle | Custom |
| JSON/XML auto-detect | Built-in | Manual | Manual | Manual | Custom |
| Path parameters | :param |
Route params | {param} |
{param} |
Custom |
| Middleware interface | Yes | Yes | Yes | Yes | Custom |
| Template engine | PHP includes | Blade | Twig/Plates | Twig | Custom |
| CLI tooling | No | Artisan | No | Maker | N/A |
| ORM | No | Eloquent | Optional | Doctrine | Custom |
| License | Apache 2.0 | MIT | MIT | MIT | Your choice |
Installation
composer require marcuwynu23/narciso
Requires PHP 7.4+ and the json extension. For MySQL use, install php-mysql. For SQLite, install php-sqlite3.
Verify:
php -r "require 'vendor/autoload.php'; echo class_exists(Marcuwynu23\\\Narciso\\\Application::class) ? 'OK' : 'FAIL';"
Quick Start
Create index.php:
<?php require_once __DIR__ . '/vendor/autoload.php'; use Marcuwynu23\Narciso\Application; $app = new Application(); $app->route('GET', '/', function ($app) { $app->json(['message' => 'Hello World']); }); $app->route('GET', '/users/:id', function ($app, $params) { $app->json(['user_id' => $params['id']]); }); $app->run();
Run it:
php -S localhost:8080 index.php
Test it:
curl http://localhost:8080/ curl http://localhost:8080/users/42
API Reference
route(string $method, string $path, callable $handler)
Register a route handler.
| Parameter | Default | Description |
|---|---|---|
$method |
— | HTTP method (GET, POST, PUT, DELETE, PATCH, etc.) |
$path |
— | URL pattern with optional :param segments |
$handler |
— | function($app, $params) callback |
use($middleware)
Add a middleware to the pipeline.
| Parameter | Default | Description |
|---|---|---|
$middleware |
— | Callable ($app, $next) or object implementing MiddlewareInterface |
useSecurityHeaders(?array $headers = null)
| Flag | Default | Description |
|---|---|---|
$headers |
null |
Custom headers array or null for defaults |
useCors(array $origins, array $methods, array $headers, bool $credentials, int $maxAge)
| Flag | Default | Description |
|---|---|---|
$origins |
['*'] |
Allowed origins |
$methods |
['GET','POST','PUT','PATCH','DELETE','OPTIONS'] |
Allowed HTTP methods |
$headers |
['Content-Type','Authorization'] |
Allowed request headers |
$credentials |
false |
Allow credentials |
$maxAge |
86400 |
Preflight cache in seconds |
useRateLimit(int $maxRequests, int $windowSeconds)
| Flag | Default | Description |
|---|---|---|
$maxRequests |
— | Max requests per window |
$windowSeconds |
— | Window duration in seconds |
sendAPI($data, array $options)
| Option | Default | Description |
|---|---|---|
format |
'json' |
'json' or 'xml' |
root |
'response' |
XML root tag |
xmlItemName |
'item' |
XML list item tag |
statusCode |
200 |
HTTP status code |
setPoweredBy($value)
| Flag | Default | Description |
|---|---|---|
$value |
— | false to remove, '' for blank, string for custom, null to leave default |
handleDatabase(array $config)
| Key | Default | Description |
|---|---|---|
type |
— | 'mysql' or 'sqlite' |
host |
'localhost' |
MySQL host |
database |
— | Database name (MySQL) or file path (SQLite) |
username |
— | MySQL username |
password |
— | MySQL password |
Configuration
Narciso uses method calls, not config files. Configuration precedence:
- Method arguments (highest)
- Built-in defaults (lowest)
$app = new Application(); $app->setViewPath(__DIR__ . '/views'); $app->setPoweredBy(false); $app->handleSession(); $app->useSecurityHeaders(); $app->useCors(['https://myapp.com']); $app->useRateLimit(60, 60); $app->handleDatabase([ 'type' => 'sqlite', 'database' => __DIR__ . '/data/app.db', ]);
Example Output
JSON Response
HTTP/1.1 200 OK Content-Type: application/json {"message":"Hello World"}
XML Response
HTTP/1.1 200 OK Content-Type: application/xml <?xml version="1.0"?> <response> <message>Hello World</message> </response>
Rate Limited Response
HTTP/1.1 429 Too Many Requests Retry-After: 43 Content-Type: application/json {"error":"Rate limit exceeded. Try again in 43 seconds."}
CI/CD Integration
GitHub Actions
name: CI on: [push, pull_request] jobs: test: runs-on: ubuntu-latest strategy: matrix: php: [8.1, 8.2, 8.3, 8.4] steps: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} tools: composer:v2 - run: composer validate - run: composer install --prefer-dist --no-progress - run: vendor/bin/phpunit
Development
| Prerequisite | Version | Purpose |
|---|---|---|
| PHP | 7.4+ | Runtime |
| Composer | 2.x | Dependency management |
git clone https://github.com/marcuwynu23/narciso.git cd narciso composer install composer test
Project Structure
narciso/
├── src/Application.php # Main class (~500 lines)
├── src/Middleware/
│ ├── MiddlewareInterface.php # Middleware contract
│ ├── CorsMiddleware.php # CORS handler
│ ├── RateLimitMiddleware.php # Per-IP rate limiter
│ └── SecurityHeadersMiddleware.php # Security headers
├── test/ # PHPUnit test suite
│ ├── TestCase.php
│ ├── ApplicationTest.php # 26 tests
│ └── MiddlewareTest.php # 10 tests
├── samples/ # Example applications
│ ├── 01_basic_routing.php
│ └── ...
└── docs/ # Documentation website
Architecture
- Application is the central class — it holds routes, middleware config, database config, and session state
- Routes are registered as
(method, pattern, handler)tuples. Path parameters (:param) are converted to regex with named groups - Middleware pipeline is an onion-wrapped array. Each middleware calls
$next()to pass control inward - Request dispatch happens in
run(): middleware stack executes first, then route matching, then the matched handler - Database connections are lazy — created on first access via
$app->dbmagic getter - API format detection checks
?format=query param first, thenAcceptheader, then defaults to JSON
Contributing
See CONTRIBUTING.md for full details — coding standards, commit conventions, PR process, and more.
License
Narciso is open source under the Apache License, Version 2.0.