Essentials

Routing

Map URLs to controllers and handlers

Routes define how your application responds to HTTP requests. Glueful provides a fast, intuitive routing system with O(1) static lookups and flexible middleware support.

Basic Routes

Define routes in routes/api.php:

use Glueful\Http\Response;

// Simple closure
$router->get('/hello', fn() => new Response(['message' => 'Hello World']));

// Controller method
$router->get('/users', [UserController::class, 'index']);
$router->post('/users', [UserController::class, 'store']);

HTTP Methods

$router->get('/users', ...);      // GET
$router->post('/users', ...);     // POST
$router->put('/users/{id}', ...); // PUT
$router->delete('/users/{id}', ...); // DELETE
$router->head('/status', ...);    // HEAD

Route Parameters

Basic Parameters

$router->get('/users/{id}', function (int $id) {
    return Response::success(['user_id' => $id]);
});

Multiple Parameters

$router->get('/posts/{year}/{month}/{slug}', function ($year, $month, $slug) {
    // Access parameters by position or name
});

Parameter Constraints

Validate parameters with regex patterns:

// Numeric ID only
$router->get('/users/{id}', [UserController::class, 'show'])
    ->where('id', '\d+');

// Multiple constraints
$router->get('/posts/{year}/{month}', [PostController::class, 'index'])
    ->where([
        'year' => '\d{4}',
        'month' => '\d{2}'
    ]);

// Slug pattern
$router->get('/blog/{slug}', [BlogController::class, 'show'])
    ->where('slug', '[a-z0-9-]+');

Route Groups

Share prefixes and middleware across multiple routes:

$router->group(['prefix' => '/api/v1'], function ($router) {
    $router->get('/users', [UserController::class, 'index']);
    $router->get('/posts', [PostController::class, 'index']);
});
// Creates: /api/v1/users, /api/v1/posts

Group with Middleware

$router->group(['prefix' => '/admin', 'middleware' => ['auth', 'admin']], function ($router) {
    $router->get('/dashboard', [AdminController::class, 'dashboard']);
    $router->get('/users', [AdminController::class, 'users']);
});

Nested Groups

$router->group(['prefix' => '/api'], function ($router) {
    $router->group(['prefix' => '/v1', 'middleware' => ['auth']], function ($router) {
        $router->get('/profile', [ProfileController::class, 'show']);
    });
});
// Creates: /api/v1/profile with auth middleware

Middleware

Route-Level Middleware

$router->get('/dashboard', [DashboardController::class, 'index'])
    ->middleware('auth');

// Multiple middleware
$router->get('/admin', [AdminController::class, 'index'])
    ->middleware(['auth', 'admin']);

// Parameterized middleware
$router->post('/api/upload', [UploadController::class, 'store'])
    ->middleware('rate_limit:10,60'); // 10 requests per 60 seconds

Available Middleware

MiddlewarePurposeParameters
authJWT authenticationNone
corsCORS headersNone
csrfCSRF protectionNone
rate_limitRate limitingmaxAttempts,windowSeconds

Dependency Injection

Auto-inject services into route handlers:

use Psr\Log\LoggerInterface;

$router->get('/users/{id}', function (int $id, UserService $service, LoggerInterface $logger) {
    $logger->info('Fetching user', ['id' => $id]);
    return Response::success($service->find($id));
});

Named Routes

Name routes for easy URL generation:

$router->get('/users/{id}/profile', [ProfileController::class, 'show'])
    ->name('user.profile');

// Generate URL
$url = route('user.profile', ['id' => 123]); // /users/123/profile

Route Attributes (PHP 8+)

Define routes directly in controllers using attributes:

use Glueful\Http\Routing\Attributes\Route;

class UserController
{
    #[Route('GET', '/users')]
    public function index() {
        // ...
    }

    #[Route('GET', '/users/{id}', where: ['id' => '\d+'])]
    public function show(int $id) {
        // ...
    }

    #[Route('POST', '/users', middleware: ['auth'])]
    public function store() {
        // ...
    }
}

Controller Resolution

When you use the array callable form [Controller::class, 'method'], the router resolves the controller via the DI container and then calls the method. This enables constructor injection and consistent lifecycle handling.

  • Requirement: Your controller class must be container‑resolvable (registered or autowire‑able).
  • Recommended: Register app controllers in an app service provider so they can be auto‑wired.

Example provider (App\Providers\AppServiceProvider):

<?php
namespace App\Providers;

final class AppServiceProvider
{
    // Provider discovery loads this static method to register services
    public static function services(): array
    {
        return [
            // Autowire controllers so [Controller::class, 'method'] works
            \App\Controllers\WelcomeController::class => [
                'autowire' => true,
                'shared' => true,
            ],
        ];
    }
}

Enable the provider in config/serviceproviders.php:

return [
    'enabled' => [
        App\Providers\AppServiceProvider::class,
    ],
];

If you don’t need DI for a route, you can also use a closure and manually instantiate the controller:

use Symfony\Component\HttpFoundation\Request;

$router->get('/index', function (Request $request) {
    return (new WelcomeController())->index($request);
});

Performance Tips

Route Caching

Cache routes in production for faster lookup:

# Cache routes
php glueful route:cache

# Clear cache
php glueful route:clear

Avoid Dynamic Routes

// ❌ Avoid - creates many routes
for ($i = 1; $i <= 100; $i++) {
    $router->get("/page{$i}", ...);
}

// ✅ Better - use parameters
$router->get('/page/{number}', ...)
    ->where('number', '\d+');

Common Patterns

RESTful Resources

// Full CRUD for a resource
$router->get('/posts', [PostController::class, 'index']);      // List
$router->post('/posts', [PostController::class, 'store']);     // Create
$router->get('/posts/{id}', [PostController::class, 'show']);  // Read
$router->put('/posts/{id}', [PostController::class, 'update']); // Update
$router->delete('/posts/{id}', [PostController::class, 'destroy']); // Delete

API Versioning

$router->group(['prefix' => '/api/v1'], function ($router) {
    require __DIR__ . '/api/v1.php';
});

$router->group(['prefix' => '/api/v2'], function ($router) {
    require __DIR__ . '/api/v2.php';
});

Protected Routes

// Public routes
$router->post('/login', [AuthController::class, 'login']);
$router->post('/register', [AuthController::class, 'register']);

// Protected routes
$router->group(['middleware' => ['auth']], function ($router) {
    $router->get('/profile', [ProfileController::class, 'show']);
    $router->put('/profile', [ProfileController::class, 'update']);
    $router->delete('/account', [AccountController::class, 'delete']);
});

Troubleshooting

Route not found (404)?

  • Check route definition spelling and parameters
  • Verify middleware isn't blocking the route
  • Clear route cache: php glueful route:clear

Parameter not passed to handler?

  • Ensure parameter names match route definition
  • Check type hints match expected data

Middleware not executing?

  • Verify middleware is registered in service provider
  • Check middleware order in groups

Next Steps