Routes¶
In LacePHP, a route is like a signpost on a map, it tells your application which code to run when a user visits a particular URL. Think of each route as an address: when someone “knocks” on that address (makes an HTTP request), your code responds by showing a page, returning data, or performing an action.
The routes/ folder in your project contains three starter files:
api.php
– for JSON-based API endpointsweb.php
– for browser views and standard web pagessystem.php
– for internal or framework-level routes
You can organize your own routes in whichever file makes the most sense.
Closure Routes¶
A closure route uses an anonymous function right in the route definition. This is the simplest way to get started.
Open
routes/web.php
.Add this code:
$router->sewGet('/hello', function() { return 'Hello, LacePHP!'; });
Start your built-in server (Run this in your terminal, changing the directory to the root of your project folder.):
php lace tread
In your browser, go to
http://127.0.0.1:6916
. You should see:Hello, LacePHP!
Here’s what happened:
sewGet(‘/hello’, …) registers a route that only responds to GET requests at the path /hello.
The closure (the function() { … } block) runs when someone visits /hello.
Whatever the function returns becomes the HTTP response body.
Tip: The “sew” prefix is just LacePHP’s playful nod to stitching together routes. You can also write $router->get(‘/hello’, …) if you prefer.
Controller Routes¶
As your app grows, you’ll want to move logic out of closures and into dedicated Controller classes. Controllers group related actions together.
Create a file weave/Controllers/GreetingController.php:
<?php namespace Weave\Controllers; class GreetingController { public function hello() { return "Hello World!"; } }
In
routes/web.php
, register the controller route:use Weave\Controllers\GreetingController; $router->sewGet( '/greet', # URL pattern with {name} placeholder [GreetingController::class, 'hello'] );
Visit
http://127.0.0.1:6916/greet
to see:Hello, Alice!
How this works:
‘/greet’ tells LacePHP to capture the path greet.
LacePHP instantiates GreetingController, calls its hello() method.
The return value is sent back to the browser.
Warning
Each route path + HTTP method combination must be unique. You cannot have two GET /hello routes. Route names and placeholders should use only letters, numbers, hyphens, or underscores—no spaces or special symbols.
Route Parameters¶
You can capture dynamic parts of the URL and pass them directly into your controller methods. Placeholders go in curly braces ({}) and LacePHP will extract them in the order you declare them.
Create the controller
<?php namespace Weave\Controllers; class GreetingController { public function hello($name) { return "Hello, " . $name; } }
Define the route
use Weave\Controllers\GreetingController; $router->sewGet( '/greet/{name}', [GreetingController::class, 'hello'] );
Try it out
http://127.0.0.1:6916/greet/Alice Hello, Alice
Multiple Parameters¶
You can capture more than one value. For example:
$router->sewGet(
'/user/{id}/post/{postId}',
['UserController', 'show']
);
Your controller method must match the placeholder order:
public function show($id, $postId)
{
// $id = user ID
// $postId = post ID
return "User {$id}, Post {$postId}";
}
Tip: Always ensure the number and order of method parameters matches your route’s placeholders. That way LacePHP can map each segment correctly.
Optional Route Parameters¶
Sometimes you want a URL segment to be optional—for example, you might want both:
/profile (show a generic profile page)
/profile/alice (show Alice’s profile)
LacePHP now supports this with a ? mark inside your {} placeholder.
Define an optional parameter by appending ? inside the braces:
$router->sewGet( '/profile/{username?}', [ProfileController::class, 'show'] );
Controller signature Make the method argument optional, too:
class ProfileController { public function show(string $username = null) { if ($username) { return "Profile of {$username}"; } return "Your generic profile page"; } }
Example requests
Request:
GET /profile
Result: Controller gets$username === null
Request:
GET /profile/bob
Result: Controller gets$username === 'bob'
Why this matters - Cleaner routes: You don’t need two separate definitions. - DRY: One route handles both “no parameter” and “with parameter.” - User-friendly: URLs can be shorter when the extra segment isn’t needed.
Note
If you omit the leading slash in your route (e.g. 'posts/{id?}'
), LacePHP will normalise it to '/posts/{id?}'
for you. Always write your patterns without a trailing slash (except root /).
HTTP Methods Cheat Sheet¶
Besides GET, LacePHP supports POST, PUT, PATCH, DELETE, and OPTIONS. Here are two equivalent ways to register each:
Method |
Route Registration |
---|---|
GET |
|
POST |
|
PUT |
|
PATCH |
|
DELETE |
|
OPTIONS |
|
Route Grouping¶
When several routes share a common prefix, middleware, or namespace, you can group them:
use Weave\Middleware\AuthMiddleware;
$router->group([
'prefix' => '/admin',
'middleware' => [AuthMiddleware::class, [[\Lacebox\Knots\RateLimitKnots::class, [10, 60]]]],
'namespace' => 'Controllers\Admin',
], function($r) {
$r->get('/dashboard', ['DashboardController', 'index']);
$r->post('/users', ['UserController', 'create']);
});
In this example:
All paths start with /admin (so the final URLs are /admin/dashboard and /admin/users).
Every request in this group runs through AuthMiddleware first.
Controllers resolve under WeavePluginsYourAppControllersAdmin by default.
This keeps your route definitions concise and DRY.
Further Reading¶
Next up, dive into Middleware to learn how to secure, log, or transform requests before they reach your routes. See Using Middleware with Routes.