How to create a REST API (RestFul) in CodeIgniter 4?

Video thumbnail

Remember to watch the complete video to create a Rest Api in CodeIgniter where we explore many more tricks that will surely help you a lot :)

We left off at the point where we already learned how to manage application routes and URLs in CodeIgniter 4.

The Rest APIs are a set of methods that can be consumed via GET, POST, PUT, PATCH, DELETE requests, in order to communicate our web application, in this case CodeIgniter 4 with other types of applications, Flutter, Vue and a long etc.

REMEMBER THAT a Rest API is not a protocol, it is not a standard, therefore it is not necessary to follow a particular pattern or specific order, you are free to define the methods as you see fit to perform the actions that you consider.

In short, it is a structure that we can create in our application to interconnect applications or systems, for example if you wanted to create a mobile application for your application, in our case a movie website, you can create your rest resources to share details, as well as being able to create and edit them in case you are interested in applying the complete CRUD from your mobile application; of course, it does not have to be exclusive to a mobile application with Flutter, Swift or Kotlin, it can be an application in Vue, React or any technology that allows you to consume this information through requests to your Rest Api.

In CodeIgniter we have it very easy to create a Rest Api, if at this point we manage to do a complete CRUD for our application, what we have to do now is import all that logic that we use in the controller and adapt it to our Rest Api; Rest Api that, as you can imagine, is also a class with the CRUD type functions that we explained above, what changes is the response, it will no longer be a view but a JSON or XML.

One of the features that the new version of CodeIgniter 4 brings with it is that we can now create a Rest API without the need to install third-party dependencies; For this, we have to create a class of type ResourceController in which we have to forcefully specify the model and the default format:

    protected $modelName = 'App\Models\MovieModel';    protected $format = 'json';

So, let's see how we can create a CRUD Rest Api or ApiRest using our CodeIgniter 4 (ResfFul).

The models

As you can see, when we define a controller of this type, of type Resource or ResourceController, we are binding a model to said class; In this case we are using the following model, which is the one we use in my CodeIgniter 4 course

<?php /blog/codeigniter/curso-para-dar-los-primeros-pasos-en-codeigniter-4-el-framework-php-del-futuro
namespace App\Models; 
use CodeIgniter\Model; 
class MovieModel extends Model {   
     protected $table = 'movies';    
     protected $primaryKey = 'id';   
     protected $allowedFields = ['title', 'description','category_id']; 
}

And this is used by the previous model:

<?php
namespace App\Models;
use CodeIgniter\Model;

class CategoryModel extends Model
{
    protected $table = 'categories';
    protected $primaryKey = 'id';
    protected $allowedFields = ['title'];
}

Creating the resource type class for the Rest API

We are going to create a controller class of type resource for our RestApi that implements the operations of type CRUD:

<?php
use CodeIgniter\RESTful\ResourceController;
class RestMovie extends ResourceController
{
    protected $modelName = 'App\Models\MovieModel';
    protected $format = 'json';
}

As you can see, we have the common operations to manage an entity that in this case would be a Movie, but it can be anything, a Post, an article...

Creating the Restful API in CodeIgniter 4

Before starting to show the operations, remember that this is a resource type controller, therefore, all the methods that we are going to use for the CRUD have defined names to which their functions refer; for example, the function that is in charge of displaying a list is called index or that of displaying the detail of the show entity and so on for the rest.

Customize the response of the Rest Api

I'm going to create a helper function in this very class that will allow me to return responses in a custom format for easy consumption; items like:

  1. Data
  2. Response status code
  3. Message

They are components that I always like to return to the Rest response and this is to give more information and flexibility when working with this type of structure:

function genericResponse($data, $msj, $code)
{
    if ($code == 200) {
        return $this->respond(array("data" => $data,                "code" => $code));
        //, 404, "No hay nada"        
    } else {
        return $this->respond(array("msj" => $msj,                "code" => $code));
    }
}

Creating the Restful methods

Get operations

For the Get operations, which are the simplest and we can easily use test from our browser, they allow us to obtain a particular record, given the identifier and all the records, for this it is not necessary to pass any identifier:

public function index()    {        
    return $this->genericResponse($this->model->findAll(), NULL, 200);    
}    
public function show($id = NULL)    {       
     return $this->genericResponse($this->model->find($id), NULL, 200);    
}

Post operations

With this type of operation, you are interested in creating a new resource, in this case a movie, therefore, we have to do some verification, how to process or validate the data that we are going to receive, and its subsequent saving in the database. then in response we can simply return the created resource.

Remember that for this type of operation, we have to create a method or function called create:

public function create()    {        
    $movie = new MovieModel();        
    $category = new CategoryModel();        
    if ($this->validate('movies')) {            
        if (!$this->request->getPost('category_id'))               
         return $this->genericResponse(NULL, array("category_id" => "Categoría no existe"), 500);            
         if (!$category->get($this->request->getPost('category_id'))) {               
             return $this->genericResponse(NULL, array("category_id" => "Categoría no existe"), 500);            }            
             $id = $movie->insert([                'title' => $this->request->getPost('title'),                'description' => $this->request->getPost('description'),                'category_id' => $this->request->getPost('category_id'),            ]);            return $this->genericResponse($this->model->find($id), NULL, 200);        }       
 $validation = \Config\Services::validation();        
 return $this->genericResponse(NULL, $validation->getErrors(), 500);   
 }

Here, as you can see, we are using a category since a movie belongs to a category, therefore we validate that it exists, and if it does not exist, we simply return an error response.

We also apply some validations through the validate function which is using the validations that we created in the corresponding configuration file for the validations and they look like this:

    public $movies =[        'title' => 'required|min_length[3]|max_length[255]',        'description' => 'min_length[3]|max_length[5000]'    ];

PUT or PATCH operations

For this type of request, which is to update a resource, in this case a movie, it can be the same scheme that we saw before to create a resource, since we need to validate the data that we are going to receive but this time we are also going to receive an identifier for the url that will allow us to update this resource, otherwise it is similar to a request of the post type:

public function update($id = NULL)
{
    $movie = new MovieModel();
    $category = new CategoryModel();
    $data = $this->request->getRawInput();
    if ($this->validate('movies')) {
        if (!$data['category_id'])
            return $this->genericResponse(NULL, array("category_id" => "Categoría no existe"), 500);
        if (!$movie->get($id)) {
            return $this->genericResponse(NULL, array("movie_id" => "Película no existe"), 500);
        }
        if (!$category->get($data['category_id'])) {
            return $this->genericResponse(NULL, array("category_id" => "Categoría no existe"), 500);
        }
        $movie->update($id, ['title' => $data['title'],                'description' => $data['description'],                'category_id' => $data['category_id'],]);
        return $this->genericResponse($this->model->find($id), NULL, 200);
    }
}

Delete operations

In this case, we are interested in developing a simple function that simply receives an identifier to be able to delete a record, in order to use it, we need to send a request of type delete from our http.

This has been one of the simplest operations, if not the simplest that we can perform:

public function delete($id = NULL)
{
    $movie = new MovieModel();
    $movie->delete($id);
    return $this->genericResponse("Producto eliminado", NULL, 200);
}

Final code of the Rest

In the end, our code, our Restful class looks like this:

<?php

namespace App\Controllers;

use App\Models\CategoryModel;
use App\Models\MovieModel;
use CodeIgniter\RESTful\ResourceController;

class RestMovie extends ResourceController
{
    protected $modelName = 'App\Models\MovieModel';
    protected $format = 'json';
    public function index()
    {
        return $this->genericResponse($this->model->findAll(), NULL, 200);
    }
    public function show($id = NULL)
    {
        return $this->genericResponse($this->model->find($id), NULL, 200);
    }
    public function delete($id = NULL)
    {
        $movie = new MovieModel();
        $movie->delete($id);
        return $this->genericResponse("Producto eliminado", NULL, 200);
    }
    public function create()
    {
        $movie = new MovieModel();
        $category = new CategoryModel();
        if ($this->validate('movies')) {
            if (!$this->request->getPost('category_id'))                return $this->genericResponse(NULL, array("category_id" => "Categoría no existe"), 500);
            if (!$category->get($this->request->getPost('category_id'))) {
                return $this->genericResponse(NULL, array("category_id" => "Categoría no existe"), 500);
            }
            $id = $movie->insert(['title' => $this->request->getPost('title'),                'description' => $this->request->getPost('description'),                'category_id' => $this->request->getPost('category_id'),]);
            return $this->genericResponse($this->model->find($id), NULL, 200);
        }
        $validation = \Config\Services::validation();
        return $this->genericResponse(NULL, $validation->getErrors(), 500);
    }
    public function update($id = NULL)
    {
        $movie = new MovieModel();
        $category = new CategoryModel();
        $data = $this->request->getRawInput();
        if ($this->validate('movies')) {
            if (!$data['category_id'])                return $this->genericResponse(NULL, array("category_id" => "Categoría no existe"), 500);
            if (!$movie->get($id)) {
                return $this->genericResponse(NULL, array("movie_id" => "Película no existe"), 500);
            }
            if (!$category->get($data['category_id'])) {
                return $this->genericResponse(NULL, array("category_id" => "Categoría no existe"), 500);
            }
            $movie->update($id, ['title' => $data['title'],                'description' => $data['description'],                'category_id' => $data['category_id'],]);
            return $this->genericResponse($this->model->find($id), NULL, 200);
        }
        $validation = \Config\Services::validation();
        return $this->genericResponse(NULL, $validation->getErrors(), 500);
    }
    private function genericResponse($data, $msj, $code)
    {
        if ($code == 200) {
            return $this->respond(array("data" => $data,                "code" => $code)); //, 404, "No hay nada"        
        } else {
            return $this->respond(array("msj" => $msj,                "code" => $code));
        }
    }
}

Keys to defining a REST API

Key changes compared to a traditional controller:

  • No Sessions: A REST API is stateless. We don't use sessions for success messages or to maintain data.
  • No Redirects: Instead of redirect(), we return a response with the appropriate HTTP status code.
  • Status Codes: If validation fails, we return a 400 (Bad Request) code along with the errors.

Características Principales

ResourceController: Al heredar de esta clase, CodeIgniter nos facilita herramientas específicas para APIs RESTful.

  • $modelName: Al definir el nombre del modelo aquí, el framework crea automáticamente una instancia accesible mediante $this->model, haciendo el código más limpio.
  • $format: Aquí establecemos el formato por defecto. Aunque XML es una opción, JSON es el estándar más utilizado por ser ligero y flexible.
  • respond(): Esta función sustituye a view(). Se encarga de castear los datos al formato elegido y enviar la respuesta HTTP correcta.

Main Features

ResourceController: By inheriting from this class, CodeIgniter provides us with specific tools for RESTful APIs.

  • $modelName: By defining the model name here, the framework automatically creates an instance accessible via $this->model, resulting in cleaner code.
  • $format: Here we set the default format. Although XML is an option, JSON is the most widely used standard due to its lightweight and flexible nature.
  • respond(): This function replaces view(). It handles casting the data to the chosen format and sending the correct HTTP response.

HTTP Status Codes

A vital aspect of APIs is the use of status codes. These allow the client application (e.g., Flutter or Vue) to know exactly what happened without needing to read the text message:

  • 200 OK: Successful request.
  • 201 Created: Resource created successfully.
  • 400 Bad Request: Invalid data.
  • 401 Unauthorized: The user is not authenticated.
  • 404 Not Found: The resource does not exist.
  • 500 Internal Server Error: Server error.

Postman

Postman is an application designed to test and experience the APIs that we are creating through a simple interface.

Postman is available for Windows, Mac, and Linux, so you can use it on the most popular operating systems on the market.

Postman is software that, in a few words, allows us to easily test all types of requests with a simple interface that, through forms, we can compose the request that we want to send to our application; in order to send a request to CodeIgniter from Postman, we have to compose the request as follows:

  • Set the type: POST, PUT, PATCH, DELETE or GET
  • If you are going to submit data by form, set it to Body -> x-www-form-urlencoded and set the data.

Remember that you can have a list of the URIs of our Rest functions with the command:

$ php spark routes

For routes of type rest, we will see:

GET    /                      \App\Controllers\Home::index            
GET    api/pelicula           \App\Controllers\Api\Pelicula::index    
GET    api/pelicula/new       \App\Controllers\Api\Pelicula::new      
GET    api/pelicula/(.*)/edit \App\Controllers\Api\Pelicula::edit/$1  
GET    api/pelicula/(.*)      \App\Controllers\Api\Pelicula::show/$1  
POST   api/pelicula           \App\Controllers\Api\Pelicula::create  
PATCH  api/pelicula/(.*)      \App\Controllers\Api\Pelicula::update/$1
PUT    api/pelicula/(.*)      \App\Controllers\Api\Pelicula::update/$1
DELETE api/pelicula/(.*)      \App\Controllers\Api\Pelicula::delete/$1  

Finally we send an invalid request:

Postman UI

Look in the headers section for the status code we set; in our case:

Status: 400 Bad Request

And if it is valid:

Postman send a request

We will see the answer that everything is ok.

Paginate

We can create a function that returns the paginated list, as we have worked on both the dashboard and the user module:

app\Controllers\Api\Pelicula.php

public function paginado()
{
    return $this->respond($this->model->paginate(10));
}

And the route:

app\Config\Routes.php

$routes->group('api', ['namespace' => 'App\Controllers\Api'], function ($routes) {
    $routes->get('pelicula/paginado', 'Pelicula::paginado');
    ***
});

Search with filters

For the filters by categories, search term and label, we will use the one from the previous chapter:

app\Controllers\Api\Pelicula.php

public function paginado_full()
{
    $peliculas = $this->model
        ->when($this->request->getGet('buscar'), static function ($query, $buscar) {
            $query->groupStart()->orLike('peliculas.titulo', $buscar, 'both');
            $query->orLike('peliculas.descripcion', $buscar, 'both')->groupEnd();
        })
        ->when($this->request->getGet('categoria_id'), static function ($query, $categoriaId) {
            $query->where('peliculas.categoria_id', $categoriaId);
        })
        ->when($this->request->getGet('etiqueta_id'), static function ($query, $etiquetaId) {
            $query->where('etiquetas.id', $etiquetaId);
        })
        ->select('peliculas.*, categorias.titulo as categoria, GROUP_CONCAT(DISTINCT(etiquetas.titulo)) as etiquetas, MAX(imagenes.imagen) as imagen')
        ->join('categorias', 'categorias.id = peliculas.categoria_id')
        ->join('pelicula_imagen', 'pelicula_imagen.pelicula_id = peliculas.id', 'left')
        ->join('imagenes', 'imagenes.id = pelicula_imagen.imagen_id', 'left')
        ->join('pelicula_etiqueta', 'pelicula_etiqueta.pelicula_id = peliculas.id', 'left')
        ->join('etiquetas', 'etiquetas.id = pelicula_etiqueta.etiqueta_id', 'left');
    $peliculas = $peliculas->groupBy('peliculas.id');
    $peliculas = $peliculas->paginate();
    return $this->respond($peliculas);
}

And the route:

app\Config\Routes.php

$routes->group('api', ['namespace' => 'App\Controllers\Api'], function ($routes) {
    $routes->get('pelicula/paginado', 'Pelicula::paginado');
    $routes->get('pelicula/paginado_full', 'Pelicula::paginado_full');
    ***
});

List of movies by category

An additional controller to get a list of movies given the category:

app\Controllers\Api\Pelicula.php

public function index_por_categoria($categoriaId)
{
    $peliculas = $this->model
        ->select('peliculas.*, categorias.titulo as categoria, GROUP_CONCAT(DISTINCT(etiquetas.titulo)) as etiquetas, MAX(imagenes.imagen) as imagen')
        ->join('categorias', 'categorias.id = peliculas.categoria_id')
        ->join('pelicula_imagen', 'pelicula_imagen.pelicula_id = peliculas.id', 'left')
        ->join('imagenes', 'imagenes.id = pelicula_imagen.imagen_id', 'left')
        ->join('pelicula_etiqueta', 'pelicula_etiqueta.pelicula_id = peliculas.id', 'left')
        ->join('etiquetas', 'etiquetas.id = pelicula_etiqueta.etiqueta_id', 'left')
        ->where('peliculas.categoria_id', $categoriaId)
        ->groupBy('peliculas.id')->paginate();
    return $this->respond($peliculas);
}

And the route:

app\Config\Routes.php

$routes->group('api', ['namespace' => 'App\Controllers\Api'], function ($routes) {
    $routes->get('pelicula/index_por_categoria/(:num)', 'Pelicula::index_por_categoria/$1');
    ***
});

The next step we need to take is to consume the app in CodeIgniter 4 from an application; for this, we need to know how to use CORS in CodeIgniter 4.

The Rest APIs are a set of methods that can be consumed via GET, POST, PUT, PATCH, DELETE requests and we are going to learn how to create a CRUD RestFul in CodeIgniter 4 using resource type controllers.

I agree to receive announcements of interest about this Blog.

Andrés Cruz

ES En español