Guia Crud PHP MySQL y React

Vamos a crear un CRUD (Create, Read, Update, Detele) usando REACT como frontend, PHP como backend y MySQL como motor de base de datos.

El frontend que vamos a hacer en REACT puede ser utilizado con cualquier backend, en otro articulo vamos a hacer un backend con python y fastapi por ejemplo, pero para este ejemplo use PHP por que ya saben que soy PHPLover.

Recuerden que el frontend es lo que ve el cliente o el lado del cliente y el backend es del lado del servidor 😉 .

Base de datos

Primero vamos con la base de datos, para este ejemplo vamos a usar solo una tabla “contacto” en la base de datos “contactos”.

create database contactos;
use contactos;

CREATE TABLE `contacto` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `lastname` varchar(255) DEFAULT NULL,
  `phone` varchar(20) DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
);

Vamos a hacer un CRUD para esa tabla y este crud nos va a servir para muchos proyectos mas.

Backend

El backend que creamos funciona para cualquier frontend que tenga las especificaciones de las rutas y respuestas de la API.

El backend en PHP vamos a tener 5 opciones.

  • all: Devuelve un jSON con los datos de todos los contactos
  • get: Devuelve un json con los datos de un contacto
  • new: Crear un contacto – Recibe un json con los datos, devuelve success si todo salio bien
  • update: Actualizar un contacto – Recibe un json con los datos, devuelve success si todo salio bien
  • delete: Eliminar un contacto – Recibe el id del elemento a eliminar, devuelve success si todo salio bien

Este archivo lo vamos a nombrar como contactos.php aqui tenemos las opciones y la conexion a la base de datos usando mysqli.

<?php
/**
 * Backend para la aplicacion directorio de contactos en React o cualquier front end
 */

/* Cabecera para permitir solicitudes CORS */
header('Access-Control-Allow-Origin: *');
header("Access-Control-Allow-Headers: X-API-KEY, Origin, X-Requested-With, Content-Type, Accept, Access-Control-Request-Method");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETE");
header("Allow: GET, POST, OPTIONS, PUT, DELETE");

$conecction = new mysqli("localhost", "root", "", "contactos");
if(isset($_GET["opt"]) && $_GET["opt"]=="all"){
// opcion "all" para obtener todos los contactos
    $sql = "SELECT * FROM contacto";
    $result = $conecction->query($sql);
    $data = [];
    while($row = $result->fetch_assoc()){
        $data[] = $row;
    }
    echo json_encode($data);
} 
else if(isset($_GET["opt"]) && $_GET["opt"]=="get"){
// opcion "get" para obtener un contacto por id
    $id = $_GET["id"];
    $sql = "SELECT * FROM contacto WHERE id = $id";
    $result = $conecction->query($sql);
    if($result->num_rows > 0){
        echo json_encode($result->fetch_assoc());
    } else {
        echo json_encode(["status" => "error", "message" => "Contacto no encontrado"]);
    }
}
elseif(isset($_GET["opt"]) && $_GET["opt"]=="new"){
    // opcion "new" para crear un nuevo contacto
    $datos = json_decode(file_get_contents('php://input'), true);
    $name = $datos["name"];
    $lastname = $datos["lastname"];
    $phone = $datos["phone"];
    if(!empty($name) && !empty($lastname) && !empty($phone)){
    $sql = "INSERT INTO contacto (name, lastname, phone, created_at) VALUES ('$name', '$lastname', '$phone', NOW())";
    if($conecction->query($sql) === TRUE){
        echo json_encode(["status" => "success"]);
    } else {
        echo json_encode(["status" => "error", "message" => $conecction->error]);
    }
    } else {
        echo json_encode(["status" => "error", "message" => "Faltan datos"]);
    }


}
else if(isset($_GET["opt"]) && $_GET["opt"]=="update"){
    // opcion "update" para actualizar un contacto existente
    $datos = json_decode(file_get_contents('php://input'), true);
    $id = $datos["contact_id"];
    $name = $datos["name"];
    $lastname = $datos["lastname"];
    $phone = $datos["phone"];

    if(!empty($id) && !empty($name) && !empty($lastname) && !empty($phone)){
    $sql = "UPDATE contacto SET name='$name', lastname='$lastname', phone='$phone' WHERE id=$id";
    if($conecction->query($sql) === TRUE){
        echo json_encode(["status" => "success"]);
    } else {
        echo json_encode(["status" => "error", "message" => $conecction->error]);
    }
    } else {
        echo json_encode(["status" => "error", "message" => "Faltan datos"]);
    }
}
else if(isset($_GET["opt"]) && $_GET["opt"]=="delete"){
    // opcion "delete" para eliminar un contacto por id
    $id = $_GET["id"];
     $sql = "DELETE FROM contacto WHERE id = $id";

    if($conecction->query($sql) === TRUE){
        echo json_encode(["status" => "success"]);
    } else {
        echo json_encode(["status" => "error", "message" => $conecction->error]);
    }
}

?>

Este backend lo vamos a poner en nuestro servidor XAMPP en la carpeta “directoriotel” para que al acceder a esta API usemos la URL: http://localhost/directoriotel/contactos.php

Esta API es provisional solo para pruebas, pero se puede mejorar claro y agregar mas seguridad, en otro post veremos como hacerlo.

Descargar

A continuacion el link de descarga: https://drive.google.com/file/d/1cGEU84VgBort2D_10LdWYQfWG4Vhf7CW/view

Ahora veremos el lado del cliente o frontend.

Modulo Inicial App.jsx

La pagina App.jsx con tiene el boton para crear nuevo contacto, ademas la lista de contactos se muestra, en la lista de contactos se muestra el contacto y sus opciones de editar y eliminar.

Asi que todo parte de aqui.

Tenemos la funcion get_contacts() que hace una peticion a nuestro backend http://localhost/directoriotel/contactos.php?opt=all para recibir un json con los datos y mostrarlos en una tabla.

Tenemos la funcion eliminar_contacto(id) que hace referencia al servicio delete de nuestro backend http://localhost/directoriotel/contactos.php?opt=delete&id=${id}

import { useState, useEffect } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
import { Link, Route, Router, Routes } from 'react-router-dom'
import NewContact from './NewContact.jsx'
import EditContact from './EditContact.jsx'

async function get_contacts(){

  try {
    const response = await fetch('http://localhost/directoriotel/contactos.php?opt=all')
    const data = await response.json()
    console.log(data)
    return data;
  }
  catch (error) {
    console.error(error)
    return [];
  }

}

async function eliminar_contacto(id){
  alert("eliminar contacto con id: " + id)
  try {
    const response = await fetch(`http://localhost/directoriotel/contactos.php?opt=delete&id=${id}`)
    const data = await response.json()
    console.log(data)
    return data;
  }
  catch (error) {
    console.error(error)
    return [];
  }

}

function Inicio(){
  const [contactos, setContactos] = useState([])

  useEffect(() => {
    get_contacts().then((data) => {
      setContactos(data)
    })
  }, [])

  return <>
  <h1>Lista de contactos</h1>
  <table border={1}>
    <thead>
      <tr>
      <th>Nombre</th>
      <th>Telefono</th>
      <th>Acciones</th>
      </tr>
    </thead>
    <tbody>
  {contactos.map((contacto) => (
     <tr key={contacto.id}>
      <td>{contacto.name+ " " + contacto.lastname}</td>
      <td>{contacto.phone}</td>
      <td>
        <button><Link to={`/edit/${contacto.id}`}>Editar</Link></button>
        <button onClick={() => eliminar_contacto(contacto.id).then(() => {
          get_contacts().then((data) => setContactos(data))
        })}>Eliminar</button>
      </td>
    </tr>
  ))}
  </tbody>
  </table>
  </>

}

function App() {
  const [count, setCount] = useState(0)

  return (
    <>
    <nav>
      <Link to="/">Home</Link>
      <Link to="/new">Nuevo contacto</Link>
    </nav>
      <Routes>
        <Route path="/" element={<Inicio />} />
        <Route path="/new" element={<NewContact />} />
        <Route path="/edit/:contactid" element={<EditContact />} />
      </Routes>
      
    </>
  )
}

export default App

Modulo de nuevo contacto NewContact.jsx

El modulo de Nuevo contacto es un formulario, que envia los datos al servidor o backend.

Cuando hacemos submit al formulario hacemos la llamada a la URL http://localhost/directoriotel/contactos.php?opt=new para guardar los datos.

async function process_form(e){

    e.preventDefault()
    const form = e.target
    const data = new FormData(form)
    const datos = Object.fromEntries(data.entries());
    try{
    const result = await fetch("http://localhost/directoriotel/contactos.php?opt=new", {
        method: "POST",
        headers: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify(datos)
    });

    if(result.ok){
        alert("Contacto creado correctamente");
        //navigate("/")
    }
} catch (error) {
    console.error(error);
}
}

function NewContact(){
    return <>
    <h1>Nuevo contacto</h1>
    <form onSubmit={(e) => { process_form(e) }}>
        <label>Nombre</label>
        <input type="text" name="name" />
        <label>Apellido</label>
        <input type="text" name="lastname" />
        <label>Telefono</label>
        <input type="text" name="phone" />
        <button type="submit">Guardar</button>
    </form>
    </>
}

export default NewContact

Modulo de editar contacto EditContact.jsx

El modulo editar contacto si se me complico un poquito por que por un lado al cargar la pagina se ejecuta la funcion get_one_contact(id) que carga los datos del contacto seleccionado y coloca los valores en el formulario de editar.

Y por otro lado tambien tenemos la funcion process_form(e) que procesa los datos del formulario y llama al servicio update.

import { useParams } from "react-router-dom";
import { useState, useEffect} from "react";

async function process_form(e){

    e.preventDefault()
    const form = e.target
    const data = new FormData(form)
    const datos = Object.fromEntries(data.entries());
    try{
    const result = await fetch("http://localhost/directoriotel/contactos.php?opt=update", {
        method: "POST",
        headers: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify(datos)
    });

    if(result.ok){
        alert("Contacto Actualziado correctamente");
        //navigate("/")
    }
} catch (error) {
    console.error(error);
}
}

async function get_one_contact(id){

  try {
    const response = await fetch(`http://localhost/directoriotel/contactos.php?opt=get&id=${id}`)
    const data = await response.json()
    console.log(data)
    return data;
  }
  catch (error) {
    console.error(error)
    return [];
  }

}

function EditContact(){
    const [name, setName] = useState("");
    const [lastname, setLastname] = useState("");
    const [phone, setPhone] = useState("");
    const {contactid} = useParams()
    const [conid, setConid] = useState("")

    useEffect(
        ()=>{
            const contact = get_one_contact(contactid).then((result)=>{
                setName(result.name)
                setLastname(result.lastname)
                setPhone(result.phone)
                setConid(result.id)
            });
        },[]
    )

    const handleNameInput = (ev)=>{ setName(ev.target.value)    }
    const handleLastnameInput = (ev)=>{ setLastname(ev.target.value)    }
    const handlePhoneInput = (ev)=>{ setPhone(ev.target.value)    }

    return <>
    <h1>Editar contacto</h1>
    <form onSubmit={(e) => { process_form(e) }}>
        <input type="hidden" name="contact_id" id="contact_id" value={conid} onChange={setConid}/>
        <label>Nombre</label>
        <input type="text" name="name" id="name" value={name} onChange={handleNameInput}/>
        <label>Apellido</label>
        <input type="text" name="lastname" id="lastname" value={lastname} onChange={handleLastnameInput}/>
        <label>Telefono</label>
        <input type="text" name="phone" id="phone" value={phone} onChange={handlePhoneInput}/>
        <button type="submit">Guardar</button>
    </form>
    </>
}

export default EditContact

Resultado

Para probar el ejemplo y que funcione bien, el backend debe estar corriendo en http://localhost/directoriotel/

Y para ejecutar el frontend usamos el comando: npm run dev

Descargar

Puedes descargar el proyecto completo, desde el siguiente link de github.

Link: https://github.com/evilnapsis/crud-react-php-mysql

Leave a Comment

Your email address will not be published. Required fields are marked *