Nour-API engineering guide

Nour-API 2.2 reference: framework and application 2.2.0, installer 2.1.0.

Architecture and repository map

framework contains the NourApi\Framework kernel. It extends the Illuminate container, lazily registers Illuminate services, dispatches routes with FastRoute, and uses Symfony HTTP responses. nour-api is the Composer project skeleton; it requires nour-api/framework ^2.2. installer uses Composer create-project to install that skeleton.

docs and nour-api.github.io are Docusaurus deployment outputs on gh-pages. Their original Docusaurus sources and build configuration are absent from these checkouts. This guide has one canonical source: docs/guide/source.json plus docs/examples/application.php. tools/render-guide.py renders the guide and copies identical HTML to the second local checkout. Historical Docusaurus pages remain historical reference until their original sources are recovered.

Requirements and compatibility

Runtime PHP requirement remains ^8.2 and Composer 2.2 or newer. Required extensions are resolved by Composer: run composer check-platform-reqs after installation. Database drivers depend on the configured connection. Do not bypass platform checks for production installations.

Development PHPUnit constraints accept 11.5 and 12.3 so PHP 8.2 can select PHPUnit 11.5. PHP 8.2–8.5 CI jobs are configured; configuration alone does not mean those jobs have run. Optional PSR-7 conversion requires symfony/psr-http-message-bridge and nyholm/psr7; the normal HTTP path does not require them. Redis requires the optional illuminate/redis ^12.21 component plus a configured client; missing optional services report actionable exceptions.

Installation

From reviewed, published packages, use Composer directly or install nour-api/installer globally. The commands below consume published versions. Use framework/application 2.2.0 and installer 2.1.0 for the behavior documented here.

The installer default command accepts a destination directory, including absolute paths and paths containing spaces. Existing directories, files and dangling symbolic links are rejected. It retains partial files after failure for diagnosis and never automatically deletes them.

If Packagist indexing is delayed, clone the tagged application from https://github.com/nour-api/nour-api at v2.2.0 and run composer install. Its production lock downloads the verified framework tag directly from GitHub. Use composer install rather than updating dependencies until registry indexing is complete.

composer create-project nour-api/nour-api my-api "^2.2"
cd my-api
composer check-platform-reqs
php -S localhost:8000 -t public

# Alternative using the reviewed installer:
composer global require nour-api/installer
nour-api my-api --project-version="^2.2" --no-git

Installer behavior and troubleshooting

The installer executes Composer without a shell, forwards --prefer-source, --no-scripts, --no-plugins and --ignore-platform-reqs only when requested, and checks subprocess exit status. Composer scripts and plugins are package code; select --no-scripts --no-plugins when you require them disabled.

It copies .env.example if needed and fills an empty APP_KEY with a random 32-byte base64 key, sets permissions to 0600 on files it writes, preserves existing non-empty keys and refuses symbolic .env links. Exactly one APP_KEY entry is required. Git initialization is optional; there is no automatic staging or commit. Review files before committing.

--version is reserved by Symfony for the installer program version. Use --project-version to select the skeleton constraint. The published --ref/-r option remains supported as an alias for --project-version; conflicting values are rejected. Earlier main-branch installer code had an incompatible private run() method and a conflicting version option. The installer does not invoke artisan key:generate: this command is not provided by the framework.

On Composer failure inspect its output, platform extensions, connectivity and package constraints. Retry into a fresh directory after reviewing partial files. On Git failure project files remain available and you may run git init manually. No success is reported after a failed required step.

Lifecycle and project structure

public/index.php loads bootstrap/app.php, which loads Composer, reads .env, sets timezone, constructs Application, registers exception and console kernels, loads app configuration, and registers routes/web.php under the App\Http\Controllers namespace. Application::run captures the request, boots service providers once, runs global middleware, resolves a route, executes route and controller middleware, prepares and sends the response, runs terminable global middleware, and invokes terminating callbacks.

app contains controllers, middleware, models, providers, events, listeners, jobs, and the console kernel. database contains factories and seeders. storage contains logs, compiled views and cache data. The web server document root must be public, not the project root. The database schema and migrations are application responsibilities.

Routing and named routes

Router supports get, post, put, patch, delete, options, head, and addRoute with a method or array of methods. FastRoute supports placeholders such as /users/{id:[0-9]+} and optional trailing segments. Missing routes return 404; unsupported methods return 405 with Allow. HEAD falls back to GET and omits the response body.

Route actions may be closures (including static closures), invokable objects, Controller@method strings or invokable controller class strings. Named routes use an action array with as. Route groups support prefix, suffix, namespace, middleware and as. An exception during group registration always unwinds the group stack.

The dynamic route dispatcher is compiled once per router revision and rebuilt after route additions or replacements. Explicit setDispatcher overrides remain supported. Values passed to route(name, parameters) are URL-encoded as single path segments. Pass raw values, not pre-encoded values; slash, question mark and fragment characters are encoded.

$router->get('/users/{id}', ['as' => 'users.show', function ($id) {
    return ['id' => $id];
}]);
$url = route('users.show', ['id' => '42']);
$router->group(['prefix' => 'v1', 'middleware' => 'auth'], function ($router) {
    $router->get('/profile', 'ProfileController@show');
});

Controllers and dependency injection

Controllers extend NourApi\Framework\Routing\Controller for validation, authorization and dispatch convenience methods. Register controller method middleware with middleware(name, options), using only or except to limit methods. Constructor and action dependencies are resolved through the Illuminate container. Route parameter names are matched to action parameter names.

Bind interfaces in an application service provider using bind or singleton, and register the provider in bootstrap/app.php. Container instances are application singletons unless bound otherwise; do not store request-specific data in service singletons. Illuminate\Http\Request and NourApi\Framework\Http\Request injection resolve the current request. Facades are optional and require withFacades().

When withFacades() switches to a different Application, cached facade roots are cleared. Custom class aliases can be registered after the default aliases have already been loaded; existing class aliases are preserved.

Application boot includes providers registered by other providers during boot. Reentrant boot calls do not run the same provider recursively. If initial boot fails, a later retry resumes pending providers without repeating providers that already booted successfully.

$app->singleton(Clock::class, SystemClock::class);
$app->register(App\Providers\AppServiceProvider::class);
// In App\Http\Controllers\ProfileController:
public function show(\Illuminate\Http\Request $request)
{
    return response()->json(['user' => $request->user()?->getAuthIdentifier()]);
}

Middleware pipeline

Register global middleware with app->middleware([...]) and route aliases with app->routeMiddleware(["auth" => Authenticate::class]). Attach aliases with a route action middleware value, an array, or a pipe-separated string. Parameters follow a colon, for example throttle:60,1, when that middleware has been implemented by the application; no built-in throttle middleware is promised.

Middleware handle(request, next, ...parameters) may continue the chain or return a response. Global short-circuit results are normalized to Symfony responses, including HEAD behavior. Global class middleware with terminate(request, response) runs after dispatch in handle(), and after response sending in run(). Route/controller terminable middleware is not automatically terminated. Application::handle returns a response without sending it or invoking application terminating callbacks.

Requests and responses

Requests support query and body input, JSON, headers, files, bearerToken(), user(), and route parameters through the Illuminate request API. Nour routeIs() and offsetExists() also work before a route is available. Request::route returns Nour route metadata arrays, not an Illuminate routing Route object.

Return strings, arrays, response(), response()->json(), Symfony responses or Responsable objects. ResponseFactory supports JSONP, downloads, file responses and streams. Validate JSONP callback names through the response API. File paths supplied to download/file must be selected and authorized by application code; never accept arbitrary filesystem paths directly from users. Set response Content-Type explicitly when appropriate.

Configuration and secrets

APP_DEBUG defaults to false. Enable it only for local development. getConfigurationPath prefers the application config/name.php file, then the framework default. Call app->configure(name) before reading that configuration. Configuration is loaded once per application; use a config file override for service-specific settings.

Do not commit .env, tokens, database passwords or generated keys. Composer direct create-project copies .env.example but does not generate a key; supply a base64-encoded random 32-byte APP_KEY before using encryption. The installer fills blank keys. Existing ciphertext depends on the key: do not rotate it without a data migration plan.

Database, cache, logging, auth, filesystem, queue, broadcasting, services and view defaults are supplied in framework/config. Session middleware, CSRF middleware and a CORS policy are not automatically installed. Cookie/session-based applications must explicitly configure those protections.

Database integration

Uncomment withEloquent() in bootstrap/app.php when using models. Configure DB_CONNECTION, DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME and DB_PASSWORD for the chosen database. MySQL, PostgreSQL, SQLite and SQL Server require their corresponding PDO drivers.

Illuminate query builder and Eloquent bind query values. Use where("email", $input); never interpolate user data into raw SQL. Parameter binding does not make untrusted table names, column names or raw expressions safe; allowlist identifiers. Use database transactions for related writes and application-level authorization for records.

Artisan exposes migrate, migrate:status, db:seed and other migration/queue/cache commands. Destructive commands such as migrate:fresh and db:wipe delete data and require operator review. The skeleton does not contain a complete users schema or a token storage schema.

Validation and errors

Use validator(data, rules) or app("validator")->make(data, rules), and validate() to return validated fields or throw a ValidationException. Controller::validate(request, rules) returns selected validated input. Invalid validation returns JSON with message and errors and status 422, including ValidationException instances without a prebuilt response.

The exception handler reports errors through the configured PSR logger. Unexpected JSON errors return 500 and a generic Server Error message in production. HTTP 5xx exceptions also hide internal messages. HTTP 4xx status/headers remain available; AuthenticationException returns 401 and authorization failures return 403. Debug output contains implementation details; leave APP_DEBUG=false on exposed servers.

An application may customize App\Exceptions\Handler. Avoid logging passwords or bearer tokens in custom context. Configure LOG_CHANNEL and storage directory permissions; stderr is available for containers.

Authentication and authorization

Authentication is opt-in: register App\Providers\AuthServiceProvider, enable the auth route alias, and attach auth to protected routes. The skeleton provider demonstrates viaRequest("api", callback). It accepts a bearer token and retains api_token input as a legacy fallback. This demonstration assumes an api_token column that applications must supply; it is not a complete token issuance system.

Production applications should store token hashes, use expiring/revocable tokens, verify tokens in constant time when comparing secrets, require HTTPS, and avoid URL query tokens. Define Gate abilities or policies for authorization. Controller::authorize and the Authorizable model trait delegate to Illuminate Gate.

Cached identities in the built-in Nour AuthManager are cleared between sequential requests without recreating guards. URL-derived caches, current route metadata and resolved PSR requests are also refreshed. Custom guards must implement forgetUser to participate in identity reset. This does not establish concurrent worker safety: use a fresh application per concurrent request and audit custom singleton state.

Events, jobs, CLI and extension points

Register event listeners/subscribers in App\Providers\EventServiceProvider and register the provider in bootstrap/app.php. Dispatch events through event(). Jobs use the Illuminate bus and configured queue connection. Configure a durable queue and failure handling for background jobs; the default sync connection executes jobs in the request.

php artisan list is the authoritative installed command list. Define custom command classes in App\Console\Kernel and schedule jobs through its schedule method. Service providers, container bindings, response macros, middleware and event listeners are the existing extension points; there is no separate plugin package manager.

Console Kernel::bootstrap(), programmatic call() and command discovery boot registered providers once, matching the normal CLI entry point.

Executable API example

The following complete example returns an Application. Load the Composer autoloader before requiring it, then call app->run() for HTTP serving. The corresponding source is examples/application.php. Run php tools/test-examples.php from the docs checkout to exercise health, dynamic routes, validation, 404, 405, HEAD and middleware headers against the sibling framework checkout.

<?php

use Illuminate\Http\Request;
use Illuminate\Contracts\Debug\ExceptionHandler;
use NourApi\Framework\Exceptions\Handler;
use NourApi\Framework\Application;

$app = new Application;
$app->configure('app');
$app->singleton(ExceptionHandler::class, Handler::class);

$app->middleware(static function ($request, $next) {
    $response = $next($request);
    $response->headers->set('X-Application', 'Nour-API');
    return $response;
});

$app->router->get('/health', ['as' => 'health', static fn () => ['status' => 'ok']]);
$app->router->get('/users/{id:[0-9]+}', static fn ($id) => ['id' => $id]);
$app->router->post('/users', static function (Request $request) {
    $data = app('validator')->make($request->all(), ['email' => 'required|email'])->validate();
    return response()->json($data, 201);
});

return $app;

Testing and local cross-repository verification

Run composer test in framework, installer and an installed application. PHP syntax checks and composer validate --strict should also pass. composer audit examines the resolved dependency versions. The framework testing base restores process-level PHP error/exception handlers after each test, preserving PHPUnit isolation.

Run php tests/integration.php in installer with sibling framework and nour-api checkouts. It makes temporary copies, exposes local packages through a temporary Composer path repository, executes the real installer and Composer create-project, checks the generated key, runs Artisan and generated application tests, and dispatches an HTTP request. It cleans only its own temporary directory. Package downloads may be required; no remote repository writes are performed.

A path repository can resolve nour-api/framework to a local checkout using an explicit 2.2.0 development alias. Do not commit developer-specific absolute paths or local Composer manifests to released application packages. Dependency resolution against an emulated PHP platform is not a substitute for running tests on that PHP version.

The ordinary framework test suite performs real in-memory SQLite CRUD, database-backed uniqueness validation, SQL parameter binding, migration/rollback and transaction cleanup checks. The development test bootstrap checks actual PDO SQLite driver availability; SQLite is not a production dependency. Failed migration commands stop the test; partial transaction setup is rolled back and nested transactions return to their prior level.

Testing actingAs()/be() identities persist across the current test requests and reset when the application is refreshed. This is test-only behavior; runtime request identity isolation is retained. Teardown attempts all cleanup callbacks and flushes the application before reporting the first cleanup failure.

framework/tests/integration/README.md describes an isolated Composer project for the optional Redis and PSR-7 components. With those components installed, the same PHPUnit suite checks successful PSR conversion and refresh between requests. The explicit tests/integration/redis.php script starts a private non-persistent Unix-socket Redis process and checks cache, atomic add, lock ownership and queue job execution, then stops its own process and removes its own files. It does not use an application Redis server.

The explicit framework/tests/integration/mariadb.php check initializes its own private MariaDB datadir and server with TCP disabled and Unix-socket authentication. It verifies Eloquent CRUD, SQL value binding, uniqueness validation, transaction rollback and Artisan migration/rollback, then stops its own server and removes its own files. It requires installed MariaDB tools, PDO MySQL and POSIX functions; it never uses an application database or changes a system service.

Migration notes and review before release

Installer callers may use --project-version or the compatible --ref/-r option for skeleton constraints and make their first Git commit explicitly. New skeletons default to APP_DEBUG=false and no example database password. Application::version now identifies Nour-API and Illuminate 12 rather than Lumen 11. Avoid treating the human-readable version string as a machine interface.

Named route parameter values are encoded as path segments. Remove application-side pre-encoding and review routes that intentionally passed slash-separated paths as one parameter. Static closures are supported. Ordinary Illuminate requests are adapted to Nour requests so its route metadata helpers work.

Clients that previously received 500 for ordinary ValidationException or AuthenticationException now receive 422 or 401. HTTP 5xx JSON messages are generic outside debug mode. Verify client error handling and custom exception renderers.

Before release run the PHP compatibility matrix, PostgreSQL and production-version MySQL integration tests beyond the SQLite and private MariaDB coverage, Windows installer checks, production web server smoke tests and review the original Docusaurus source. Release publication requires passing the compatibility and cross-repository checks.