Getting Started

Install

composer create-project monad/skeleton NewApp
cd NewApp

create-project writes .env for you, from .env_example and with a freshly generated APP_SECRET — it is generated rather than left blank because a blank signing key does not fail, it just makes every CSRF and session token forgeable. Every other key arrives present and empty, documented in place. An existing .env is never overwritten.

Fill in the DB_* variables in .env, then create Monad's built-in tables and run your own migrations:

php mitosis setup
php mitosis migrate

Start the built-in development server:

php mitosis serve

This binds the PHP built-in server to http://127.0.0.1:8000, using public/router.php. Requires PHP >=8.2.

Your first route

Routes are registered in app/routes/web.php. Route::get() takes a URI and either a closure or a [ControllerClass::class, 'method'] pair — controller actions are static methods, so that pair is directly callable with no container resolving it:

use Monad\Clarity\Services\Response;
use Monad\Clarity\Services\Route;

Route::get('/hello', fn () => Response::text('Hello, Monad.'));

Visit http://127.0.0.1:8000/hello and you'll see the response text. Route also has post(), put(), patch(), delete(), and options(), plus named parameters ({id}, {id:int}, {slug:alpha}, {id:uuid}) and route groups.

Your first middleware

A middleware is any callable of the shape function (Request $request, callable $next): Response — call $next($request) to continue the pipeline, or return your own Response to short-circuit it. Register one with ->middleware():

use Monad\Clarity\Services\Request;
use Monad\Clarity\Services\Response;
use Monad\Clarity\Services\Route;

Route::get('/hello', fn () => Response::text('Hello, Monad.'))
    ->middleware(function (Request $request, callable $next): Response {
        $response = $next($request);

        return $response->withHeader('X-Powered-By', 'Monad');
    });

Reload /hello and check the response headers — X-Powered-By: Monad is there. This is the same contract every built-in middleware uses (Csrf, RateLimiter, Authentication, and the rest), so a closure like this one and a fully-configured class both plug into ->middleware() the same way.

Next steps

  • Quickstart — build a slightly larger app: a controller, a view, and a named route group.
  • Routing — the full Route API: groups, named routes, typed parameters, the 404-vs-405 distinction.
  • Middleware Pipeline — how the pipeline is built and run, and when to use a class instead of a closure.