Como fazer um CRUD básico inicial Codeigniter 4 - react - aplicação web com operações CRUD (Create, Read, Update, Delete) integrando o backend em CodeIgniter 4 (um framework PHP) com o frontend em React (uma biblioteca JavaScript).

Resumo dos passos principais:

  1. Configuração do Backend (CodeIgniter 4):
    • Instale o #CodeIgniter 4 via Composer.
    • Configure o ambiente, incluindo #banco de dados (ex.: MySQL).
    • Crie um modelo e #controlador para gerenciar uma entidade (ex.: "Usuários").
    • Desenvolva rotas e métodos para as operações CRUD:
      • Create: Inserir dados via POST.
      • Read: Listar ou buscar dados via GET.
      • Update: Atualizar dados via PUT.
      • Delete: Excluir dados via DELETE.
    • Implemente uma API RESTful para comunicação com o frontend, retornando dados em JSON.
  2. Configuração do Frontend (React):
    • Crie um projeto React usando create-react-app ou outra ferramenta.
    • Instale bibliotecas como axios para requisições HTTP.
    • Crie componentes para:
      • Listar registros (ex.: tabela de usuários).
      • Formulários para criar e editar registros.
      • Botões para deletar registros.
    • Configure chamadas à API do CodeIgniter para cada operação CRUD.
  3. Integração Frontend e Backend:
    • Conecte o React ao CodeIgniter via requisições HTTP (usando URLs da API).
    • Gerencie estados no React (ex.: com useState e useEffect) para exibir e atualizar dados dinamicamente.
    • Trate erros e valide dados no frontend e backend.
  4. Testes e Finalização:
    • Teste as operações CRUD (inserir, listar, editar e excluir) no frontend.
    • Verifique a comunicação com o backend e a persistência no banco de dados.
    • Ajuste detalhes como interface e validações.

O tutorial foca em criar uma aplicação funcional e simples, ideal para iniciantes que querem aprender a integrar um backend PHP com um frontend React,


Crie seu backend com o comando: composer create-project codeigniter4/appstarter backend

Comandos spark úteis: 

php spark make:migration Products
php spark migrate
php spark make:model ProductModel
php spark make:migration Products
// CodeIgniter v4.6.3 Command Line Tool - Server Time: 2025-08-09 22:28:36 UTC+00:00
//File created: APPPATH/Database/Migrations/2025-08-09-222836_Products.php

php spark routes
/*
CodeIgniter v4.6.3 Command Line Tool - Server Time: 2025-08-10 01:02:30 UTC+00:00

+--------+--------------------+------+--------------------------------------+----------------+---------------+
| Method | Route | Name | Handler | Before Filters | After Filters |
+--------+--------------------+------+--------------------------------------+----------------+---------------+
| GET | / | » | \App\Controllers\Home::index | cors | |
| GET | products | » | \App\Controllers\Products::index | cors | cors |
| GET | products/new | » | \App\Controllers\Products::new | cors | cors |
| GET | products/(.*)/edit | » | \App\Controllers\Products::edit/$1 | cors | cors |
| GET | products/(.*) | » | \App\Controllers\Products::show/$1 | cors | cors |
| POST | products | » | \App\Controllers\Products::create | cors | cors |
| PATCH | products/(.*) | » | \App\Controllers\Products::update/$1 | cors | cors |
| PUT | products/(.*) | » | \App\Controllers\Products::update/$1 | cors | cors |
| DELETE | products/(.*) | » | \App\Controllers\Products::delete/$1 | cors | cors |
+--------+--------------------+------+--------------------------------------+----------------+---------------+
*/
php spark make:migration CreateCategoriesTable
php spark make:controller Categories --restful

Vamos trabalhar com frontend e backend sendo react e codeigniter 4.

 

Controller Products (Migration/Controller)

<?php

namespace App\Database\Migrations;

use CodeIgniter\Database\Migration;

class Products extends Migration
{
 public function up()
 {
 $this->forge->addField([
 'id' => [
 'type' => 'INT',
 'constraint' => 11,
 'unsigned' => true,
 'auto_increment' => true,
 ],
 'title' => [
 'type' => 'VARCHAR',
 'constraint' => 255,
 'unique' => true,
 ],
 'price' => [
 'type' => 'DECIMAL',
 'constraint' => '10,2',
 ] 
 ]);
 $this->forge->addKey('id', true);
 $this->forge->createTable('products', true);
 }

 public function down()
 {
 $this->forge->dropTable('products');
 }
}


//-------------------------------------------------------------------------------------------------

<?php
namespace App\Controllers;
use CodeIgniter\RESTful\ResourceController;
use CodeIgniter\API\ResponseTrait;
use App\Models\ProductModel;
class Products extends ResourceController
{
 use ResponseTrait;

 public function index()
{
 $model = new ProductModel();
 
 
 // Buscar produtos com paginação
 $products = $model->orderBy('id', 'DESC')
 ->paginate();
 
 // Preparar resposta com dados e informações de paginação
 $data = [
 'products' => $products,
 'pager' =>[
 'currentPage' => $model->pager->getCurrentPage(),
 'total_pages'=>$model->pager->getPageCount(),
 'perPage' => $model->pager->getPerPage(),
 
 ]
 ];
 
 return $this->respond($data);
}

 public function show($id = null)
 {
 $model = new ProductModel();
 $data = $model->find(['id' => $id]);
 if (!$data) return $this->failNotFound('No Data Found');
 return $this->respond($data[0]);
 }

 public function create() {
 helper(['form']);
 $rules = [
 'name' => 'required',
 'price' => 'required',
 'category' => 'required',
 'description' => 'required'
 ];
 $data = [
 'name' => $this->request->getVar('name'),
 'price' => $this->request->getVar('price'),
 'category' => $this->request->getVar('category'),
 'description' => $this->request->getVar('description')
 ];
 
 if(!$this->validate($rules)) return $this->fail($this->validator->getErrors());
 $model = new ProductModel();
 $model->save($data);
 $response = [
 'status' => 201,
 'error' => null,
 'messages' => [
 'success' => 'Data Inserted'
 ]
 ];
 return $this->respondCreated($response);
 }

 public function update($id=null) {
 
 helper(['form']);
 $rules = [
 'name' => 'required',
 'price' => 'required',
 'category' => 'required' ];
 $data = [
 'name' => $this->request->getVar('name'),
 'price' => $this->request->getVar('price'),
 'category' => $this->request->getVar('category'),
 ];
 
 if(!$this->validate($rules)) return $this->fail($this->validator->getErrors());
 $model = new ProductModel();
 $find = $model->find(['id' => $id]);
 if(!$find) return $this->failNotFound('No Data Found');
 $model->update($id, $data);
 
 $response = [
 'status' => 200,
 'error' => null,
 'messages' => [
 'success' => 'Data updated'
 ]
 ];
 return $this->respond($response);
 }
 public function delete($id=null) {
 $model = new ProductModel();
 $find = $model->find(['id' => $id]);
 if(!$find) return $this->failNotFound('No Data Found');
 $model->delete($id);
 
 $response = [
 'status' => 200,
 'error' => null,
 'messages' => [
 'success' => 'Data deleted'
 ]
 ];
 return $this->respond($response);
 }
}

 

Categories (Migration/Controller)

<?php

namespace App\Database\Migrations;

use CodeIgniter\Database\Migration;

class CreateCategoriesTable extends Migration
{
 public function up()
 {
 $this->forge->addField([
 'id' => [
 'type' => 'INT',
 'constraint' => 11,
 'unsigned' => true,
 'auto_increment' => true,
 ],
 'name' => [
 'type' => 'VARCHAR',
 'constraint' => '100',
 'null' => false,
 ],
 'description' => [
 'type' => 'TEXT',
 'null' => true,
 'default' => '',
 ],
 ]);
 $this->forge->addKey('id', true);
 $this->forge->createTable('categories', true);
 }

 public function down()
 {
 $this->forge->dropTable('categories');
 }
}


// Controller ------------------------------------------------------------------------------------
<?php

namespace App\Controllers;

use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\RESTful\ResourceController;
use App\Models\CategoryModel;
class Categories extends ResourceController
{
 /**
 * Return an array of resource objects, themselves in array format.
 *
 * @return ResponseInterface
 */
 public function index()
 {
 $model = new CategoryModel();
 $data = $model->findAll();
 return $this->respond([
 'status' => 200,
 'error' => null,
 'messages' => [
 'success' => 'Data Found'
 ],
 'categories' => $data
 ]);
 }

 /**
 * Return the properties of a resource object.
 *
 * @param int|string|null $id
 *
 * @return ResponseInterface
 */
 public function show($id = null)
 {
 $model = new CategoryModel();
 $data = $model->find(['id' => $id]);
 if (!$data) return $this->failNotFound('No Data Found');
 return $this->respond([
 'status' => 200,
 'error' => null,
 'messages' => [
 'success' => 'Data Found'
 ],
 'category' => $data[0]
 ]);
 }

 /**
 * Return a new resource object, with default properties.
 *
 * @return ResponseInterface
 */
 public function new()
 {
 helper(['form']);
 $rules = [
 'name' => 'required',
 'description' => 'required'
 ];
 $data = [
 'name' => $this->request->getVar('name'),
 'description' => $this->request->getVar('description')
 ];
 
 if(!$this->validate($rules)) return $this->fail($this->validator->getErrors());
 $model = new CategoryModel();
 $model->save($data);
 $response = [
 'status' => 201,
 'error' => null,
 'messages' => [
 'success' => 'Data Inserted'
 ]
 ];
 return $this->respondCreated($response);
 }

 /**
 * Create a new resource object, from "posted" parameters.
 *
 * @return ResponseInterface
 */
 public function create()
 {
 return $this->new();
 }

 /**
 * Return the editable properties of a resource object.
 *
 * @param int|string|null $id
 *
 * @return ResponseInterface
 */
 public function edit($id = null)
 {
 $model = new CategoryModel();
 $data = $model->find(['id' => $id]);
 if (!$data) return $this->failNotFound('No Data Found');
 return $this->respond([
 'status' => 200,
 'error' => null,
 'messages' => [
 'success' => 'Data Found'
 ],
 'category' => $data[0]
 ]);
 }

 /**
 * Add or update a model resource, from "posted" properties.
 *
 * @param int|string|null $id
 *
 * @return ResponseInterface
 */
 public function update($id = null)
 {
 helper(['form']);
 $rules = [
 'name' => 'required',
 'description' => 'required'
 ];
 $data = [
 'name' => $this->request->getVar('name'),
 'description' => $this->request->getVar('description')
 ];
 
 if(!$this->validate($rules)) return $this->fail($this->validator->getErrors());
 $model = new CategoryModel();
 $find = $model->find(['id' => $id]);
 if(!$find) return $this->failNotFound('No Data Found');
 $model->update($id, $data);
 $response = [
 'status' => 200,
 'error' => null,
 'messages' => [
 'success' => 'Data updated'
 ]
 ];
 return $this->respond($response);
 }

 /**
 * Delete the designated resource object from the model.
 *
 * @param int|string|null $id
 *
 * @return ResponseInterface
 */
 public function delete($id = null)
 {
 $model = new CategoryModel();
 $find = $model->find(['id' => $id]);
 if(!$find) return $this->failNotFound('No Data Found');
 $model->delete($id);
 
 $response = [
 'status' => 200,
 'error' => null,
 'messages' => [
 'success' => 'Data deleted'
 ]
 ];
 return $this->respond($response);
 }
}

Routes.

<?php

use CodeIgniter\Router\RouteCollection;

/**
 * @var RouteCollection $routes
 */
$routes->get('/', 'Home::index');

$routes->resource('products', ['filter' => 'cors']);
$routes->resource('categories', ['filter' => 'cors']);

Frontend - Git -> https://github.com/GilbertoMG/codeigniter4-react

import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { apiService } from '../../services/api';
import CategoriesList from '../Categories/CategoriesSelect';

const ProductAdd = () => {
 const navigate = useNavigate();
 const [formData, setFormData] = useState({
 name: '',
 price: '',
 category: '',
 description: ''
 });
 const [loading, setLoading] = useState(false);

 const handleChange = (e) => {
 setFormData({
 ...formData,
 [e.target.name]: e.target.value
 });
 };

 const handleSubmit = async (e) => {
 e.preventDefault();
 setLoading(true);

 try {
 await apiService.post('products', formData);
 alert('Produto criado com sucesso!');
 navigate('/products');
 } catch (error) {
 alert('Erro ao criar produto');
 console.error('Erro:', error);
 } finally {
 setLoading(false);
 }
 };

 return (
 <div className="product-add">
 <div className="page-header">
 <h1>Adicionar Produto</h1>
 <button 
 onClick={() => navigate('/products')} 
 className="btn btn-secondary"
 >
 Voltar
 </button>
 </div>

 <form onSubmit={handleSubmit} className="form">
 <div className="form-group">
 <label>Nome:</label>
 <input
 type="text"
 name="name"
 value={formData.name}
 onChange={handleChange}
 required
 className="form-control"
 />
 </div>

 <div className="form-group">
 <label>Preço:</label>
 <input
 type="number"
 step="0.01"
 name="price"
 value={formData.price}
 onChange={handleChange}
 required
 className="form-control"
 />
 </div>

 

 <div className="mb-3">
 <label>Categoria:</label>
 <CategoriesList 
 onSelectCategory={(categoryId) => handleChange({ target: { name: 'category', value: categoryId } })} 
 />
 </div>


 <div className="form-group">
 <label>Descrição:</label>
 <textarea
 name="description"
 value={formData.description}
 onChange={handleChange}
 className="form-control"
 rows="4"
 />
 </div>

 <button 
 type="submit" 
 disabled={loading}
 className="btn btn-primary"
 >
 {loading ? 'Salvando...' : 'Salvar Produto'}
 </button>
 </form>
 </div>
 );
};

export default ProductAdd;