marko/routing
Routing component for the Marko PHP framework. Register routes, map URLs to controllers, and generate links with a clean, Laravel-inspired API. Supports route groups, parameters, middleware, and named routes to keep application URLs organized and maintainable.
marko/routing is a component package, not a standalone library — it's the routing layer of the Marko framework and is pulled in transitively when you install the framework itself. The repository is marked [READ-ONLY], so there is no separate versioning or issue tracker to follow; everything lives in the main marko-php/marko monorepo.
Minimal path to a first request:
Install the framework (composer require marko/framework or scaffold via the Marko installer), which brings marko/routing along with it.
Open the route definition file your skeleton provides — typically routes/web.php (and often a sibling routes/api.php). This is the first place to look: the routing package registers a Route facade/helper bound in the container, and the routes file is just executed at boot.
Define your first route with a closure:
Route::get('/hello/{name}', function ($name) {
return "Hello, {$name}";
});
Hit it via your dev server / front controller and confirm the parameter is injected.
That's the whole first-use case: a parameterized GET route backed by a closure. Everything else (controllers, middleware, groups) is layering on top of that primitive. Until you understand that routes resolve to a handler plus a middleware stack, the rest of the package looks more magical than it is.
The package is Laravel-shaped — the day-to-day API will feel familiar if you've used Illuminate\Routing, and the patterns below are the ones you'll actually reach for.
Controllers over closures. Closures are fine for smoke tests but don't cache well and don't get resolved through the container consistently. Point routes at [Controller::class, 'method'] or the string form 'Controller@method' so dependency injection and middleware can kick in.
Verbs and convenience methods. Route::get/post/put/patch/delete cover most cases. For anything spanning multiple verbs, Route::match([...], ...) and Route::any(...) exist. Don't reach for any reflexively — it makes 405s impossible and complicates route caching.
Parameters and constraints. {id} is required, {id?} is optional (must be trailing). Add regex constraints via ->where('id', '[0-9]+') or ->whereNumber('id') if the shorthand exists in your version. Constraints are the right place to reject bad input — don't validate numeric IDs in the controller if the router can do it for free.
Groups for prefix/middleware/namespace. This is where the package earns its keep:
Route::group(['prefix' => 'admin', 'middleware' => ['auth', 'admin']], function () {
Route::get('/users', [UserController::class, 'index']);
});
Prefer groups over repeating prefixes and middleware strings on every route — the group closure is evaluated once at boot, and changing the prefix is a one-line edit.
Named routes and URL generation. Name routes (->name('users.show')) and generate URLs with route('users.show', ['user' => $id]) rather than hardcoding paths. This is the single biggest long-term win: renaming a URI becomes a non-event. It also pairs with redirects (redirect()->route(...)).
Middleware as the integration seam. Routing is where your middleware pipeline is assembled. Anything cross-cutting — auth, throttling, CORS, request logging — belongs on route middleware, not in controllers. Order matters: middleware runs in the order declared, and route middleware runs after global middleware.
Model binding. If route parameters are named to match a model (e.g. {user}), the package may resolve a model instance automatically. This is worth using for the "clean controller" feel, but be aware of the resolution semantics — implicit binding typically hits findOrFail and 404s on miss, which is usually what you want but occasionally isn't.
API vs web split. The web and api route files usually differ in middleware (web gets sessions/CSRF; api gets a stateless prefix like /api). If you're exposing a JSON API, define routes in the api file and skip session middleware for performance.
marko-php/marko. Don't open anything against this repo — it will be ignored. When filing a bug, check the monorepo's issue tracker and reference marko/routing in the title.vendor/marko/routing/src) — it's small enough to read in an afternoon, and reading it will answer almost every ambiguity faster than searching./{slug} defined before /about will swallow /about. Always define specific routes before dynamic ones./users and /users/ are distinct patterns. Pick a convention and normalize at the web-server layer (nginx try_files) or accept that users will occasionally 404 on a trailing slash.PUT/DELETE routes, confirm the framework's method-override middleware is enabled and that your form posts _method. A 405 on a form submission is almost always this.route:cache. If Marko offers an equivalent, use it — but remember the cache is invalidated only by your cache-clear command, not by editing the routes file. New routes returning 404 in prod but working locally is a cache-staleness symptom.$next($request)), downstream middleware and the controller never run — this is the mechanism, not a bug, but it surprises people debugging "why isn't my controller hit."Router/Route bindings in the container rather than patching vendor files. The facade just resolves a binding — rebinding is the supported override mechanism.marko/framework and a manually-required marko/routing is a real risk. Don't require it directly; let the framework manage the constraint.How can I help you explore Laravel packages today?