Middleware in Lithe: How It Works and How to Create Your Own
Lithe
Posted on October 24, 2024
Middleware provides a convenient mechanism for inspecting and filtering HTTP requests entering your application. For example, Lithe includes middleware that checks if the user is authenticated. If not, the middleware will redirect the user to the login screen. If the user is authenticated, the middleware allows the request to proceed.
How Middleware Works in Lithe
In Lithe, middleware are functions with access to the request object ($req
), response object ($res
), and the $next
function in the application's request-response cycle. The $next
function, when invoked, calls the next middleware in the current stack.
Middleware functions provide a convenient way to inspect, filter, and manipulate incoming HTTP requests to your application. They can:
- Execute any code.
- Modify the request and response objects.
- End the request-response cycle.
- Call the next middleware in the stack.
If the current middleware doesn't terminate the request-response cycle, it must call $next()
to pass control to the next middleware. Otherwise, the request will remain pending.
Elements of a Middleware Function
The following code demonstrates the elements of a middleware function:
$app->use(function ($req, $res, $next) {
$next();
});
Where:
-
$req
: HTTP request argument, conventionally called$req
. -
$res
: HTTP response argument, conventionally called$res
. -
$next
: Callback argument, conventionally called$next
.
Defining Middleware
Here鈥檚 a simple example of middleware called myLogger
. This middleware prints the message LOGGED
every time a request passes through it. It鈥檚 defined as a function assigned to a variable called myLogger
:
$myLogger = function ($req, $res, $next) {
echo 'LOGGED';
$next();
};
Notice the $next()
call above. This function calls the next middleware in the application. $next()
isn't a built-in PHP or Lithe function but is the third argument passed to the middleware function. Though $next()
could be named anything, by convention, it is always called "next." To avoid confusion, stick to this convention.
Imagine middleware as a series of "layers" that HTTP requests pass through before reaching your application. Each layer can examine or reject the request.
Loading Middleware
To load middleware, you call the use()
method of the \Lithe\App
class, specifying the middleware function. For example:
$app = new \Lithe\App;
$myLogger = function ($req, $res, $next) {
echo 'LOGGED';
$next();
};
$app->use($myLogger);
$app->get('/', function ($req, $res, $next) {
$res->send('Hello World!');
});
Whenever the app receives a request, the message "LOGGED" will be printed. The order of middleware loading matters: those loaded first are executed first.
The myLogger
middleware simply prints a message, then passes the request to the next middleware using $next()
.
Using Middleware
A Lithe application can use the following types of middleware:
Application-level Middleware
You attach application-level middleware to an instance of the application using the use()
or METHOD()
methods, where METHOD
refers to the HTTP method (e.g., GET, PUT, POST) in lowercase.
This example shows middleware without a path. The middleware runs every time a request is received:
$app->use(function ($req, $res, $next) {
echo 'Hello World!';
$next();
});
In the example below, middleware handles a GET request to the path /user/:id
:
$app->get('/user/:id', function ($req, $res, $next) {
if ($req->param('id') === '0') {
return $next();
}
$res->send('ID is not 0');
}, function ($req, $res) {
$res->send('regular');
});
Router-level Middleware
Router-level middleware works like application-level middleware but is attached to an instance of \Lithe\Http\Router
:
$router = new \Lithe\Http\Router;
You load router-level middleware using the use()
and METHOD()
functions.
Here鈥檚 an example of router-level middleware:
$router = new \Lithe\Http\Router;
$router->use(function ($req, $res, $next) {
echo 'Time: ', Date('H:i:s'), '<br>';
$next();
});
$router->get('/user/:id', function ($req, $res, $next) {
if ($req->param('id') === '0') {
$res->redirect('/');
}
$next();
}, function ($req, $res) {
echo $req->param('id');
$res->render('special');
});
$app->use('/api', $router);
Third-party Middleware
You can use third-party middleware to add functionality to your Lithe applications. Install the required PHP module and then load it at the application or router level.
Here鈥檚 an example of loading session middleware with \Lithe\Middleware\Session\session
:
use function Lithe\Middleware\Session\session;
$app = new \Lithe\App;
$app->use(session([
'secure' => true
]));
For a list of third-party middleware commonly used with Lithe, check the Third-party Middleware resource.
Configurable Middleware
If you need your middleware to be configurable, you can create a function that accepts an array of options or other parameters and then returns the middleware implementation based on those parameters. See the example below:
<?php
function my_middleware($options) {
return function ($req, $res, $next) use ($options) {
// Middleware implementation based on the provided options
// Continue to the next middleware
$next();
};
}
Now, you can use the middleware with custom configurations:
$app->use(my_middleware(['option1' => '1', 'option2' => '2']));
To create middleware that other developers can install via Composer, there is a package called lithemod/flow. It provides interfaces and utilities for handling HTTP requests and responses in Lithe, which facilitates the creation of standardized and ready-to-use middlewares across various applications.
lithemod/flow helps build robust middlewares by providing a unified interface for Request
and Response
, making development more efficient and organized. This simplifies the integration of your middleware into other projects and ensures that the code follows consistent standards.
Posted on October 24, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.