Antes de crear nuestra API, vamos a entender la estructura de archivos que utilizaremos:
Primero, creamos el archivo de conexión a la base de datos:
<?php
$host = 'localhost';
$dbname = 'prueba';
$user = 'root';
$pass = '';
try {
// Crear conexión PDO
$pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8", $user, $pass);
// Configurar PDO para mostrar errores
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
// En caso de error, devolver respuesta de error en JSON
http_response_code(500);
echo json_encode(["error" => "Error de conexión: " . $e->getMessage()]);
exit;
}
?>
A continuación, creamos el controlador que manejará las operaciones CRUD para los usuarios:
<?php
// Incluir archivo de conexión a la BD
require_once 'db.php';
/**
* Obtener todos los usuarios
* Método: GET
* URL: /usuarios
*/
function obtenerUsuarios() {
global $pdo;
try {
$stmt = $pdo->query("SELECT * FROM usuarios");
$usuarios = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($usuarios);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(["error" => "Error al obtener usuarios"]);
}
}
/**
* Obtener un usuario por ID
* Método: GET
* URL: /usuarios/{id}
* @param int $id ID del usuario
*/
function obtenerUsuario($id) {
global $pdo;
try {
$stmt = $pdo->prepare("SELECT * FROM usuarios WHERE id = ?");
$stmt->execute([$id]);
$usuario = $stmt->fetch(PDO::FETCH_ASSOC);
if ($usuario) {
echo json_encode($usuario);
} else {
http_response_code(404);
echo json_encode(["error" => "Usuario no encontrado"]);
}
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(["error" => "Error al obtener usuario"]);
}
}
/**
* Crear un nuevo usuario
* Método: POST
* URL: /usuarios
* @param array $data Datos del usuario a crear
*/
function crearUsuario($data) {
global $pdo;
// Validar datos necesarios
if (!isset($data['nombre']) || !isset($data['correo'])) {
http_response_code(400);
echo json_encode(["error" => "Nombre y correo son obligatorios"]);
return;
}
try {
$stmt = $pdo->prepare("INSERT INTO usuarios (nombre, correo) VALUES (?, ?)");
$stmt->execute([$data['nombre'], $data['correo']]);
http_response_code(201); // 201 Created
echo json_encode(["mensaje" => "Usuario creado"]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(["error" => "Error al crear usuario"]);
}
}
/**
* Actualizar un usuario existente
* Método: PUT
* URL: /usuarios/{id}
* @param int $id ID del usuario
* @param array $data Datos actualizados
*/
function actualizarUsuario($id, $data) {
global $pdo;
// Validar datos necesarios
if (!isset($data['nombre']) || !isset($data['correo'])) {
http_response_code(400);
echo json_encode(["error" => "Nombre y correo son obligatorios"]);
return;
}
try {
$stmt = $pdo->prepare("UPDATE usuarios SET nombre = ?, correo = ? WHERE id = ?");
$stmt->execute([$data['nombre'], $data['correo'], $id]);
if ($stmt->rowCount() > 0) {
echo json_encode(["mensaje" => "Usuario actualizado"]);
} else {
http_response_code(404);
echo json_encode(["error" => "Usuario no encontrado o sin cambios"]);
}
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(["error" => "Error al actualizar usuario"]);
}
}
/**
* Eliminar un usuario
* Método: DELETE
* URL: /usuarios/{id}
* @param int $id ID del usuario
*/
function eliminarUsuario($id) {
global $pdo;
try {
$stmt = $pdo->prepare("DELETE FROM usuarios WHERE id = ?");
$stmt->execute([$id]);
if ($stmt->rowCount() > 0) {
echo json_encode(["mensaje" => "Usuario eliminado"]);
} else {
http_response_code(404);
echo json_encode(["error" => "Usuario no encontrado"]);
}
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(["error" => "Error al eliminar usuario"]);
}
}
?>
Finalmente, creamos el archivo index.php que actuará como enrutador para todas las peticiones:
<?php
// Configurar cabeceras para API REST
header("Content-Type: application/json");
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
// Para peticiones OPTIONS (CORS preflight)
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
exit(0);
}
// Incluir el controlador
require_once 'UsuarioController.php';
// Obtener método HTTP
$method = $_SERVER['REQUEST_METHOD'];
// Analizar la ruta solicitada
$path = explode('/', trim($_SERVER['PATH_INFO'] ?? '', '/'));
$resource = $path[0] ?? null;
$id = $path[1] ?? null;
// Enrutamiento basado en método HTTP y recurso
switch ($method) {
case 'GET':
if ($resource === 'usuarios') {
$id ? obtenerUsuario($id) : obtenerUsuarios();
}
break;
case 'POST':
if ($resource === 'usuarios') {
$data = json_decode(file_get_contents("php://input"), true);
crearUsuario($data);
}
break;
case 'PUT':
if ($resource === 'usuarios' && $id) {
$data = json_decode(file_get_contents("php://input"), true);
actualizarUsuario($id, $data);
}
break;
case 'DELETE':
if ($resource === 'usuarios' && $id) {
eliminarUsuario($id);
}
break;
default:
http_response_code(405); // Method Not Allowed
echo json_encode(["error" => "Método no permitido"]);
}
?>
Para que nuestra API funcione, necesitamos crear la tabla usuarios
en la base de datos:
-- Crear la base de datos si no existe
CREATE DATABASE IF NOT EXISTS prueba;
-- Usar la base de datos
USE prueba;
-- Crear la tabla usuarios
CREATE TABLE IF NOT EXISTS usuarios (
id INT AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(100) NOT NULL,
correo VARCHAR(100) NOT NULL UNIQUE,
fecha_registro TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Insertar algunos usuarios de prueba
INSERT INTO usuarios (nombre, correo) VALUES
('Juan Pérez', 'juan@example.com'),
('María García', 'maria@example.com');
Una vez que hayamos creado todos los archivos y la base de datos, podemos probar nuestra API con herramientas como Postman o utilizando cURL desde la línea de comandos:
# GET - Obtener todos los usuarios
curl http://localhost/api/usuarios
# GET - Obtener un usuario por ID
curl http://localhost/api/usuarios/1
# POST - Crear un nuevo usuario
curl -X POST http://localhost/api/usuarios \
-H "Content-Type: application/json" \
-d '{"nombre":"Ana Rodríguez","correo":"ana@example.com"}'
# PUT - Actualizar un usuario
curl -X PUT http://localhost/api/usuarios/1 \
-H "Content-Type: application/json" \
-d '{"nombre":"Juan Pérez Actualizado","correo":"juan.nuevo@example.com"}'
# DELETE - Eliminar un usuario
curl -X DELETE http://localhost/api/usuarios/2
// Respuesta GET /usuarios
[
{
"id": "1",
"nombre": "Juan Pérez",
"correo": "juan@example.com",
"fecha_registro": "2023-05-15 10:30:45"
},
{
"id": "2",
"nombre": "María García",
"correo": "maria@example.com",
"fecha_registro": "2023-05-15 10:30:45"
}
]
// Respuesta POST /usuarios (201 Created)
{
"mensaje": "Usuario creado"
}
// Respuesta de error (404 Not Found)
{
"error": "Usuario no encontrado"
}
Para una API en producción, es importante implementar medidas de seguridad:
<?php
// Ejemplo simple de autenticación por API Key
function verificarApiKey() {
// En producción, estas claves deberían almacenarse de forma segura
$apiKeys = [
'abcd1234xyz' => 'usuario_cliente',
'efgh5678abc' => 'usuario_admin'
];
// Obtener encabezados
$headers = getallheaders();
// Verificar si existe el encabezado X-API-Key
if (!isset($headers['X-API-Key'])) {
http_response_code(401); // Unauthorized
echo json_encode(["error" => "Se requiere API Key"]);
exit;
}
$apiKey = $headers['X-API-Key'];
// Verificar si la API Key es válida
if (!array_key_exists($apiKey, $apiKeys)) {
http_response_code(403); // Forbidden
echo json_encode(["error" => "API Key inválida"]);
exit;
}
// Aquí podríamos verificar permisos específicos según el usuario
$usuario = $apiKeys[$apiKey];
return $usuario;
}
// Esta función se llamaría al inicio de index.php
$usuario = verificarApiKey();
?>
Crear una API REST con PHP nativo es relativamente sencillo y no requiere frameworks adicionales. En esta lección hemos visto cómo estructurar los archivos, crear un sistema de enrutamiento básico y manejar diferentes tipos de peticiones HTTP. Hemos implementado operaciones CRUD para una entidad Usuario y añadido un ejemplo básico de seguridad. Estos conceptos pueden expandirse para crear APIs más complejas y robustas.