CodeIgniter Forums
Customizable 404 Error Pages for Specific Routes - Printable Version

+- CodeIgniter Forums (https://forum.codeigniter.com)
+-- Forum: CodeIgniter 4 (https://forum.codeigniter.com/forumdisplay.php?fid=28)
+--- Forum: CodeIgniter 4 Feature Requests (https://forum.codeigniter.com/forumdisplay.php?fid=29)
+--- Thread: Customizable 404 Error Pages for Specific Routes (/showthread.php?tid=91512)



Customizable 404 Error Pages for Specific Routes - datamweb - 08-22-2024

Currently, CodeIgniter allows developers to override the default 404 error page using the $routes->set404Override() method. This is useful when you want a custom 404 page across the entire application. However, in some cases, especially in larger applications or when using packages, there is a need to define custom 404 error pages for specific routes or route groups. Unfortunately, at the moment, CodeIgniter doesn't offer native support for handling route-specific 404 errors.

But in many web service(API) projects, there is a need to define custom 404 error pages for specific routes or groups of routes.


Global Impact of $routes->set404Override():

When we use the $routes->set404Override() method in CodeIgniter, the custom 404 page applies globally across the entire project. For instance, if you are working on a package that has its own route definitions and use this method like:

PHP Code:
$routes->set404Override('Datamweb\BlankCI\Controllers\Errors::Show404'); 

This override affects the entire project, not just the specific routes related to your package. As a result, all other parts of the application that depend on their own 404 error behavior will be overridden by this configuration. This makes it impossible to isolate different behaviors for different routes or contexts, leading to conflicts between different components of the project.

Multi-Module or Package Applications:

In projects with multiple modules or packages, different components may require distinct 404 behaviors. For example, a REST API section might need a minimal JSON response, while the frontend might require a more user-friendly HTML page for the 404 error. The current implementation makes it difficult to implement different 404 responses for different areas of the application.

Hardcoding 404 Error Handling in Controllers:

Without the ability to define custom 404 pages per route or route group, developers often have to resort to handling errors manually inside controllers. This can be cumbersome and makes the codebase harder to maintain, as each controller has to implement its own logic to check for specific methods and return custom 404 responses.

For example, developers might end up writing code like this:

PHP Code:
$routes->match(['GET''POST''DELETE','PUT' and ...],'example-route''ExampleController::method'); 

PHP Code:
  
public function method(): ResponseInterface
  
{
      if (!$this->request->is('post')) {
          return $this->formatResponse(
              'error',
              ResponseInterface::HTTP_METHOD_NOT_ALLOWED,
              "The HTTP method({$this->request->getMethod()}) used is not allowed for this endpoint.",
              null,
              ['method' => 'The HTTP method used is not allowed for this endpoint. Allowed methods are: \"POST\"."],
              null
          );
      }
  } 

While this works, it increases complexity and clutter in controllers, which should ideally focus on core business logic, not error handling.

Proposed Solution:

To address the limitations of the current implementation, I propose adding a method to the `RouteCollection` class that allows developers to set custom 404 error pages per route or route group. Here's how it could work:

New Method withCustom404() for Routes:
Developers should be able to define custom 404 pages for specific routes directly within the route definition. For example:

PHP Code:
  $routes->post'example-route''ExampleController::method')
          ->withCustom404(function() {
              return view('errors/custom404');
          }); 


In this case, if a user accesses the example-route with an unsupported method or path, the custom 404 page defined in withCustom404 will be returned instead of the global one.

Support for Grouped Routes:
The method should also support route groups, so developers can apply a custom 404 error page to a set of routes within the same group. For example:

PHP Code:
  $routes->group('api', ['namespace' => 'App\Controllers\Api'], function($routes) {
      $routes->get('users''UserController::index');
      $routes->post('users/create''UserController::create');
     
  
})->withCustom404(function() {
          return $this->respond(['error' => 'API not found'], 404);
      });; 


In this case, all routes under the `/api` namespace will return a JSON 404 error, while the rest of the application will continue using the default 404 page.

No Global Impact:

The custom 404 page defined using withCustom404() or similar should only affect the specific route or group of routes. It should not override the global 404 handler for the entire project unless explicitly set at a global level.

Adding the ability to define route-specific 404 error pages would provide a more flexible and modular approach to error handling in CodeIgniter. This feature would be especially beneficial in larger applications or in cases where developers use multiple packages, allowing for distinct behaviors without affecting the global application.


RE: Customizable 404 Error Pages for Specific Routes - ozornick - 08-22-2024

It is difficult for the first viewing.
~N methods are needed: for 500, 403, 404 * environments * number of route groups


RE: Customizable 404 Error Pages for Specific Routes - kenjis - 08-22-2024

The idea to customize 404 page is reasonable and good.

But the sample code is difficult to understand or seems contradictory.
Because these methods for routes are to define the specified routes.
But 404 means there is no route.

Also, if you add methods like that to customize the 404 page for specific routes, 
it will be difficult to tell which routes have been customized.

For example, let's say you create a package and customize the 404 page under a specific path (e.g. /some-api/).
However, the developers using that package want to display their own 404 page. How can they do that?
It probably seems quite troublesome to do so.


RE: Customizable 404 Error Pages for Specific Routes - kenjis - 08-22-2024

How about defining routes for the 404 page like this?

PHP Code:
// For customized 404 page.
$routes->get('api/(:any)''Error404::index'); 

PHP Code:
<?php

namespace App\Controllers;

use 
CodeIgniter\API\ResponseTrait;
use 
CodeIgniter\HTTP\ResponseInterface;

class 
Error404 extends BaseController
{
    use ResponseTrait;

    public function index(): ResponseInterface
    
{
        return $this->respond(['error' => 'API not found'], 404);
    }




RE: Customizable 404 Error Pages for Specific Routes - datamweb - 08-22-2024

In my project, I solved this issue by overriding the RouteCollection class and adding a method to handle custom 404 pages for specific routes. The method I use is called setCustom404ForRoute, which works as follows:

PHP Code:
/**
 * Sets a custom 404 page for a specific route.
 *
 * This method checks if the current URI matches the specified route name
 * and if the HTTP method used is not allowed. If both conditions are met,
 * it overrides the default 404 behavior with the provided callable.
 *
 * @param callable|string|null $callable       A callable function or controller method to handle the custom 404.
 * @param string               $routeName      The name of the route to apply the custom 404 to.
 * @param array|string         $allowedMethods The allowed HTTP methods for the route.
 */
public function setCustom404ForRoute($callablestring $routeName, array|string $allowedMethods): RouteCollectionInterface
{
    /** @var RequestInterface $request */
    $request service('request');

    // Check if the current URI matches the route name and the HTTP method is not allowed
    if (uri_string() === $routeName && ! $request->is($allowedMethods)) {
        // Override the default 404 handler with the custom one
        unset($this->override404);
        $this->override404 $callable;
    }

    return $this;



To manage the responses, I've created a controller that sends error messages in a detailed JSON format. For example, if a GET method is used instead of POST on a specific route, the response looks like this:

PHP Code:
<?php

declare(strict_types=1);

/**
 * This file is part of CodeIgniter BlankCI.
 *
 * (c) Pooya Parsa Dadashi <[email protected]>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Datamweb\BlankCI\Controllers;

use 
CodeIgniter\HTTP\ResponseInterface;
use 
Datamweb\BlankCI\Controllers\Api\BaseApiController;

class 
Errors extends BaseApiController
{
    public function show404()
    {
        return $this->formatResponse(
            'error',
            ResponseInterface::HTTP_METHOD_NOT_ALLOWED,
            "The HTTP method({$this->request->getMethod()}) used is not allowed for this endpoint.",
            null,
            ['method' => 'The HTTP method used is not allowed for this endpoint. Allowed methods are: "POST".'],
            null
        
);
    }

    public function getCurrentVersion()
    {
        return '';
    }


And my Routes:

PHP Code:
$routes->post('api/v1/ticket-history/record-change''TicketHistoryController::recordChange')->setCustom404ForRoute('Datamweb\BlankCI\Controllers\Errors::Show404''api/v1/ticket-history/record-change''post'); 

Here’s an example of the response for a GET('GET', 'POST', 'DELETE','PUT' and ... ) (allowed method (POST))request when the HTTP method is not allowed:

Code:
{
    "status": "error",
    "code": 405,
    "message": "The HTTP method(GET) used is not allowed for this endpoint.",
    "data": null,
    "errors": {
        "method": "The HTTP method used is not allowed for this endpoint. Allowed methods are: \"POST\"."
    },
    "pagination": null,
    "meta": {
        "requestId": "66c80342f2dd1",
        "timestamp": "2024-08-23T03:34:26Z"
    }
}

In case the correct POST method is used but the data fails validation, the response includes a 400 error with detailed validation issues:

Code:
{
    "status": "error",
    "code": 400,
    "message": "Error in validating the data sent.",
    "data": null,
    "errors": {
        "ticket_id": "Ticket ID must be an integer.",
        "change_type": "Change type is required.",
        "old_value": "Old value is required.",
        "new_value": "New value is required.",
        "changed_by": "Changed by is required."
    },
    "pagination": null,
    "meta": {
        "requestId": "66c803780155c",
        "timestamp": "2024-08-23T03:35:20Z"
    }
}

Additionally, other routes can still use CodeIgniter's default 404 page or be overridden with a custom page using the method $routes->set404Override(). This approach allows developers to easily manage their custom 404 pages while maintaining flexibility in handling specific routes.

@kenjis This idea stems from a real need in a practical project, which is why I believe, although it requires further review for better implementation, adding it to CodeIgniter would be an excellent feature and would provide greater flexibility. and what you mentioned does not yield the same result.


RE: Customizable 404 Error Pages for Specific Routes - kenjis - 08-23-2024

Your implementation uses 404Override for more than just 404. I think that's a bad idea.
405 is not 404. It's confusing to have setCustom404ForRoute() handle 405.

Also, such implementations that dynamically override 404Override cannot be cached.
There is currently no feature to cache defined routes, but if the number of routes becomes large, it would be better to cache them to improve performance.

I still recommend the implementation like this:

PHP Code:
// Normal route
$routes->post(
    'api/v1/ticket-history/record-change',
    'TicketHistoryController::recordChange'
);
// For 405 error
$routes->match(
    ['GET''DELETE''PUT'],
    'api/v1/ticket-history/record-change',
    'Datamweb\BlankCI\Controllers\Errors::show405'
); 

or add a new method to generate such routes internally.
E.g.,
PHP Code:
$routes->method(
    ['POST'// Allowed methods
    'api/v1/ticket-history/record-change',
    'TicketHistoryController::recordChange',
    'Datamweb\BlankCI\Controllers\Errors::show405'// Controller for errors
); 



RE: Customizable 404 Error Pages for Specific Routes - datamweb - 08-23-2024

(08-23-2024, 12:05 AM)kenjis Wrote: I still recommend the implementation like this:

PHP Code:
// Normal route
$routes->post(
    'api/v1/ticket-history/record-change',
    'TicketHistoryController::recordChange'
);
// For 405 error
$routes->match(
    ['GET''DELETE''PUT'],
    'api/v1/ticket-history/record-change',
    'Datamweb\BlankCI\Controllers\Errors::show405'
); 

This approach is definitely not suitable for me, as it requires defining individual error handling for each disallowed method separately. With an increasing number of routes, this method becomes overly complex and hard to maintain.

Code:
+--------+--------------------------------------------+
| Method | Route                                      |
+--------+--------------------------------------------+
| GET    | api/v1/ticket-history/record-change        |
| PUT    | api/v1/ticket-history/record-change        |
| DELETE | api/v1/ticket-history/record-change        |
| POST   | api/v1/ticket-history/record-change        |
+--------+--------------------------------------------+

Quote:or add a new method to generate such routes internally.
E.g.,
PHP Code:
$routes->method(
    ['POST'// Allowed methods
    'api/v1/ticket-history/record-change',
    'TicketHistoryController::recordChange',
    'Datamweb\BlankCI\Controllers\Errors::show405'// Controller for errors
); 

The second suggestion seems appealing and logical, but I currently have no idea on how to implement it.
Thank you for participating in this discussion!


RE: Customizable 404 Error Pages for Specific Routes - kenjis - 08-23-2024

In terms of 405 errors, I think there is room to add a feature that specifies a resource and the allowed HTTP methods.

If you define a resource (URI path) and the allowed HTTP methods, then CI4 should return 405 to requests for methods that are not allowed.
And the server must generate an Allow header in a 405 response with a list of methods that the target resource currently supports.
See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405

However, the current router does not have the concept of “resources”, so it may not be easy to implement.


RE: Customizable 404 Error Pages for Specific Routes - ozornick - 08-23-2024

You can extend the Error Handler and split HTTP errors into a handler in the controller.
As schematically (soorry, with phone):

PHP Code:
if ($exception->getCode() >= 400 && $controller->overWrite404) {
    return  $controller->overWrite404();


if you need to process any response in 404, this is the work of Filters. Recognize the response and also replace the error