CRUD básico inicial Codeigniter 4 - react

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;