How Magento 2's Request-Response Lifecycle Works

How Magento 2's Request-Response Lifecycle Works

Find out how Magento's routing process works, as we go over how an incoming request comes in, gets matched to a router, and returns a response with a controller.

Mark Shust
Mark Shust
51 min read

Have you ever wondered what happens behind the scenes when you click a link or submit a form on a Magento site? How does Magento know what to do with the request, and how to generate the appropriate response?

The answer lies in the request-response cycle, which is a complex orchestration of components and processes that work together to seamlessly deliver some output to the user's browser.

Understanding the request-response cycle is crucial for Magento developers, as it forms the foundation of how Magento works. By knowing what happens at each step of the process, you can better diagnose issues, optimize performance, and create custom extensions that integrate seamlessly with the architecture.

Note that we will get a bit detailed here, as we’re diving into some of the inner workings of Magento, including URL rewrites, dependency injection, and design patterns such as plugins and event observers. If you're a bit newer to Magento, some of these concepts may sound a bit foreign to you, but don't worry - I'll try to break things down into simple and easy-to-understand terms.

And if you're an experienced Magento dev, you may think you already know everything there is to know about the request-response cycle… but there is always more to learn! We'll explore a couple advanced topics and edge cases that you may not have encountered before, and share some tips on how to better optimize your Magento code.

And if any of this sounds overly complex, just remember that many architects and engineers have painstakingly diagrammed and plotted out how this entire process would work before any of the code was written. Magento was built to be extensible and flexible, so while it does have some inherent complexity, you also pick up much of the power of the framework because of the way the code has been structured.

If you’re ready… let’s get started from where it all starts: the web browser.

ℹ️ Requests can also start from executing a command line or making an API request. But in this article, we’ll be focusing solely on requests initiated from the web browser.

The Web Browser Request

When you open your favorite web browser, type in a URL, or click on a link, …you're setting off a chain of events. This ultimately leads to the display of some output on your screen.

But if you’ve ever stopped to think about what’s happening behind the scenes, it’s… a lot. But everything starts with that initial request.

When you enter a URL like https://www.example.com/products/awesome-widget into your browser's address bar and submit it, it kicks off the process. The browser takes that URL and uses it to craft an HTTP request, that is then sent to a server. This request is made up of a few components.

The first part of this request is the HTTP method. This is usually GET for standard web requests, or POST when submitting a form post. The request also includes not only the URL, but the headers, which are sort of like metadata linked to the request.

These headers contain information about the request such as the type of browser that initiated the request, whether it was referred from another site, and the type of data that is being passed along with it.

Magento request headers

The SSL Handshake

One interesting thing to note is that the request URL doesn't always exactly match the URL that was entered in the browser. If you're using HTTPS (which just about every request nowadays does by default), the browser will first try to establish a secure connection with the backend server.

This involves a process called the TLS handshake, which is when the browser and server talk to each other to exchange information. The cryptographic keys are linked, which encrypt any data that is sent between the browser and the server.

Once a secure connection is established, the browser then sends the actual request for the page. You may be thinking, “the server can now just send a request back, cool!”, but this is just the first step in the process.

Internet Randomness

When your request is sent, it needs to travel through many different servers, hopping over from one network and server to the other, until it reaches the correct server. Along the trip, it may come across proxy servers, firewalls, or other sorts of obstacles that can affect its path.

It’s like the digital version of Oregon Trail, except instead of dysentery, the request has to worry about network congestion and dropped packets.

Dropped packets

But let's assume our request survives the journey and makes it to the final destination. That server then acts as a sort of dispatcher. It sorts through all incoming requests, including the one we just made, and figures out how to handle them.

In the case of Magento, the first stop of request is the web server, which is usually either Nginx or Apache (though it could be any other web server that also supports PHP). The web server acts as a greeter which then decides what to do with the request.

Most Magento sites these days use Nginx as their web server, as it is more performant, scalable, and easier to configure than Apache. We will assume this is the case with our journey.

Nginx: The First Point of Contact

So, our little HTTP request made its way across the internet and arrived at the web server. This is usually it’s first point of contact that acts as the gatekeeper to determine where to send the request.

In the world of Magento, Nginx is often used as a reverse proxy, which is a server that sits in front of the actual Magento instance and handles incoming requests.

Magento reverse proxy with Nginx

Server Blocks

When Nginx receives the request, it looks at the server configuration to determine how to process it. The config expects a server block to be defined in this confuguration, which acts as a virtual host.

Back when I started coding, the concept of virtual hosts didn’t exist, and there could only be one website defined for one server. Since then, virtual hosts were created, which allow you to host multiple websites on a single server, which saves a lot of time and money so you don’t need to stand up a separate web server for each website.

Each of these server blocks has its own related configuration values, which tells Nginx how to handle requests for the related site.

Here’s the configuration used in my docker-magento project:

upstream fastcgi_backend {
  server unix:/sock/docker.sock;
}

server {
  listen 8000;
  return 301 https://$host$request_uri;
}

server {
  listen [::]:8443 ssl http2 ipv6only=on;
  listen 8443 ssl http2;

  ssl_certificate /etc/nginx/certs/nginx.crt;
  ssl_certificate_key /etc/nginx/certs/nginx.key;

  set $MAGE_ROOT /var/www/html;

  fastcgi_buffer_size 64k;
  fastcgi_buffers 8 128k;

  location /livereload.js {
    proxy_set_header Host $host;
    proxy_pass http://phpfpm:35729/livereload.js;
  }

  location /livereload {
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
    proxy_pass http://phpfpm:35729/livereload;
  }

  include /var/www/html/nginx[.]conf;
}

Nginx matches the incoming request against one of these server blocks based on the server name, and it then listens on the specified port. Once it has matched a request to a server block, it then starts processing it according to the rules defined in the configuration.

Magento's Nginx Configuration

Note that the last line in that configuration loads any files within our project location, /var/www/html, that start with nginx.conf, which includes the nginx.conf.sample file included in Magento (simplified below for brevity):

## ...

root $MAGE_ROOT/pub;

index index.php;
autoindex off;
charset UTF-8;
error_page 404 403 = /errors/404.php;
#add_header "X-UA-Compatible" "IE=Edge";

## ...
location / {
    try_files $uri $uri/ /index.php$is_args$args;
}

location /pub/ {
    location ~ ^/pub/media/(downloadable|customer|import|custom_options|theme_customization/.*\.xml) {
        deny all;
    }
    alias $MAGE_ROOT/pub/;
    add_header X-Frame-Options "SAMEORIGIN";
}

##...

The location / block contains the line: try_files $uri $uri/ /index.php$is_args$args;, which passes all requests to the index.php file, and then URL rewriting takes over, which is where things start to get interesting 🤓

URL Rewrites

Nginx comes with a built-in rewrite engine that is very powerful, and allows you to manipulate the requesting URL. This allows you to carry out “transforms”, such as rewrites.

A common rewrite in Magento is to remove the index.php part of the URL. By default, Magento URLs include index.php in the URL, like so:

https://www.example.com/index.php/products/awesome-widget

But with a rewrite rule, Nginx is able to strip out the index.php part to make this into a “friendly” URL, such as https://www.example.com/products/awesome-widget.

Nginx uses regular expressions to match URL patterns to transforms, sort of like a “Find and Replace”. But it can also carry out other actions on the request, such as compressing a response with gzip to speed up files transferring over the network, or serving static files like images and CSS, which bypasses Magento application code entirely.

After Nginx processes the request, it passes the baton over to the next service: PHP-FPM. This allows the web server to communicate with PHP. Nginx sends the requesting URL, method, and headers to PHP-FPM, takes that information, and then executes it in Magento, which then generates a response to return back to the user with Nginx.

Request Routing in Magento 2

Once the Magento app is bootstrapped, a fully initialized Magento app is ready to process the request. But you’re probably wondering… how does Magento know what to do with the request?

That’s where routing comes into play 😄

The Front Controller

In apps, routing is usually handled by what’s known as a “front controller”, which is the first place of entry for a request. Magento is no different. The front controller is responsible for taking the incoming request and figuring out how to handle it.

In Magento, the front controller class is located at Magento\Framework\App\FrontController. This file is the brains behind the routing process. When the front controller receives a request, it works with some other components to determine the route that is responsible for handling it.

The dispatch method of the FrontController class is the part of code which executes an action to generate a response:

vendor/magento/framework/App/FrontController.php
<?php
 
namespace Magento\Framework\App;
 
// ...
 
class FrontController implements FrontControllerInterface
{
    // ...
 
    public function dispatch(RequestInterface $request)
    {
        Profiler::start('routers_match');
        $this->validatedRequest = false;
        $routingCycleCounter = 0;
        $result = null;
        while (!$request->isDispatched() && $routingCycleCounter++ < 100) {
            /** @var \Magento\Framework\App\RouterInterface $router */
            foreach ($this->_routerList as $router) {
                try {
                    $actionInstance = $router->match($request);
                    if ($actionInstance) {
                        $result = $this->processRequest(
                            $request,
                            $actionInstance
                        );
                        break;
                    }
                } catch (\Magento\Framework\Exception\NotFoundException $e) {
                    $request->initForward();
                    $request->setActionName('noroute');
                    $request->setDispatched(false);
                    break;
                }
            }
        }
        Profiler::stop('routers_match');
        if ($routingCycleCounter > 100) {
            throw new \LogicException('Front controller reached 100 router match iterations');
        }
        return $result;
    }
 
    // ...
}
 

This code loops through all of the available routers and tries to match the request to a router. In Magento, the standard router is the main router that will check for a related controller action, which will then handle the request.

To learn more about custom routers and advanced routing, check out the Magento 2 Router Wizardry course, which goes into this into extreme detail.

The Request Object

A key player in the routing process is the request object, which is an instance of Magento\Framework\App\Request\Http. The request object contains information about the incoming request, including the URL, query params, HTTP headers, and more.

This class contains many helper functions which store information about the request, return full request names, and so on:

vendor/magento/framework/App/Request/Http.php
<?php
 
namespace Magento\Framework\App\Request;
 
// ...
 
class Http extends Request implements
    RequestContentInterface,
    RequestSafetyInterface,
    HttpRequestInterface,
    ResetAfterRequestInterface
{
    // ...
 
    public function initForward()
    {
        if (empty($this->beforeForwardInfo)) {
            $this->beforeForwardInfo = [
                'params' => $this->getParams(),
                'action_name' => $this->getActionName(),
                'controller_name' => $this->getControllerName(),
                'module_name' => $this->getModuleName(),
                'route_name' => $this->getRouteName(),
            ];
        }
        return $this;
    }
 
    // ...
 
    public function getFullActionName($delimiter = '_')
    {
        return $this->getRouteName() .
            $delimiter .
            $this->getControllerName() .
            $delimiter .
            $this->getActionName();
    }
 
    // ...
}

URL Rewrites

Before the front controller can determine the route, the request goes through the URL rewrite process within Magento’s application layer (which is another process that is a bit different from the one handled by Nginx). Remember that URL rewrites can transform the requested URL into something else, such as creating SEO-friendly URLs, or redirecting an old URL to a new one.

In Magento 2, the URL rewrite process is handled by the Magento\UrlRewrite\Controller\Router class, and is processed before the standard router.

This URL rewrite router class has a match method that takes the current request object as a parameter. This method is called by the front controller during the routing process, which then determines if the current requesting URL matches any of the available URL rewrites.

Here is that match method from this router class:

vendor/magento/module-url-rewrite/Controller/Router.php
<?php
 
namespace Magento\UrlRewrite\Controller;
 
// ...
use Magento\Framework\App\RouterInterface;
// ...
 
class Router implements RouterInterface
{
    // ...
 
    public function match(RequestInterface $request)
    {
        $rewrite = $this->getRewrite(
            $request->getPathInfo(),
            $this->storeManager->getStore()->getId()
        );
 
        if ($rewrite === null) {
            // No rewrite rule matching current URl found, continuing with
            // processing of this URL.
            return null;
        }
 
        $requestStringTrimmed = ltrim($request->getRequestString() ?? '', '/');
        $rewriteRequestPath = $rewrite->getRequestPath();
        $rewriteTargetPath = $rewrite->getTargetPath() ?? '';
        $rewriteTargetPathTrimmed = ltrim($rewriteTargetPath, '/');
 
        if (preg_replace('/\?.*/', '', $rewriteRequestPath) === preg_replace('/\?.*/', '', $rewriteTargetPath) &&
            (
                !$requestStringTrimmed ||
                !$rewriteTargetPathTrimmed ||
                strpos($requestStringTrimmed, $rewriteTargetPathTrimmed) === 0
            )
        ) {
            // Request and target paths of rewrite found without query params are equal and current request string
            // starts with request target path, continuing with processing of this URL.
            return null;
        }
 
        if ($rewrite->getRedirectType()) {
            // Rule requires the request to be redirected to another URL
            // and cannot be processed further.
            return $this->processRedirect($request, $rewrite);
        }
        // Rule provides actual URL that can be processed by a controller.
        $request->setAlias(
            UrlInterface::REWRITE_REQUEST_PATH_ALIAS,
            $rewriteRequestPath
        );
        $request->setPathInfo('/' . $rewriteTargetPath);
        return $this->actionFactory->create(
            Forward::class
        );
    }
 
    // ...
}

In this method, the router tries to find a matching URL rewrite for the current request. If a match is found, it checks if the rewrite is a redirect and if so, the router processes the redirect and then returns a redirect response.

If it is not a redirect, the router modifies the request with the alias and rewrite target, and then continues processing it in Magento through a forward action.

This router class is configured in the etc/frontend/di.xml file of the Magento_UrlRewrite module:

vendor/magento/module-url-rewrite/etc/frontend/di.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\App\RouterList">
        <arguments>
            <argument name="routerList" xsi:type="array">
                <item name="urlrewrite" xsi:type="array">
                    <item name="class" xsi:type="string">Magento\UrlRewrite\Controller\Router</item>
                    <item name="disable" xsi:type="boolean">false</item>
                    <item name="sortOrder" xsi:type="string">20</item>
                </item>
            </argument>
        </arguments>
    </type>
    <!-- ... -->
</config>

As you can see, the Magento\UrlRewrite\Controller\Router class has a sort order of 20.

The Magento Store module’s etc/frontend/di.xml file also adds some additional routers to this list, including the standard router and default router, with the sort orders of 30 and 100, respectively:

vendor/magento/module-store/etc/frontend/di.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <!-- ... -->
    <type name="Magento\Framework\App\RouterList" shared="true">
        <arguments>
            <argument name="routerList" xsi:type="array">
                <item name="standard" xsi:type="array">
                    <item name="class" xsi:type="string">Magento\Framework\App\Router\Base</item>
                    <item name="disable" xsi:type="boolean">false</item>
                    <item name="sortOrder" xsi:type="string">30</item>
                </item>
                <item name="default" xsi:type="array">
                    <item name="class" xsi:type="string">Magento\Framework\App\Router\DefaultRouter</item>
                    <item name="disable" xsi:type="boolean">false</item>
                    <item name="sortOrder" xsi:type="string">100</item>
                </item>
            </argument>
        </arguments>
    </type>
</config>

This means that the UrlRewrite router (with the sort order of 20) is processed before the standard router, and that the Magento\Framework\App\Router\DefaultRouter class with a sort order of 100 catches the request if it isn’t matched to any other routers.

Route Matching

After the URL rewrite process is complete, the front controller takes the modified request and then starts the process of matching it to a route. This is completed in the standard router class (Magento\Framework\App\Router\Base).

The standard router’s match method parses the params, and then calls matchAction:

vendor/magento/framework/App/Router/Base.php
// ...
public function match(\Magento\Framework\App\RequestInterface $request)
{
    $params = $this->parseRequest($request);
 
    return $this->matchAction($request, $params);
}
// ...

…and then matchAction loops through all of the available modules, and attempts to match the request to a controller action:

vendor/magento/framework/App/Router/Base.php
// ...
protected function matchAction(\Magento\Framework\App\RequestInterface $request, array $params)
{
    $moduleFrontName = $this->matchModuleFrontName($request, $params['moduleFrontName']);
    if (!strlen((string) $moduleFrontName)) {
        return null;
    }
 
    /**
     * Searching router args by module name from route using it as key
     */
    $modules = $this->_routeConfig->getModulesByFrontName($moduleFrontName);
    if (empty($modules) === true) {
        return null;
    }
 
    /**
     * Going through modules to find appropriate controller
     */
    $currentModuleName = null;
    $actionPath = null;
    $action = null;
    $actionInstance = null;
 
    $actionPath = $this->matchActionPath($request, $params['actionPath']);
    $action = $request->getActionName() ?: ($params['actionName'] ?: $this->_defaultPath->getPart('action'));
    $this->_checkShouldBeSecure($request, '/' . $moduleFrontName . '/' . $actionPath . '/' . $action);
 
    foreach ($modules as $moduleName) {
        $currentModuleName = $moduleName;
 
        $actionClassName = $this->actionList->get($moduleName, $this->pathPrefix, $actionPath, $action);
        if (!$actionClassName || !is_subclass_of($actionClassName, $this->actionInterface)) {
            continue;
        }
 
        $actionInstance = $this->actionFactory->create($actionClassName);
        break;
    }
 
    if (null == $actionInstance) {
        $actionInstance = $this->getNotFoundAction($currentModuleName);
        if ($actionInstance === null) {
            return null;
        }
        $action = self::NO_ROUTE;
    }
 
    // set values only after all the checks are done
    $request->setModuleName($moduleFrontName);
    $request->setControllerName($actionPath);
    $request->setActionName($action);
    $request->setControllerModule($currentModuleName);
    $request->setRouteName($this->_routeConfig->getRouteByFrontName($moduleFrontName));
    if (isset($params['variables'])) {
        $request->setParams($params['variables']);
    }
    return $actionInstance;
}
// ...

This is where the Magento\Framework\App\Router\ActionList class comes into play. The action list is like a directory of all available controller actions in Magento.

Here's a simplified version of the ActionList class:

vendor/magento/framework/App/Router/ActionList.php
<?php
 
namespace Magento\Framework\App\Router;
 
// ...
 
class ActionList
{
    // ...
 
    public function get($module, $area, $namespace, $action)
    {
        if ($area) {
            $area = '\\' . $area;
        }
        $namespace = $namespace !== null ? strtolower($namespace) : '';
        if (strpos($namespace, self::NOT_ALLOWED_IN_NAMESPACE_PATH) !== false) {
            return null;
        }
        if ($action && in_array(strtolower($action), $this->reservedWords)) {
            $action .= 'action';
        }
        $fullPath = str_replace(
            '_',
            '\\',
            strtolower(
                $module . '\\controller' . $area . '\\' . $namespace . '\\' . $action
            )
        );
        try {
            if ($this->validateActionClass($fullPath)) {
                return $this->actions[$fullPath];
            }
        } catch (ReflectionException $e) {
            return null;
        }
 
        return null;
    }
 
    private function validateActionClass(string $fullPath): bool
    {
        if (isset($this->actions[$fullPath])) {
            if (!is_subclass_of($this->actions[$fullPath], $this->actionInterface)) {
                return false;
            }
            $reflectionClass = $this->reflectionClassFactory->create($this->actions[$fullPath]);
            if ($reflectionClass->isInstantiable()) {
                return true;
            }
        }
        return false;
    }
}
 

The get method takes the module name and action path, and tries to find the corresponding controller action class. If a match is found, it then creates an instance of the action class in the validateActionClass method using the action factory.

The front controller (Magento\Framework\App\FrontController) loops through the action list, comparing the requested URL against all of the defined routes. It's a game of "match the pattern," where the front controller tries to find the best match for the current request.

The Anatomy of a Magento URL

Each route in Magento 2 is defined using a combination of the front name, controller name, and action name. For example, a route for the product view page looks like catalog/product/view. The front controller uses this information to determine which controller action will handle the request.

Magento 2 anatomy of a URL

ℹ️ As an aside, sign up for Snippets to get a Magento code snippet like this in your inbox, one every week, for 6+ months! (unsubscribe anytime)

Dispatching the Request

Once a match is found, the front controller dispatches the request to the appropriate controller action. The controller action is responsible for processing the request, and then generating the response.

Here's an example of a very simple controller action class, which returns a Page object:

app/code/Vendor/Module/Controller/Index/Index.php
<?php
 
declare(strict_types=1);
 
namespace Vendor\Module\Controller\Index;
 
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\View\Result\Page;
use Magento\Framework\View\Result\PageFactory;
 
class Index implements HttpGetActionInterface
{
    public function __construct(
        private PageFactory $pageFactory,
    ) {}
 
    public function execute(): Page
    {
        return $this->pageFactory->create();
    }
}

The execute method is where the action happens. It's responsible for processing the request, interacting with any models or services that it needs to, and then generating an appropriate response.

The “No Route” Handlers

If no match is found during the routing process, the front controller delegates the request to the “no route” handlers. These handlers act as a safety net that catch any requests that weren’t able to get matched to another router.

Magento 2 has separate no route handlers for the frontend and backend areas:

Frontend No Route Handler

Within the frontend no route handler’s process method, it gets the configuration value of web/default/no_route, which is declared in vendor/magento/module-backend/etc/adminhtml/system.xml.

It then sends the request to that route for processing:

vendor/magento/framework/App/Router/NoRouteHandler.php
// ...
public function process(\Magento\Framework\App\RequestInterface $request)
{
    $noRoutePath = $this->_config->getValue('web/default/no_route', 'default');
 
    if ($noRoutePath) {
        $noRoute = explode('/', $noRoutePath);
    } else {
        $noRoute = [];
    }
 
    $moduleName = isset($noRoute[0]) ? $noRoute[0] : 'core';
    $actionPath = isset($noRoute[1]) ? $noRoute[1] : 'index';
    $actionName = isset($noRoute[2]) ? $noRoute[2] : 'index';
 
    $request->setModuleName($moduleName)->setControllerName($actionPath)->setActionName($actionName);
 
    return true;
}
// ...

Backend No Route Handler

Similarly, the backend no route handler works in basically the same way, catching requests that fail to match a backend within a process method. But rather than having the no route path defined within a system configuration value, it is hard-coded to adminhtml/noroute/index:

vendor/magento/module-backend/App/Router/NoRouteHandler.php
// ...
public function process(\Magento\Framework\App\RequestInterface $request)
{
    $requestPathParams = explode('/', trim($request->getPathInfo(), '/'));
    $areaFrontName = array_shift($requestPathParams);
 
    if ($areaFrontName === $this->helper->getAreaFrontName(true)) {
        $moduleName = $this->routeConfig->getRouteFrontName('adminhtml');
        $actionNamespace = 'noroute';
        $actionName = 'index';
        $request->setModuleName($moduleName)->setControllerName($actionNamespace)->setActionName($actionName);
        return true;
    }
    return false;
}
// ...

CMS No Route Handler

CMS pages don’t officially have a “no route” handler, however there is a fallback mechanism defined that is important to mention. The CMS router (Magento\Cms\Controller\Router) is responsible for handling routes specific for CMS pages.

This router is defined in the etc/frontend/di.xml file of the Magento_Cms module:

vendor/magento/module-cms/etc/frontend/di.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\App\RouterList">
        <arguments>
            <argument name="routerList" xsi:type="array">
                <item name="cms" xsi:type="array">
                    <item name="class" xsi:type="string">Magento\Cms\Controller\Router</item>
                    <item name="disable" xsi:type="boolean">false</item>
                    <item name="sortOrder" xsi:type="string">60</item>
                </item>
            </argument>
        </arguments>
    </type>
</config>

The CMS router checks if the request URL matches any CMS pages and, if a match is found, dispatches the request to the appropriate CMS controller action (for more info about this, see the match method of this class).

The CMS module has a related fallback defined for non-matching routes. The “CMS No-Route Page” is defined in the web/default/cms_no_route configuration setting declared in vendor/magento/module-cms/etc/adminhtml/system.xml.

This is the route that will handle the request if no matching CMS pages are found.

Summary of the Routing Process

Before we close this article out, let’s look at a brief overview again of exactly what happens in Magento 2’s routing process:

  1. The web browser initiates an HTTP request, which includes the URL, HTTP method, headers, and other metadata.
  2. The request travels through the internet and reaches the web server, which is typically Nginx acting as a reverse proxy.
  3. Nginx processes the request based on the server configuration, including URL rewriting rules, and then passes the request to PHP-FPM.
  4. In Magento 2, the front controller (Magento\Framework\App\FrontController) is responsible for routing the request to the appropriate controller action.
  5. The front controller first checks the URL rewrite rules handled by the Magento\UrlRewrite\Controller\Router class.
  6. If no URL rewrite is found, the front controller then uses the Magento\Framework\App\Router\Base (the "standard" router) to match the request to a controller action.
  7. The standard router looks up the available controller actions using the Magento\Framework\App\Router\ActionList class and creates an instance of the appropriate controller action.
  8. The front controller then dispatches the request to the controller action, which processes the request and generates the response.
  9. If no matching route is found, the "no route" handlers (Magento\Framework\App\Router\NoRouteHandler and Magento\Backend\App\Router\NoRouteHandler) take over to handle the request.
  10. The generated response is then sent back through Nginx, which handles the response headers, caching, and delivery to the web browser.

Routing is the post critical aspect of Magento’s request-response lifecycle. It’s the process that determines which controller action handles the request, which leads to generating a response.

I hope this article was useful to you!

What’s next?

There are a few other actions that get carried out which can modify the response before it is returned to the browser. Be on the lookout for a subsequent article that goes over this process!

Until then, you may like my How To Build a Custom Router in Magento 2 for Dashes video on YouTube.

Building a custom router can be useful if you want Magento to do something that it does not do out of the box. In this case, it was handling special characters within URLs:

This article could have been a lot to take in. If it was, consider taking one of our courses to deep-dive into how everything in Magento works:

  1. Jumpstart into Magento free course (13,000+ students)
  2. Magento 2 advanced routing premium course (700+ students)
  3. Learn visually with blocks of code & inline comments (3,000+ students)