Routing in CodeIgniter 4: The Definitive Guide to Setting Up Professional CRUD

- Andrés Cruz - ES En español

Video thumbnail

CodeIgniter 4, as the modern framework that it is, has a complete system for handling routes, something that has been present for a while in other frameworks like Laravel or Django. In these, there is an additional routing layer that acts as an intermediary between system URLs and controllers, allowing us to precisely define which controller function will process each URL.

In the previous entry we saw how to delete records in CodeIgniter 4, where we worked with controllers and their functions, created our first controller, and defined a GET type route.

The Importance of the Controller

Remember that CodeIgniter 4 is an MVC (Model-View-Controller) framework, and each layer has a well-defined responsibility:

  • View: The layer seen by the end user; it handles presentation.
  • Model: Handles data manipulation and performs CRUD operations with the database.
  • Controller: It is the intermediary; it connects the view with the model and manages business logic.
  • Routes: In CodeIgniter 4, routes are mandatory (when auto-routing is disabled) and define how we access controller resources from a URL.

Route Composition

Just like in most frameworks that implement a routing layer, a basic route requires three essential parameters:

  1. The URI (the URL to be mapped)
  2. The controller that will handle the request
  3. The function within the controller that will execute the process

In addition to these three parameters, we must define the route type (or HTTP method), which we discuss in more detail below. For example, the following is a POST type route:

$routes->post('/save_form', 'Admin::save');

Route Types

In CodeIgniter 4 we have multiple route types available depending on the HTTP method we need. If we define a GET type route, it will only resolve requests of that type. Similarly, if we want to process data sent from a form, we need a POST route. Choosing the correct method is not only a best practice: it is also a security measure, as it prevents a URL from accepting requests it shouldn't.

Defining Routes in app/Config/Routes.php

Routes are defined in the app/Config/Routes.php file. Before starting, make sure to set $routes->setAutoRoute(false); if you want to define your routes manually and have total control over them. This way, only the routes you explicitly declare will be accessible.

$routes->post('/save_form', 'Admin::save');

Therefore, if we try to access this POST type route via a browser request (which defaults to GET), we will get the following error:

404 post route

A simple 404, because we are accessing via a GET request a method that is only configured to be consumed via POST.

The solution is simple: define an additional GET type route for that same URI:

$routes->get('/', 'Pelicula::index');
$routes->get('/save_form', 'Admin::save');
$routes->post('/save_form', 'Admin::save');

Creating a Controller and Using Namespaces

For a route to work, we need a backing controller. We will create one called Pelicula.php in the app/Controllers folder:

<?php
namespace App\Controllers;
class Pelicula extends BaseController {
    public function index() {
        return "Hola Mundo";
    }
}

And we add a save() function to handle saving:

public function save() { return "save"; }

What is a Namespace?
The namespace is a mechanism to group classes under a logical path that matches the project's folder structure. It is essential for the framework to be able to autoload the class correctly. It must always follow the structure: App\Controllers. If the file name is Peliculas.php but the class is named Pelicula, you will get a 404 error or a class loading error. The file and the class must have the exact same name.

With everything configured correctly, we will have the expected result:

Correct get route

The previous route works to be consumed by both GET and POST, since we defined both pointing to the same controller and function.

Of course, we also have the PUT, PATCH, and DELETE methods:

$routes->get('products', 'Product::feature');
$routes->post('products', 'Product::feature');
$routes->put('products/(:num)', 'Product::feature');
$routes->patch('products/(:num)', 'Product::feature');
$routes->delete('products/(:num)', 'Product::feature');

Each of these methods responds to a different type of operation, and using them correctly is the basis for building a semantically correct API or CRUD.

Grouping Routes

When several routes share a common segment in the URI —for example, the admin prefix—, CodeIgniter 4 allows us to group them with the $routes->group() method to avoid repeating that segment in each definition:

$routes->group('admin', function ($routes) {
   $routes->get('save_form', 'Admin::save');
   $routes->post('save_form', 'Admin::save');
});

With this, the full URI to access those routes becomes:

http://testcodeigniter4.test/admin/save_form

Get and post type route

Additionally, you can nest groups to create deeper structures with no level limits:

$routes->group('admin', function ($routes) {
  $routes->group('user', function ($routes) {
     $routes->get('save_form', 'Admin::save');
     $routes->post('save_form', 'Admin::save');
   });
});

The resulting URI would be:

http://testcodeigniter4.test/admin/user/save_form

And if you need to add more routes within the same group, you simply declare them at the same level:

$routes->group('admin', function ($routes) {
   $routes->group('user', function ($routes) {
   $routes->get('save_form', 'Admin::save');
   $routes->post('save_form', 'Admin::save');
   $routes->get('index', 'Admin::index');
   });
});

Making it accessible at:

http://testcodeigniter4.test/admin/user/index

With the following function in the controller:

public function index() { return "Admin"; }

We will have the expected result:

Get index route

Parameters in Routes and URLs

We can also define parameters within routes to pass dynamic data directly from the URL. For example, to receive an ID number:

$routes->get('save_form/(:num)', 'Admin::save/$1', ['as' => 'admin_save']);

The captured parameter ($1) is automatically passed as an argument to the controller function:

public function save($id) { return "save: " . $id; }

Placeholders

Placeholders allow us to restrict or validate the type of data expected in a URL segment. CodeIgniter 4 offers the following natively:

PlaceholderDescription
(:any)Matches any character passed through the URL, including /.
(:segment)Same as (:any), but excludes the / character.
(:num)Matches integer numbers only.
(:alpha)Matches any character of the alphabet (no numbers or symbols).
(:alphanum)Matches any combination of letters and numbers.
(:hash)Similar to (:segment), but encodes the value internally. Useful for obfuscated IDs.

Named Routes

We can assign a name to any route using the 'as' option. This allows us to reference it by its name instead of writing the URI directly. The advantage is clear: if at any point you need to change the URI, you only do it in the route definition and the rest of the code updates automatically:

$routes->get('save_form', 'Admin::save', ['as' => 'admin_save']);

From a view, we use the route_to() helper function to generate the URL from the name:

<?= route_to('admin_save') ?>

If the route receives arguments, we pass them as additional parameters:

<?= route_to('admin_save', $movie->id) ?>

Defining Routes for a Complete CRUD

The most common thing in any application is needing CRUD operations: create, read, update, and delete resources. To implement this manually, you would need at least 7 different routes: one for the listing, one to show the creation form, another to process that form, another for the edit form, another to process it, one to delete, and optionally one to show details. Fortunately, CodeIgniter 4 offers the $routes->resource() method, which generates all those routes with a single line:

$routes->resource('photos', $options);

You can check the routes it generates in detail in the official documentation.

Common CRUD Routes

In CodeIgniter 4, the most used routes for any CRUD operation are the following:

$routes->get('pelicula', 'Pelicula::index');         // Item listing
$routes->get('pelicula/new', 'Pelicula::new');       // Creation form
$routes->post('pelicula', 'Pelicula::create');       // Processes and saves the new record
$routes->get('pelicula/(:num)/edit', 'Pelicula::edit');   // Edit form
$routes->put('pelicula/(:num)', 'Pelicula::update'); // Processes the update
$routes->delete('pelicula/(:num)', 'Pelicula::delete'); // Deletes the record

This scheme represents the URIs and HTTP methods used by convention to perform CRUD operations at the web application level.

Viewing Generated Routes

To inspect at any time what routes your application has registered, CodeIgniter 4 includes the spark command:

php spark routes

This command prints a table in the console with the HTTP method, URI, and associated handler. If a route does not appear in that table, it does not exist for the framework.

Automatically Generating Routes

An interesting feature of CodeIgniter 4 is the ability to automatically generate routes from the public methods registered in your controller. To enable it, you must turn on the option in your routes file:

$routes->setAutoRoute(true);

Given the following example controller:

<?php
namespace App\Controllers;
class Pelicula extends BaseController {
    public function index()
    {
        echo "Hola Mundo";
    }
    public function test($x = 0, $n = 10)
    {
        echo "Hola Mundo test " . $x . " " . $n;
    }
    // public function new()
    // {
    //    echo view("pelicula/create");
    // }
    // public function create()
    // {
    //    echo "Procesando el Form!";
    // }
    public function edit($id)
    {
        echo view("pelicula/edit");
    }
    public function update($id)
    {
        echo "Procesando el Form! " . $id;
    }
}

By removing manually defined routes and running php spark routes, you will see that the framework has automatically generated the following:

+--------+-----------------------+------------------------------------------+
| Method | Route                 | Handler                                  |
+--------+-----------------------+------------------------------------------+
| auto   | /                     | \App\Controllers\Home::index             |
| auto   | home                  | \App\Controllers\Home::index             |
| auto   | home/index[/...]      | \App\Controllers\Home::index             |
| auto   | pelicula              | \App\Controllers\Pelicula::index         |
| auto   | pelicula/index[/...]  | \App\Controllers\Pelicula::index         |
| auto   | pelicula/test[/...]   | \App\Controllers\Pelicula::test          |
| auto   | pelicula/edit[/...]   | \App\Controllers\Pelicula::edit          |
| auto   | pelicula/update[/...] | \App\Controllers\Pelicula::update        |
+--------+-----------------------+------------------------------------------+

This is a particularly useful option in small projects or early development phases, where speed matters more than exhaustive route control. However, for production applications it is recommended to define routes manually for greater security and clarity.

Recommended CRUD Organization

The structure most commonly used by convention in CodeIgniter 4 for a complete CRUD would be the following:

GET /pelicula

Method: index()

Displays the general listing of records.

GET /pelicula/new

Method: new()

Displays the form to create a new resource.

POST /pelicula

Method: create()

Processes the form and saves the new record in the database.

GET /pelicula/edit/{id}

Method: edit($id)

Displays the edit form pre-filled with the data of the corresponding record.

PUT /pelicula/{id}

Method: update($id)

Processes the form and updates the existing record.

DELETE /pelicula/{id}

Method: delete($id) or destroy($id) — Deletes the record from the database.

PUT vs PATCH

  • PUT → Replaces the entire entity with the sent data.
  • PATCH → Updates partially the entity; only the sent fields.

Practical example:

  • If the user edits a complete post → PUT
  • If only the title changes → PATCH

Summary of CRUD routes and methods:

HTTP MethodSuggested RouteFunctionPurpose
GETpeliculaindex()Listing of all movies.
GETpelicula/newnew()Displays the form to create a new movie.
POSTpeliculacreate()Processes and saves the new movie.
GETpelicula/edit/(:num)edit($id)Displays the form to edit a specific movie.
PUT / PATCHpelicula/(:num)update($id)Processes the data update.
DELETEpelicula/(:num)delete($id)Deletes the resource from the database.

Important on Security

Sent data behaves very differently depending on the HTTP method used:

  • GET → Data travels in the URL and is visible in the browser bar, history, and server logs. Never use GET to send sensitive information.
  • POST → Data is encapsulated in the request body, making it safer for submitting forms with sensitive information.

Common Errors When Defining Routes

One of the most frequent errors when starting with CodeIgniter 4 is the mismatch between the controller file name and the class name:

  • File: Pelicula.php
  • Class defined as: class Peliculas

This discrepancy causes a class loading error and returns a 404. The file name and the class name must match exactly, respecting uppercase and lowercase letters.

Best Practices in Function Naming

Although technically you can give any name to your controller functions, following established conventions is an indispensable practice in collaborative projects. The standard names are:

  • index
  • create
  • new
  • edit
  • update
  • delete

Following these conventions allows any developer to understand at a glance what each function does, without needing to read the code or debug.

️ Pro Tip: Route Debugging

If your route ever doesn't respond as expected, don't guess. Always run the following command from your project root to check which routes the framework recognizes:

php spark routes

This command prints a clear table with the HTTP method, URI, handler, and any applied restrictions. If your route does not appear in that table, it does not exist for CodeIgniter 4.

➡️ External Redirects

Sometimes it is necessary to redirect a route to an external URL. You can easily do this within app/Config/Routes.php using the addRedirect() method:

$routes->addRedirect('google', 'https://google.com');

This is extremely useful for handling outdated URLs, doing route migrations without breaking existing links, or delegating processes to third-party services without leaving your application's routing structure.

Conclusion

This is a very solid and proven organization for working with routes in CodeIgniter 4. It may vary depending on each project's needs, but it is the most used scheme nowadays and the one most followed by convention in the community.

The important thing is:

  • Understand what each type of HTTP request does and when to use it.
  • Be consistent with function names and URIs.
  • Don't reinvent the wheel; conventions exist for a reason.
  • Maintain clarity in the structure to facilitate project maintenance.

With this you already have a solid mastery of CodeIgniter 4's routing system. The next step is to learn how to create a REST API in CodeIgniter 4.

Learn how to configure routes in CodeIgniter 4 from scratch: GET, POST, PUT, and DELETE methods, route groups, placeholders, named routes, and a complete CRUD implementation, with code examples.


Únete a la comunidad de desarrolladores que han decidido dejar de picar código y empezar a construir productos reales. Recibe mis mejores trucos de arquitectura cada semana:

I agree to receive announcements of interest about this Blog.