En este tutorial, crearemos una aplicación web completa que utiliza APIs REST, Fetch API, PHP, y MySQL para implementar una tienda en línea. Aprenderemos cómo estructurar el backend con PHP para proporcionar datos a través de una API REST, así como el frontend para consumir y mostrar esos datos utilizando Fetch API.
1. Configuración inicial
Requisitos previos
- Servidor local configurado (por ejemplo, XAMPP).
- Base de datos MySQL.
- Conocimientos básicos de PHP, HTML, CSS, y JavaScript.
Estructura del proyecto
Crea un proyecto con la siguiente estructura:
1 2 3 4 5 |
/api-tienda |-- /images # Carpeta para imágenes de los productos |-- index.html # Archivo principal del frontend |-- fetch-products.php # Archivo que contiene la API REST |-- database.php # Archivo para la conexión a MySQL |
2. Crear la base de datos y tabla
Abre tu gestor de bases de datos como phpMyAdmin y usa la siguiente consulta SQL para crear la base de datos y la tabla:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
CREATE DATABASE tienda; USE tienda; CREATE TABLE productos ( id INT AUTO_INCREMENT PRIMARY KEY, nombre VARCHAR(100) NOT NULL, fabricante VARCHAR(100), precio DECIMAL(10, 2) NOT NULL, imagen VARCHAR(100) NOT NULL ); -- Insertar datos de ejemplo INSERT INTO productos (nombre, fabricante, precio, imagen) VALUES ('Laptop', 'Dell', 750.00, 'laptop.jpg'), ('Smartphone', 'Samsung', 500.00, 'smartphone.jpg'), ('Tablet', 'Apple', 650.00, 'tablet.jpg'), ('Auriculares', 'Sony', 100.00, 'auriculares.jpg'); |
3. Crear la conexión a la base de datos (database.php)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?php $host = 'localhost'; $dbname = 'tienda'; $username = 'root'; // Cambiar si usas otro usuario $password = ''; // Cambiar si usas contraseña try { $pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8", $username, $password); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } catch (PDOException $e) { die("Error de conexión: " . $e->getMessage()); } ?> |
4. Crear la API REST (fetch-products.php)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
<?php require 'database.php'; // Configurar cabeceras header('Access-Control-Allow-Origin: *'); header('Content-Type: application/json'); try { // Parámetros enviados por el frontend $page = isset($_GET['page']) ? (int)$_GET['page'] : 1; $limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 12; $search = isset($_GET['search']) ? trim($_GET['search']) : ''; $priceOrder = isset($_GET['priceOrder']) ? strtolower(trim($_GET['priceOrder'])) : ''; $offset = ($page - 1) * $limit; // Base de la consulta $baseQuery = "FROM productos WHERE 1"; // Agregar búsqueda por nombre si se proporciona if (!empty($search)) { $baseQuery .= " AND nombre LIKE :search"; } // Contar el total de productos $totalQuery = "SELECT COUNT(*) AS total " . $baseQuery; $totalStmt = $pdo->prepare($totalQuery); if (!empty($search)) { $totalStmt->bindValue(':search', "%$search%", PDO::PARAM_STR); } $totalStmt->execute(); $totalProductos = $totalStmt->fetch(PDO::FETCH_ASSOC)['total']; // Obtener productos con búsqueda, filtro y paginación $productsQuery = "SELECT nombre, fabricante, CAST(precio AS DECIMAL(10, 2)) AS precio, imagen " . $baseQuery; // Agregar orden por precio si se especifica if ($priceOrder === 'asc') { $productsQuery .= " ORDER BY precio ASC"; } elseif ($priceOrder === 'desc') { $productsQuery .= " ORDER BY precio DESC"; } else { $productsQuery .= " ORDER BY nombre ASC"; // Orden predeterminado } // Agregar límites para la paginación $productsQuery .= " LIMIT :limit OFFSET :offset"; $stmt = $pdo->prepare($productsQuery); if (!empty($search)) { $stmt->bindValue(':search', "%$search%", PDO::PARAM_STR); } $stmt->bindValue(':limit', $limit, PDO::PARAM_INT); $stmt->bindValue(':offset', $offset, PDO::PARAM_INT); $stmt->execute(); $productos = $stmt->fetchAll(PDO::FETCH_ASSOC); // Preparar respuesta echo json_encode([ 'total' => $totalProductos, 'page' => $page, 'limit' => $limit, 'productos' => $productos, ]); } catch (PDOException $e) { echo json_encode([ 'status' => 'error', 'message' => 'Error al obtener productos: ' . $e->getMessage(), ]); } ?> |
5. Crear el frontend (index.html)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Productos - API REST</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="container mt-5"> <h1 class="text-center mb-4">Productos Disponibles</h1> <!-- Barra de búsqueda y filtros --> <div class="row mb-4"> <div class="col-md-6"> <input type="text" id="searchInput" class="form-control" placeholder="Buscar producto por nombre..." oninput="fetchProducts()"> </div> <div class="col-md-6"> <select id="priceFilter" class="form-select" onchange="fetchProducts()"> <option value="">Ordenar por precio</option> <option value="asc">Menor precio</option> <option value="desc">Mayor precio</option> </select> </div> </div> <!-- Contenedor de productos --> <div id="productGrid" class="row row-cols-1 row-cols-md-4 g-4"></div> <!-- Controles de paginación --> <nav class="mt-4"> <ul class="pagination justify-content-center" id="paginationControls"></ul> </nav> </div> <script> let currentPage = 1; const limit = 12; const merchantEmail = "tu-correo@comerciante.com"; // Reemplaza con tu correo de PayPal async function fetchProducts(page = 1) { try { const search = document.getElementById('searchInput').value.trim(); const priceOrder = document.getElementById('priceFilter').value; const queryParams = new URLSearchParams({ page, limit, search, priceOrder }); const response = await fetch(`fetch-products.php?${queryParams}`); if (!response.ok) throw new Error(`Error: ${response.status}`); const data = await response.json(); const { productos, total } = data; const productGrid = document.getElementById('productGrid'); const paginationControls = document.getElementById('paginationControls'); // Limpiar contenido previo productGrid.innerHTML = ''; paginationControls.innerHTML = ''; // Renderizar productos if (productos.length === 0) { productGrid.innerHTML = ` <div class="col-12"> <p class="text-center">No se encontraron productos.</p> </div> `; } else { productos.forEach(product => { const precio = parseFloat(product.precio); // Convertir a número productGrid.innerHTML += ` <div class="col"> <div class="card h-100"> <img src="images/${product.imagen}" class="card-img-top" alt="${product.nombre}"> <div class="card-body"> <h5 class="card-title">${product.nombre}</h5> <p class="card-text">Fabricante: ${product.fabricante}</p> <p class="card-text">Precio: $${precio.toFixed(2)}</p> <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank"> <input type="hidden" name="cmd" value="_xclick"> <input type="hidden" name="business" value="${merchantEmail}"> <input type="hidden" name="item_name" value="${product.nombre}"> <input type="hidden" name="amount" value="${precio.toFixed(2)}"> <input type="hidden" name="currency_code" value="USD"> <button type="submit" class="btn btn-primary w-100">Comprar</button> </form> </div> </div> </div> `; }); } // Generar controles de paginación const totalPages = Math.ceil(total / limit); for (let i = 1; i <= totalPages; i++) { paginationControls.innerHTML += ` <li class="page-item ${i === page ? 'active' : ''}"> <button class="page-link" onclick="changePage(${i})">${i}</button> </li> `; } } catch (error) { console.error('Error al obtener productos:', error); } } function changePage(page) { currentPage = page; fetchProducts(page); } // Cargar la primera página al iniciar document.addEventListener('DOMContentLoaded', () => fetchProducts(currentPage)); </script> </body> </html> |
Habilidades Adquiridas en este Tutorial
A lo largo de este tutorial, desarrollaste una aplicación completa que incluye tanto el backend como el frontend para una tienda en línea. En el proceso, adquiriste las siguientes habilidades clave:
- Diseño y configuración de una base de datos relacional:
- Creaste una base de datos MySQL para almacenar productos, aprendiendo a definir estructuras básicas de tablas, tipos de datos, y relaciones entre datos.
- Desarrollo de una API REST con PHP:
- Implementaste una API REST que puede manejar múltiples funcionalidades como la paginación, búsqueda y filtrado por precio.
- Aprendiste a utilizar métodos seguros para realizar consultas a la base de datos, utilizando sentencias preparadas con PDO para evitar ataques de inyección SQL.
- Gestión de parámetros dinámicos:
- Integramos parámetros como
page
,limit
,search
, ypriceOrder
para proporcionar flexibilidad al cliente y mejorar la experiencia del usuario.
- Integramos parámetros como
- Paginación dinámica en la API y frontend:
- Diseñaste un sistema de paginación para dividir los resultados en páginas manejables.
- Implementaste controles de paginación en el frontend que permiten navegar entre páginas con botones dinámicos.
- Buscador funcional:
- Agregaste un campo de búsqueda que interactúa con la API para filtrar productos en tiempo real según el nombre.
- Ordenación de datos:
- Creaste un sistema para ordenar los productos por precio de forma ascendente o descendente, proporcionando una experiencia de compra más intuitiva.
- Consumo de APIs REST con Fetch API:
- Utilizaste Fetch API en JavaScript para consumir la API creada, aprendiendo a manejar solicitudes asíncronas y procesar respuestas en formato JSON.
- Diseño responsivo con Bootstrap:
- Organizaste los productos en una cuadrícula que se adapta automáticamente al tamaño de la pantalla, mostrando cuatro productos por fila en dispositivos más grandes.
- Integración de un botón de compra con PayPal:
- Implementaste un botón que permite a los usuarios realizar pagos utilizando PayPal, facilitando la transacción sin necesidad de configurar una cuenta empresarial o utilizar la API oficial.
- Gestión de errores:
- Configuraste manejadores de errores tanto en el backend como en el frontend para mostrar mensajes útiles al usuario en caso de fallos.
- Preparación para la producción:
- Diseñaste el sistema para ser escalable, modular y compatible con futuras integraciones, como la adición de nuevos métodos de pago o la gestión de usuarios.
Con este conocimiento, ahora puedes abordar proyectos más complejos que involucren integración de APIs, desarrollo de frontend interactivo, y manejo de bases de datos robustas. Si tienes preguntas o deseas expandir esta aplicación (por ejemplo, agregar un carrito de compras, un sistema de usuarios o mejorar la seguridad), ¡puedes seguir aprendiendo y mejorando!
Ver demostración Comprar archivos