🧭 Routing in PageBlocks
Routes define which code should execute when a specific URL is accessed on your site.
Example: if a user visits /about, you can set up a route to display the "About Us" page.
🔰 Basic Example
PageBlocks uses the Route facade to define routes:
use Boshnik\PageBlocks\Facades\Route;
Route::get('/about', function () {
return 'This is the About page';
});📥 When someone opens https://example.com/about, this function runs, and the user sees its output.
You can return the following from a route:
| Type | Result |
|---|---|
string | Plain text |
array | JSON |
object | JSON or HTML (depending on content and headers) |
Response | Explicit response object using the Response class |
Everything returned from a route automatically passes through
Response::dispatchResponse, which determines how to properly send the result to the client.
📂 Where Are Routes Located?
All routes are stored in the core/App/routes folder of your project:
core/
└── App/
└── routes/
├── web.php
├── api.php
└── ...🔄 All .php files in this folder are automatically loaded, and their routes are registered when the application starts.
📝 You can split routes into multiple files:
web.php— Main website routesapi.php— API routesadmin.php,auth.php,blog.php— Any additional route groups
The filename doesn’t matter as long as it’s in
core/App/routesand has a.phpextension.
⚙️ Route Methods
Each route is associated with an HTTP method—the way a browser or client sends a request to the server. Here are the main methods:
📄 Route::get($uri, $callback)
Responds to GET requests—when a user simply opens a page.
Route::get('/contact', function () {
return 'Contact information';
});📩 Route::post($uri, $callback)
Handles POST requests—commonly used when submitting forms.
Route::post('/feedback', function () {
return 'Thank you for your feedback!';
});📝 Route::put($uri, $callback)
Used for full resource updates.
Route::put('/profile', function () {
return 'Profile updated';
});🔧 Route::patch($uri, $callback)
Partial resource updates—for example, changing a single field.
Route::patch('/profile/email', function () {
return 'Email updated';
});🗑 Route::delete($uri, $callback)
Deletes a resource.
Route::delete('/account', function () {
return 'Account deleted';
});⚙️ Route::options($uri, $callback)
Handles OPTIONS requests—useful for CORS and APIs.
Route::options('/api/data', function () {
return response('', 204)
->header('Allow', 'GET, POST, OPTIONS')
->header('Access-Control-Allow-Origin', '*')
->header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
->header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
});🔀 Route::match(array $methods, string $uri, $callback)
Defines a route that responds to multiple methods.
For example, if the same URL should handle both GET and POST:
Route::match(['GET', 'POST'], '/subscribe', function () {
return 'Subscription form or its processing';
});👆 This route will work for:
- Opening
/subscribein a browser (GET); - Submitting a form to the same URL (POST).
Methods must be in uppercase (GET, POST, PUT, etc.).
🧮 Route::any(string $uri, $callback)
Responds to all HTTP request types: GET, POST, PUT, DELETE, PATCH, OPTIONS.
Route::any('/ping', function () {
return 'pong';
});This route will work regardless of the client’s method—useful if the request type doesn’t matter.
🔁 Redirect Routes
Sometimes, instead of processing a request, you just need to redirect the user to another URL. Use the redirect method for this.
🔁 Route::redirect($from, $to, $status = 302)
Route::redirect('/old-link', '/new-link');👆 Anyone who opens /old-link will be automatically redirected to /new-link.
By default, this uses status code 302 (temporary redirect), but you can specify another:
Route::redirect('/moved', '/here', 301); // permanent redirect📦 Useful for site reorganization or URL renaming.
🖼 Quick Route for Displaying a Template
If you just need to display a template without logic, use the Route::view method.
🖼 Route::view($uri, $view, $data = [])
Route::view('/about', 'pages/about');👆 This route will render the pages/about.tpl template.
You can also pass data:
Route::view('/thanks', 'pages/thanks', ['title' => 'Thank you for your order']);Internally, this method calls
response()->view(...), so the result is identical to manually returning a template.
🧩 Route Parameters
Routes can contain variables—parameters that are automatically extracted from the URL and passed to the handler.
🎯 Required Parameters
Route::get('/user/{id}', function ($id) {
return "User profile #$id";
});👆 Visiting /user/42 will output: User profile #42.
❔ Optional Parameters
To make a parameter optional, add ? and specify a default value:
Route::get('/user/{name?}', function ($name = 'Guest') {
return "Hello, $name!";
});📥 /user → Hello, Guest!
📥 /user/Anna → Hello, Anna!
🔍 Parameter Constraints — where()
Allows you to set regular expressions to validate parameters:
Route::get('/user/{id}', function ($id) {
return "User #$id";
})->where('id', '\\d+');👆 Now /user/abc won’t work—only digits (\d+).
Constraints can be set for multiple parameters:
Route::get('/post/{slug}/{id}', function ($slug, $id) {
return "$slug: $id";
})->where([
'slug' => '[a-z\-]+',
'id' => '\\d+',
]);📦 Accessing Request Inside a Route
To access request data, use dependency injection with Request:
use Boshnik\PageBlocks\Http\Request;
Route::post('/submit', function (Request $request) {
$email = $request->input('email');
return "You entered: $email";
});🧃 Named Routes
Named routes are useful for generating links and redirects:
Route::get('/profile/{id}', function ($id) {
return "Profile #$id";
})->name('profile.show');Now you can generate a link to this route:
route('profile.show', ['id' => 7]); // /profile/7🧭 This is especially helpful when generating links in templates or using redirects:
response()->redirect(route('profile.show', ['id' => $user->id]));📦 Route Groups
The group() method lets you group routes and apply shared settings: prefix, middleware, controller, etc.
🔹 Basic Example
Route::group(function () {
Route::get('/a', fn() => 'A');
Route::get('/b', fn() => 'B');
});Routes inside the group will be registered normally—without a prefix or additional parameters.
🏁 Prefix
Adds a common prefix to all routes in the group:
Route::prefix('/admin')->group(function () {
Route::get('/dashboard', fn() => 'Dashboard');
Route::get('/users', fn() => 'Users');
});📥 Registered routes:
/admin/dashboard/admin/users
🛡 Shared Middleware
Route::middleware('auth')->group(function () {
Route::get('/cabinet', fn() => 'Personal account');
});All routes in the group will be processed via the auth middleware.
👨🏫 Default Controller
Sets a shared controller for routes in the group:
Route::controller(ProfileController::class)->group(function () {
Route::get('/edit', 'edit');
Route::post('/update', 'update');
});🔁 Calls the methods:
ProfileController->edit()ProfileController->update()
🛡 Middleware
Middleware are intermediate handlers that run before the controller or closure executes. Examples include auth checks, CSRF protection, etc.
📌 Usage Example
Route::get('/dashboard', fn() => 'Dashboard')->middleware('auth');🧩 Multiple Middleware
Route::middleware(['auth', 'admin'])->group(function () {
Route::get('/admin', fn() => 'Admin');
});🚫 Excluding Middleware
You can disable middleware applied earlier:
Route::get('/open', fn() => 'Open')->withoutMiddleware('auth');🧾 Where Are Aliases Configured?
Middleware can be referenced by names (aliases) or full class names. Aliases are configured in app.php:
'middleware' => [
'auth' => \App\Middleware\Auth::class,
],🛑 Fallback Routes
If no route matches, you can define a fallback route that runs as a last resort.
🚨 Route::fallback($callback)
Route::fallback(function () {
return response()->view('errors/404')->send();
});📌 This route only triggers if no other route matches.
✅ Useful for custom 404 pages or other global handlers.
Always place fallback routes at the end of all other routes, or they might "catch" unintended requests.
🔐 CSRF Protection
All routes with POST, PUT, PATCH, or DELETE methods are automatically protected against CSRF attacks.
To pass the check, forms must include a hidden token field.
✅ Method 1: {csrf()}
You can use a convenient function in templates:
<form method="POST">
{csrf()}
<button>Submit</button>
</form>This generates:
<input type="hidden" name="_token" value="{csrf_token}">Also supports spoofing if _method is manually added:
<form method="POST">
{csrf()}
<input type="hidden" name="_method" value="PUT">
<button>Update</button>
</form>🛠 Method 2: Manually Insert the Token
You can manually add the hidden field:
<input type="hidden" name="_token" value="{csrf_token}">Useful if you want full control over the HTML.
⚙️ Method 3: Meta Tag with Token
If you use pbFetch, it automatically looks for the CSRF token in a meta tag:
<meta name="csrf-token" content="{csrf_token}">Then, pbFetch will include this token in the X-CSRF-TOKEN header when sending requests.
🔐 Token validation is handled by the VerifyCsrfToken middleware, which is automatically applied to all POST, PUT, PATCH, and DELETE routes.
If the token is missing or invalid, a 419 Page Expired error will be shown.