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 endpoints

  • web.php – for browser views and standard web pages

  • system.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.

  1. Open routes/web.php.

  2. Add this code:

    $router->sewGet('/hello', function() {
        return 'Hello, LacePHP!';
    });
    
  3. Start your built-in server (Run this in your terminal, changing the directory to the root of your project folder.):

    php lace tread
    
  4. 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.

  1. Create a file weave/Controllers/GreetingController.php:

    <?php
    namespace Weave\Controllers;
    
    class GreetingController
    {
        public function hello()
        {
            return "Hello World!";
        }
    }
    
  2. In routes/web.php, register the controller route:

    use Weave\Controllers\GreetingController;
    
    $router->sewGet(
        '/greet',  # URL pattern with {name} placeholder
        [GreetingController::class, 'hello']
    );
    
  3. 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.

  1. Create the controller

    <?php
    namespace Weave\Controllers;
    
    class GreetingController
    {
        public function hello($name)
        {
            return "Hello, " . $name;
        }
    }
    
  2. Define the route

    use Weave\Controllers\GreetingController;
    
    $router->sewGet(
        '/greet/{name}',
        [GreetingController::class, 'hello']
    );
    
  3. 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.

  1. Define an optional parameter by appending ? inside the braces:

    $router->sewGet(
        '/profile/{username?}',
        [ProfileController::class, 'show']
    );
    
  2. 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";
        }
    }
    
  3. Example requests

    • Request: GET /profile Result: Controller gets $username === null

    • Request: GET /profile/bob Result: Controller gets $username === 'bob'

  4. 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

$router->sewGet('/users', …) or $router->get('/users', …)

POST

$router->sewPost('/users', …) or $router->post('/users', …)

PUT

$router->sewPut('/users/{id}', …)

PATCH

$router->sewPatch('/users/{id}', …)

DELETE

$router->sewDelete('/users/{id}', …)

OPTIONS

$router->sewOptions('/users', …)

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.