Índice de contenido
- Los modelos
- Creando la clase de tipo recurso para la Rest Api
- Creando la Api Restful en CodeIgniter 4
- Personalizar la respuesta de la Rest Api
- Creando los métodos de la Restful
- Operaciones tipo Get
- Operaciones tipo Post
- Operaciones tipo PUT o PATCH
- Operaciones tipo Delete
- Código final de la Rest
- Claves para definir una Rest API
- Códigos de Estado HTTP
- Postman
- Paginado
- Búsqueda con filtros
- Listado de películas por categoría
Recuerda ver el vídeo completo para crear una Api Rest en CodeIgniter en donde exploramos muchos trucos más que seguramente te servirán de mucho :)
Nos quedamos en el cual, ya aprendimos a manejar las rutas y URLs de la aplicación en CodeIgniter 4.
Las Rest APIs son un conjunto de métodos que pueden ser consumidos vía peticiones GET, POST, PUT, PATCH, DELETE, para poder comunicar nuestra aplicación web, en este caso CodeIgniter 4 con otros tipos de aplicaciones, Flutter, Vue y un largo etc.
RECUERDA QUE una Api Rest no es un protocolo, no es un estándar por lo tanto no es necesario que siga un patrón en particular u orden en especifico, tu eres libre de definir los métodos como mejor te parezca para realizar las acciones que tu consideres.
Una de las características que trae consigo las nueva versión de CodeIgniter 4 es que ahora podemos crear una Api Rest sin necesidad de instalar dependencias de terceros; para esto, tenemos que crear una clase de tipo ResourceController en la cual tenemos que especificar a fuerza el modelo y el formato por defecto:
protected $modelName = 'App\Models\MovieModel'; protected $format = 'json';Así que, vamos a ver cómo podemos crear una Rest Api o ApiRest de tipo CRUD empleando nuestro CodeIgniter 4 (ResfFul).
En definitiva, es una estructura que podemos crear en nuestra aplicación para interconectar aplicaciones o sistemas, por ejemplo si quisieras crear una aplicación móvil para tu aplicación, en nuestro caso una web de películas, puedes crear tus recursos Rest para compartir el detalle de las mismas, así como de poder crear y editar las mismas en caso de que te interese aplicar el CRUD completo desde tu aplicación móvil; por supuesto, no tiene por qué ser exclusivo de una aplicación móvil con Flutter, Swift o Kotlin, puede ser una aplicación en Vue, React o cualquier tecnología que te permita consumir esta información mediante peticiones a tu Rest Api.
En CodeIgniter la tenemos muy fácil para crear una Rest Api, si ya en este punto logramos hacer un CRUD completo para nuestra aplicación, lo que tenemos que hacer ahora es importar toda esa lógica que empleamos en el controlador y adaptarla a nuestra Rest Api; Rest Api que como puedes suponer viene siendo también una clase con las funciones tipo CRUD que explicamos anteriormente, lo que cambia es la respuesta, ya no será una vista sino un JSON o XML.
Los modelos
Como puedes ver, cuando definimos un controlador de este tipo, de tipo recurso o ResourceController, estamos atando un modelo a dicha clase; en este caso estamos empleando el siguiente modelo que es el que empleamos en mi curso de CodeIgniter 4 en Udemy:
<?php
namespace App\Models;
use CodeIgniter\Model;
class MovieModel extends Model {
protected $table = 'movies';
protected $primaryKey = 'id';
protected $allowedFields = ['title', 'description','category_id'];
}Y este lo emplea el modelo anterior:
<?php
namespace App\Models;
use CodeIgniter\Model;
class CategoryModel extends Model
{
protected $table = 'categories';
protected $primaryKey = 'id';
protected $allowedFields = ['title'];
}
Creando la clase de tipo recurso para la Rest Api
Vamos a crear una clase controlador de tipo recurso para nuestra RestApi que implementa las operaciones de tipo CRUD:
<?php
use CodeIgniter\RESTful\ResourceController;
class RestMovie extends ResourceController
{
protected $modelName = 'App\Models\MovieModel';
protected $format = 'json';
}Como puedes ver, tenemos las operaciones comunes para administrar alguna entidad que en este caso sería una Movie, pero puede ser cualquier cosa, un Post, un artículo...
Creando la Api Restful en CodeIgniter 4
Antes de comenzar a mostrar las operaciones, recuerda que este es un controlador de tipo recurso, por lo tanto, todos los métodos que vamos a emplear para el CRUD tienen nombres definidos a lo que se refiere sus funciones; por ejemplo, la función que se encarga de mostrar un listado se llama index o la de mostrar el detalle de la entidad show y así para el resto.
Personalizar la respuesta de la Rest Api
Voy a crear una función de ayuda en esta misma clase que me permitirá devolver las respuesta en un formato personalizado para facilitar su consumo; elementos como:
- Data
- Código de estado de la respuesta
- Mensaje
Son componentes que siempre me gusta devolver a la respuesta de la Rest y esto es para dar más información y flexibilidad al momento de trabajar con este tipo de estructuras:
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));
}
}Creando los métodos de la Restful
Operaciones tipo Get
Para las operaciones Get, que son las más sencillas y podemos emplear probar fácilmente desde nuestro navegador, nos permiten obtener un registro en particular, dado el identificador y todos los registros, para esto no hace falta pasar ningún identificador:
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);
}Operaciones tipo Post
Con este tipo de operaciones le interesa es crear un nuevo recurso, en este caso una movie, por lo tanto, tenemos que hacer algunas verificaciones cómo, procesar o validar los datos que vamos a recibir, y su posterior guardado en la base de datos, luego, como respuesta simplemente podemos devolver el recurso creado.
Recuerda que para este tipo de operación, tenemos que crear un método o función llamado 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);
}Aquí como puedes ver, estamos empleando una categoría ya que una movie pertenece a una categoría por lo tanto validamos que la misma exista, y si no existe simplemente devolvemos una respuesta de error.
También aplicamos algunas validaciones mediante la función de validate que esta empleando las validaciones que creamos en en archivo de configuración correspondiente para las validaciones y lucen así:
public $movies =[ 'title' => 'required|min_length[3]|max_length[255]', 'description' => 'min_length[3]|max_length[5000]' ];Operaciones tipo PUT o PATCH
Para este tipo de petición que es de actualizar un recurso, en este caso una movie, puede ser el mismo esquema que vimos anteriormente para crear un recurso, ya que necesitamos validar los datos que vamos a recibir pero esta vez también vamos a recibir un identificador por la url que nos permitirá actualizar este recurso, por lo demás es similar al de una petición de tipo post:
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);
}
}Operaciones tipo Delete
En este caso, nos interesa desarrollar una sencilla función que simplemente recibe un identificador para poder eliminar un registro, para poder emplearlo, necesitamos enviar una petición de tipo delete de nuestro http.
Esta viene siendo una de las operaciones más sencillas por no decir la más sencilla que podemos realizar:
public function delete($id = NULL)
{
$movie = new MovieModel();
$movie->delete($id);
return $this->genericResponse("Producto eliminado", NULL, 200);
}Código final de la Rest
A la final, nuestro código, nuestra clase Restful luce de la siguiente manera:
<?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));
}
}
}Claves para definir una Rest API
Cambios clave respecto a un controlador tradicional:
- Sin Sesiones: Una REST API es stateless (sin estado). No usamos sesiones para mensajes de éxito ni para mantener datos.
- Sin Redirecciones: En lugar de redirect(), devolvemos una respuesta con el código de estado HTTP adecuado.
- Códigos de Estado: Si la validación falla, devolvemos un código 400 (Bad Request) junto con los errores.
Códigos de Estado HTTP
Un aspecto vital de las APIs es el uso de códigos de estado. Estos permiten que la aplicación cliente (ej. Flutter o Vue) sepa exactamente qué ocurrió sin necesidad de leer el mensaje de texto:
- 200 OK: Petición exitosa.
- 201 Created: Recurso creado con éxito.
- 400 Bad Request: Datos inválidos.
- 401 Unauthorized: El usuario no está autenticado.
- 404 Not Found: El recurso no existe.
- 500 Internal Server Error: Error en el servidor.
Postman
Postman es una aplicación pensada para probar y experimentar las API que estamos creando mediante una sencilla interfaz.
Postman está disponible para Windows, Mac y Linux, así que, puedes usarlo en los sistemas operativos más populares del mercado.
Postman es un software que en pocas palabras nos permite probar todos los tipos de peticiones fácilmente con una sencilla interfaz que, mediante formularios, podemos componer la petición que queremos enviar a nuestra aplicación; para poder enviar una petición a CodeIgniter desde Postman, tenemos que componer la petición de la siguiente manera:
- Configurar el tipo: POST, PUT, PATCH, DELETE o GET
- Si vas a enviar datos por formulario, configurarlo en Body -> x-www-form-urlencoded y establecer los datos.
Recuerda que puedes tener una lista de las URIs de nuestras funciones Rest con el comando de:
$ php spark routesPara las rutas de tipo rest, veremos:
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 Finalmente enviamos una petición invalida:

Fíjate en la sección de headers que aparece el código de estado que establecimos; en nuestro caso:
Status: 400 Bad RequestY si es válida:

Veremos la respuesta de que todo está ok.
Paginado
Podemos crear una función que devuelva el listado paginado, como hemos trabajado tanto en el dashboard como en el módulo de usuario:
app\Controllers\Api\Pelicula.php
public function paginado()
{
return $this->respond($this->model->paginate(10));
}Y su ruta:
app\Config\Routes.php
$routes->group('api', ['namespace' => 'App\Controllers\Api'], function ($routes) {
$routes->get('pelicula/paginado', 'Pelicula::paginado');
***
});Búsqueda con filtros
Para los filtros por categorías, término de búsqueda y etiqueta, usaremos el del capítulo anterior:
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);
}Y su ruta:
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');
***
});Listado de películas por categoría
Un controlador adicional para obtener un listado de películas dado la categoría:
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);
}Y su ruta:
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');
***
});El siguiente paso que tenemos hacer es consumir la app en CodeIgniter 4 desde una aplicación, para ello, debemos de conocer como usar los CORS en CodeIgniter 4.