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

Video thumbnail

CodeIgniter 4, as the modern framework it is, has developed a complete system to handle routes, something that has been present for a while in other frameworks such as Laravel or Django, in which we have an additional layer or level to manage routes; therefore, we have a mechanism to indicate which controller function will process which URL through routes.

In the previous entry, we saw how to delete records in CodeIgniter 4 and worked with controllers and functions, in addition to creating our first controller and associated function and a GET type route.

The Importance of the Controller

Remember that CodeIgniter is an MVC (Model-View-Controller) framework:

  • View: The layer the end user sees.
  • Model: Responsible for manipulating data and performing CRUD operations with the database.
  • Controller: It is the intermediary; it communicates the view with the model and manages the business logic.
  • Routes: In CodeIgniter 4, they are mandatory and define how we access the controller's resources.

Composition of Routes

As with most frameworks that work with a routing layer, they basically define 3 basic parameters:

  1. The URI
  2. The Controller
  3. The function within the controller, which is responsible for performing the process

In addition to the type of route, which we discuss a bit further down, which for this example is a POST type:

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

Route Types

In CodeIgniter 4, we have multiple types of routes that we can use depending on the method to be employed, that is, if we use GET type routes, it means they will only be able to resolve GET type requests; now, if we are interested in sending a POST, which we generally use for instance to save data from a form, we have to create a POST type route:

Defining Routes in Config/Routes.php

Routes are defined in the app/Config/Routes.php file. Before starting, make sure you have the line $routes->setAutoRoute(false); commented out if you want to define your routes manually and have total control over them.

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

Therefore, if we try to access this POST type route via a browser query, it will give us the following error:

404 POST route

Simply a 404 because we are accessing via a GET request a method that is only configured to be accessed via POST.

So, we have to define another route as GET, or simply add another route for the GET:

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

Creating a Controller and the use of Namespaces

For a route to work, we need a 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 "Hello World";
   }
}

And with this, for the following function:

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

What is a Namespace?
The Namespace is a mechanism for grouping classes into folders. It is essential for the framework to be able to import the class automatically. It must always follow the folder structure: App\Controllers. If you change this path or the class name (for example, if the file is named Peliculas but the class is Pelicula), you will get a 404 error or a loading error.

We have the expected result:

Correct GET route

Because the previous route works to be consumed by both GET and POST.

Of course, we also have routes for PUT, PATCH or DELETE types:

$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');

Which we can use depending on the type of operation we want to perform.

Grouping Routes

In case we have several routes with part of the same URI being identical; for example, let's suppose our route now has the word "admin" in it:

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

Now we have to add an "admin" to the path; in my case:

http://testcodeigniter4.test/admin/save_form

GET and POST type route

You can add as many grouping levels as you want or need:

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

And to access it:

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

And if we want to create another route:

$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');    }); 
});

We can perfectly do it:

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

And with the following function in our controller:

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

We have the following:

GET index route

Parameters for Routes and URLs

We can also define parameters for routes to pass additional data; for example, a number:

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

From the function:

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

Placeholders

We can configure different placeholders according to the type of data we want to receive:

PlaceholdersDescription
(:any)Will match any character passed through the URL.
(:segment)Same as the previous one excluding the /.
(:num)Matches any integer.
(:alpha)Matches any character of the alphabet.
(:alphanum)Matches any character of the alphabet or number.
(:hash) 

Named Routes

We can also give a name to our route; later on, in another entry, we will see a practical example of all this, but suppose it is simply a reference that we give to be able to refer to the route by name and NOT directly place the URI; the advantage of this explains itself and in this way, we can perfectly vary the URI without needing to update the reference in our code:

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

And from a view; we simply place the reference to the name:

<?= route_to('admin_save') ?>

If it were to receive arguments:

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

Defining our routes for a CRUD operation

The most common thing in systems is that we need to perform CRUD operations, that is, to create, read, update and delete an entity; as you can see, for example for the create and update view we need to define two POST routes for each of these operations, one to show the form and another to process the information, plus the view for the listing would be a total of 7 different routes to perform a generic operation like doing a CRUD; luckily in modern frameworks like CodeIgniter 4, we have a method that allows us to define through a single function or method all types of routes we need:

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

You can see the routes we have to specify in the official documentation.

Common Routes

In CI4, we can say that the common routes to use in any application are:

$routes->get('pelicula','Pelicula::index'); // GET type to generate a list of elements
$routes->get('pelicula/new','Pelicula::new'); // GET type to render the creation form
$routes->post('pelicula','Pelicula::create');// POST type to process the form when creating a resource
$routes->get('pelicula/xx/edit','Pelicula::edit');// GET type to render the form to edit an element
$routes->put('pelicula/xx','Pelicula::update');// PUT type to process the entire form of an existing record
$routes->delete('pelicula/xx','Pelicula::delete'); // DELETE type which, surprise, allows deleting a record

The previous scheme are the URIs used par excellence to perform each of the indicated operations and the common route types to perform the famous CRUDs at the application level.

View Generated Routes

To be able to see the routes generated at the application level, we have the spark command:

php spark routes

Generate Routes Automatically

A very interesting feature of CodeIgniter is the ability to generate routes automatically, according to the methods we have registered; for this, in our routes file, we have to enable it:

$routes->setAutoRoute(true);

Given the following controller:

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

And you remove the previously defined routes and check which routes have been generated:

+--------+-----------------------+------------------------------------------+
| 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        |
+--------+-----------------------+------------------------------------------+

It is a particularly useful option when you have an application where you can take advantage of this type of feature.

Recommended CRUD Organization

A common organization would be:

GET /pelicula

Method: index()

Show create form

GET /pelicula/new

Method: new()

Create resource

POST /pelicula

Method: create()

Show edit form

GET /pelicula/edit/{id}

Method: edit($id)

Update

PUT /pelicula/{id}

Method: update($id)

Delete

DELETE /pelicula/{id}

Method: delete($id) or destroy($id)

PUT vs PATCH

  • PUT → Updates the entire entity.
  • PATCH → Partially updates the entity.

Example:

  • If you edit the entire post → PUT
  • If you only change the title → PATCH

Summary:

HTTP MethodSuggested RouteFunctionPurpose
GETpeliculaindexList of all movies.
GETpelicula/newnewShows the form to create a new movie.
POSTpeliculacreateProcesses and saves the new movie.
GETpelicula/edit/(:num)editShows the form to edit a specific movie.
PUT/PATCHpelicula/(:num)updateProcesses the update of data.
DELETEpelicula/(:num)deleteDeletes the resource from the database.

Important about Security

Data sent by:

  • GET → Travels in the URL.
  • POST → Is encapsulated in the request.

Common Errors

A very common one:

  • File is named Pelicula.php
  • Class is named Peliculas

That generates an error.

Names must match exactly.

That is why POST is safer for sending forms.

Good Naming Practice

Although you can invent names, it is not recommended.

Using conventions like:

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

Allows any developer to quickly understand what the application does without having to do debugging.

️ Pro Tip: Route Debugging

If your route ever doesn't respond as expected, don't guess. Always use the terminal in the root of your project to verify which routes the framework recognizes:

php spark routes

This will show you a clear table with the method, the URI, the handler, and any applied constraints. If your route does not appear there, it does not exist for CodeIgniter.

➡️ External Redirects

Sometimes, it is necessary to redirect a route to an external URL. You can easily do this in your Config/Routes.php file:

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

This is extremely useful for handling obsolete links or delegating processes to third-party services without leaving your route structure.

Conclusion

This is a very good organization for working with routes in CodeIgniter 4.

Can it change? Yes.
But this is the most used scheme today and the one most followed by convention.

The important thing is:

  • Understand what each type of request does.
  • Be consistent with names.
  • Do not reinvent the wheel.
  • Maintain clarity in the structure.

With this, you already have a solid reinforcement of the use of routes.

The next step consists of learning how to create a Rest API in CodeIgniter 4.

Having trouble with your routes in CodeIgniter 4? Learn how to configure GET, POST, PUT, DELETE methods, route groups, and resources for scalable development.

I agree to receive announcements of interest about this Blog.

Andrés Cruz

ES En español