Si alguna vez te has preguntado cómo hace una app de celular para mostrar datos que vienen de un servidor, o cómo un frontend en React o Vue se comunica con tu backend en PHP, la respuesta casi siempre es la misma: una API REST.
En este curso vamos a crear una API REST con PHP desde cero, sin frameworks, cubriendo el CRUD completo (insertar, listar, actualizar y eliminar datos) conectado a MySQL, para que entiendas exactamente qué pasa por debajo antes de saltar a herramientas como Laravel que te automatizan buena parte de esto.
Te puede interesar – Curso de Programacion Orientada a Objetos con PHP
Para este curso ya deberías venir con las bases de PHP resueltas (variables, arrays, funciones) y, si le echaste un ojo al curso de POO, mejor todavía, porque vamos a organizar el código en clases hacia el final. También vamos a dar por hecho que ya pasaste por la guía de conexión a base de datos con PHP y MySQL, así que aquí no vamos a explicar de nuevo qué es PDO ni cómo se arma la conexión: vamos directo a usarla para que el CRUD de esta API guarde datos de verdad en una tabla, en vez de en un archivo de texto.
Lo que vamos a ver
- Lección 0: Qué es una API REST y por qué importa
- Lección 1: Métodos HTTP y códigos de estado
- Lección 2: JSON en PHP
- Lección 3: Tu primer endpoint
- Lección 4: Recibiendo datos del cliente
- Lección 5: Diseñando rutas REST
- Lección 6: Enrutamiento sin framework
- Lección 7: CRUD — Listar y obtener datos (GET)
- Lección 8: CRUD — Insertar datos (POST)
- Lección 9: CRUD — Actualizar datos (PUT y PATCH)
- Lección 10: CRUD — Eliminar datos (DELETE)
- Lección 11: Validación y respuestas de error consistentes
- Lección 12: Códigos de estado correctos para cada situación
- Lección 13: CORS, consumiendo tu API desde otro dominio
- Lección 14: Autenticación básica con API Keys
- Lección 15: Consumiendo APIs externas con cURL
- Lección 16: Probando tu API con Postman
- Lección 17: Versionado y buenas prácticas
Lección 0: Qué es una API REST y por qué importa
Una API es, en pocas palabras, una forma en la que dos programas se comunican entre sí. REST es un estilo particular de diseñar esa comunicación, pensado alrededor de “recursos” (piensa en usuarios, productos, tareas) que se manipulan usando los métodos que ya trae el protocolo HTTP: obtener, crear, actualizar, eliminar.
Lo que hace especial a REST es que es sin estado (stateless): cada petición que llega al servidor debe traer toda la información necesaria para procesarse, sin que el servidor tenga que “recordar” nada de peticiones anteriores. Esto es distinto a lo que quizás ya conoces con $_SESSION, donde el servidor sí guarda información entre una petición y otra.
Aprender esto te sirve para algo muy concreto: prácticamente cualquier aplicación moderna necesita una API en algún punto, ya sea una app móvil que consulta datos, un frontend separado del backend, dos sistemas de la misma empresa que necesitan hablarse entre sí, o simplemente porque quieres que otros desarrolladores se integren con tu sistema sin tocar tu base de datos directamente.
Un apunte rápido: existen otros estilos para construir APIs (SOAP, GraphQL, gRPC), pero REST sigue siendo, por lejos, el más usado en el mundo PHP, sobre todo por lo simple que resulta empezar con él.
Antes de seguir, piensa en una app que uses seguido (redes sociales, banco, streaming) e imagina qué “recursos” podría tener su API por debajo: usuarios, publicaciones, videos, transacciones. No hace falta que lo escribas, solo que empieces a pensar en esos términos.
Lección 1: Métodos HTTP y códigos de estado
REST se apoya directamente en el protocolo HTTP, así que antes de escribir una sola línea de código necesitas tener claros dos conceptos: los métodos y los códigos de estado.
Los métodos HTTP le dicen al servidor qué acción quieres ejecutar:
GET: obtener información, sin modificar nada.POST: insertar datos nuevos.PUT: reemplazar por completo los datos existentes.PATCH: modificar parcialmente los datos existentes.DELETE: eliminar datos.
Los códigos de estado le dicen al cliente qué pasó con su petición. No necesitas memorizarlos todos, pero sí los que vas a usar todo el tiempo:
| Código | Significado | Cuándo usarlo |
|---|---|---|
| 200 | OK | La petición se procesó bien (GET, PUT, PATCH exitosos) |
| 201 | Created | Se insertaron datos nuevos (POST exitoso) |
| 204 | No Content | La acción funcionó pero no hay nada que devolver (DELETE exitoso) |
| 400 | Bad Request | El cliente mandó datos mal formados o incompletos |
| 401 | Unauthorized | Falta autenticación o es inválida |
| 404 | Not Found | Los datos solicitados no existen |
| 500 | Internal Server Error | Algo falló del lado del servidor |
Una API que siempre responde 200 sin importar qué haya pasado, incluyendo errores, es una de las señales más claras de que no está bien diseñada. El código de estado es información valiosa para quien consume tu API, así que úsalo con propiedad desde el principio.
Como práctica mental, piensa qué código le corresponde a estas situaciones: alguien intenta obtener una tarea con un ID que no existe, alguien crea una tarea nueva exitosamente, y alguien manda un formulario sin el campo obligatorio “título”.
Lección 2: JSON en PHP
Casi todas las APIs REST modernas hablan en JSON, un formato de texto ligero para representar datos estructurados. La buena noticia es que PHP trae soporte nativo para convertir entre JSON y arrays de PHP, sin necesidad de ninguna librería externa.
<?php
$tarea = [
"id" => 1,
"titulo" => "Aprender REST",
"completada" => false
];
$json = json_encode($tarea);
echo $json; // {"id":1,"titulo":"Aprender REST","completada":false}
Para hacer el camino contrario, de un texto JSON a un array de PHP con el que puedas trabajar normalmente, usas json_decode. El segundo parámetro en true es importante: le dice a PHP que te devuelva un array asociativo en vez de un objeto stdClass, que es el comportamiento por defecto.
<?php
$json = '{"id":1,"titulo":"Aprender REST","completada":false}';
$tarea = json_decode($json, true);
echo $tarea["titulo"]; // Aprender REST
Si vas a imprimir JSON directamente en el navegador para revisarlo mientras desarrollas, la bandera JSON_PRETTY_PRINT te lo devuelve indentado y mucho más legible:
<?php echo json_encode($tarea, JSON_PRETTY_PRINT);
Un detalle que casi nadie revisa al principio y después causa dolores de cabeza: si tu JSON tiene tildes o eñes, json_encode por defecto las convierte en secuencias de escape tipo \u00f1. Sigue siendo JSON válido, pero si prefieres verlo en texto plano, agrega la bandera JSON_UNESCAPED_UNICODE.
Lección 3: Tu primer endpoint
Un endpoint no es más que un archivo PHP normal, solo que en vez de devolver HTML, devuelve JSON, y le avisa al cliente de qué tipo de contenido se trata mediante un header.
<?php
// api/hola.php
header("Content-Type: application/json");
echo json_encode([
"mensaje" => "Hola, esta es tu primera API en PHP"
]);
Si levantas tu servidor local y visitas localhost/api/hola.php, deberías ver ese JSON en pantalla. El header Content-Type: application/json es más importante de lo que parece: sin él, muchos clientes (navegadores, librerías de JavaScript) podrían interpretar tu respuesta como texto plano en vez de tratarla como JSON de verdad, aunque el contenido se vea igual a simple vista.
Este header siempre debe ir antes de cualquier echo o salida, igual que pasaba con session_start() en el curso anterior. PHP envía los headers antes que el cuerpo de la respuesta, así que si ya imprimiste algo, es demasiado tarde para agregarlos.
Crea un archivo api/estado.php que devuelva un JSON con tu nombre, la fecha actual y un mensaje indicando que la API está funcionando.
Lección 4: Recibiendo datos del cliente
Ya sabes devolver JSON, ahora toca recibirlo. Aquí es donde una API REST se diferencia bastante de los formularios tradicionales que viste en el curso de PHP desde cero.
Para datos que van en la URL (típicamente en un GET), sigues usando $_GET como siempre:
<?php // api/buscar.php?q=reunion $busqueda = $_GET["q"] ?? ""; echo json_encode(["buscando" => $busqueda]);
Pero cuando un cliente te manda un POST, PUT o PATCH con un cuerpo en formato JSON (que es lo normal en una API REST, a diferencia de un formulario HTML tradicional), ese contenido no llega en $_POST. Llega en el cuerpo crudo de la petición, y lo lees con php://input:
<?php
$datosCrudos = file_get_contents("php://input");
$datos = json_decode($datosCrudos, true);
echo json_encode(["recibido" => $datos]);
Este es probablemente el error más común de quien empieza con APIs en PHP: seguir intentando leer $_POST["titulo"] cuando en realidad el cliente está mandando un JSON en el cuerpo, y $_POST en ese caso simplemente va a estar vacío. Si tu API no recibe nada donde debería, este suele ser el primer lugar a revisar.
No está de más validar que el JSON recibido en verdad se haya podido decodificar, porque si el cliente manda un JSON mal formado, json_decode te va a devolver null sin avisarte de forma escandalosa.
<?php
$datos = json_decode(file_get_contents("php://input"), true);
if ($datos === null) {
http_response_code(400);
echo json_encode(["error" => "JSON inválido"]);
exit;
}
Lección 5: Diseñando rutas REST
Antes de escribir el enrutamiento, tómate un momento para pensar cómo se ven las URLs de una API bien diseñada, porque es algo que después es incómodo de cambiar sin romper a quien ya la esté consumiendo.
La convención más aceptada es usar sustantivos en plural para representar colecciones de datos, y dejar que el método HTTP (no la URL) indique la acción:
GET /api/tareas -> listar todas las tareas GET /api/tareas/5 -> obtener la tarea con id 5 POST /api/tareas -> crear una tarea nueva PUT /api/tareas/5 -> reemplazar la tarea 5 PATCH /api/tareas/5 -> actualizar parcialmente la tarea 5 DELETE /api/tareas/5 -> eliminar la tarea 5
Nota que nunca aparece un verbo en la URL, como /api/crearTarea o /api/eliminarTarea/5. Ese estilo era común hace años, pero rompe justo el principio que hace útil a REST: dejar que el método HTTP cargue con el significado de la acción, y que la URL se limite a identificar qué datos se están manipulando.
Cuando unos datos dependen de otros (por ejemplo, los comentarios de una tarea específica), esa relación también se refleja en la URL de forma anidada:
GET /api/tareas/5/comentarios -> comentarios de la tarea 5 POST /api/tareas/5/comentarios -> crear un comentario en la tarea 5
Diseña, aunque sea en papel, las rutas que necesitarías para una API de una biblioteca con libros y préstamos, sin escribir código todavía.
Lección 6: Enrutamiento sin framework
Los frameworks como Laravel te resuelven el enrutamiento de forma automática, pero entender cómo funciona por debajo te va a ayudar muchísimo cuando algún día tengas que depurar un problema raro en uno de ellos. Vamos a construir un enrutador simple usando el patrón front controller: todas las peticiones pasan por un único archivo index.php, que decide qué código ejecutar según la URL y el método.
Primero necesitas redirigir todo el tráfico a ese archivo. Si usas Apache, esto se logra con un .htaccess:
# api/.htaccess
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]
Y en index.php, lees la URL y el método para decidir qué hacer:
<?php
// api/index.php
header("Content-Type: application/json");
$metodo = $_SERVER["REQUEST_METHOD"];
$uri = trim(parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH), "/");
$partes = explode("/", $uri); // ejemplo: ["api", "tareas", "5"]
$recurso = $partes[1] ?? null;
$id = $partes[2] ?? null;
if ($recurso === "tareas") {
if ($metodo === "GET" && $id === null) {
echo json_encode(["accion" => "listar todas las tareas"]);
} elseif ($metodo === "GET" && $id !== null) {
echo json_encode(["accion" => "obtener tarea", "id" => $id]);
} elseif ($metodo === "POST") {
echo json_encode(["accion" => "insertar tarea"]);
} else {
http_response_code(404);
echo json_encode(["error" => "Ruta no encontrada"]);
}
}
Es un enrutador bastante rústico, con if encadenados, pero cumple su función y te deja ver exactamente qué está pasando en cada petición, sin la magia que a veces esconde un framework. Conforme tu API crezca vas a querer algo más ordenado, pero para aprender el concepto, esto es más que suficiente.
Lección 7: CRUD — Listar y obtener datos (GET)
A partir de aquí vamos a construir el CRUD completo de una API de tareas, guardando todo en una tabla tareas de MySQL. Damos por hecho que ya sabes armar la tabla y la conexión con PDO de tu guía anterior, así que aquí vamos directo al grano.
CREATE TABLE tareas (
id INT AUTO_INCREMENT PRIMARY KEY,
titulo VARCHAR(150) NOT NULL,
completada BOOLEAN DEFAULT FALSE
);
Un conexion.php como el que ya conoces, que vamos a incluir en cada endpoint:
<?php
// conexion.php
$pdo = new PDO("mysql:host=localhost;dbname=api_tareas;charset=utf8mb4", "root", "");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
Con eso, listar todas las tareas es una consulta directa:
<?php
require "conexion.php";
if ($metodo === "GET" && $id === null) {
$consulta = $pdo->query("SELECT * FROM tareas");
$tareas = $consulta->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($tareas);
}
Y para obtener una sola tarea, usamos una sentencia preparada, igual que en tu guía de MySQL, para no exponer la API a inyección SQL:
<?php
if ($metodo === "GET" && $id !== null) {
$stmt = $pdo->prepare("SELECT * FROM tareas WHERE id = ?");
$stmt->execute([$id]);
$tarea = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$tarea) {
http_response_code(404);
echo json_encode(["error" => "Tarea no encontrada"]);
} else {
echo json_encode($tarea);
}
}
Prueba esto directamente desde el navegador visitando /api/tareas y /api/tareas/1, y también con un id que no exista, como /api/tareas/999, para confirmar que te devuelve el 404.
Lección 8: CRUD — Insertar datos (POST)
Para insertar una tarea nueva, leemos el JSON del cuerpo, validamos, e insertamos con una sentencia preparada. MySQL se encarga del id automáticamente gracias al AUTO_INCREMENT, así que ni siquiera tenemos que calcularlo a mano.
<?php
if ($metodo === "POST" && $recurso === "tareas") {
$datos = json_decode(file_get_contents("php://input"), true);
if (empty($datos["titulo"])) {
http_response_code(400);
echo json_encode(["error" => "El campo 'titulo' es obligatorio"]);
exit;
}
$stmt = $pdo->prepare("INSERT INTO tareas (titulo, completada) VALUES (?, ?)");
$stmt->execute([$datos["titulo"], false]);
$nuevaTarea = [
"id" => $pdo->lastInsertId(),
"titulo" => $datos["titulo"],
"completada" => false
];
http_response_code(201);
echo json_encode($nuevaTarea);
}
lastInsertId() te devuelve el id que MySQL le acaba de asignar a la fila recién creada, justo lo que necesitas para devolverle al cliente los datos completos, incluyendo su identificador. Respondemos con 201, no 200, porque justo eso es lo que indica que se insertaron datos nuevos.
Prueba insertar una tarea nueva usando algún cliente HTTP (en la lección de Postman vamos a verlo con calma), mandando un JSON como {"titulo": "Repasar CRUD"}.
Lección 9: CRUD — Actualizar datos (PUT y PATCH)
Aquí hay una diferencia que suele confundir a cualquiera: PUT reemplaza los datos completos (el cliente debe mandar todos los campos, incluso los que no cambian), mientras que PATCH modifica solo los campos que decide enviar.
<?php
if (($metodo === "PUT" || $metodo === "PATCH") && $id !== null) {
$datos = json_decode(file_get_contents("php://input"), true);
$stmt = $pdo->prepare("SELECT * FROM tareas WHERE id = ?");
$stmt->execute([$id]);
$tarea = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$tarea) {
http_response_code(404);
echo json_encode(["error" => "Tarea no encontrada"]);
exit;
}
if ($metodo === "PUT") {
$titulo = $datos["titulo"] ?? $tarea["titulo"];
$completada = $datos["completada"] ?? false;
} else {
// PATCH: solo pisamos lo que venga en la petición, el resto se conserva
$titulo = $datos["titulo"] ?? $tarea["titulo"];
$completada = $datos["completada"] ?? $tarea["completada"];
}
$stmt = $pdo->prepare("UPDATE tareas SET titulo = ?, completada = ? WHERE id = ?");
$stmt->execute([$titulo, $completada, $id]);
echo json_encode(["mensaje" => "Tarea actualizada"]);
}
En la práctica, muchísimas APIs (incluyendo bastantes conocidas) usan PUT para todo, incluso actualizaciones parciales, y no pasa nada grave si haces lo mismo en tus propios proyectos. Pero ahora que conoces la diferencia real, vas a entender por qué algunas APIs sí distinguen entre ambos, y vas a poder decidir con criterio propio en cada proyecto.
Lección 10: CRUD — Eliminar datos (DELETE)
El último paso del CRUD es el más corto de los cuatro:
<?php
if ($metodo === "DELETE" && $id !== null) {
$stmt = $pdo->prepare("DELETE FROM tareas WHERE id = ?");
$stmt->execute([$id]);
if ($stmt->rowCount() === 0) {
http_response_code(404);
echo json_encode(["error" => "Tarea no encontrada"]);
exit;
}
http_response_code(204);
}
rowCount() te dice cuántas filas afectó la consulta. Si es cero, significa que ese id no existía, y ahí es donde respondemos 404 en vez de asumir que todo salió bien.
También notarás que con 204 no mandamos ningún echo. Es intencional: ese código de estado significa literalmente “no hay contenido”, así que la convención es no mandar cuerpo en la respuesta.
Con las cuatro lecciones anteriores ya tienes un CRUD REST completo y funcional. Tómate un momento para probar las cuatro operaciones en orden: crea una tarea, consúltala, actualízala, y finalmente elimínala.
Lección 11: Validación y respuestas de error consistentes
Hasta ahora fuimos validando cosas sueltas (que el título no esté vacío, que el JSON sea válido), pero conforme tu API crece, ayuda mucho que todos los errores tengan la misma forma. Un cliente que consume tu API debería poder confiar en que, sin importar qué endpoint falle, el error siempre se ve igual.
Una estructura simple y bastante estándar es esta:
<?php
function responderError($codigo, $mensaje) {
http_response_code($codigo);
echo json_encode(["error" => $mensaje]);
exit;
}
// uso:
if (empty($datos["titulo"])) {
responderError(400, "El campo 'titulo' es obligatorio");
}
Centralizar esto en una sola función evita que termines con quince formas ligeramente distintas de reportar errores a lo largo de tu API, algo que pasa más seguido de lo que te imaginas cuando el proyecto crece y varias personas (o tú mismo, meses después) van agregando endpoints.
Si tu API va a validar varios campos en cada endpoint, también puede convenirte devolver todos los errores encontrados de una sola vez, en vez de uno por uno en peticiones separadas:
<?php
$errores = [];
if (empty($datos["titulo"])) {
$errores[] = "El campo 'titulo' es obligatorio";
}
if (isset($datos["completada"]) && !is_bool($datos["completada"])) {
$errores[] = "El campo 'completada' debe ser verdadero o falso";
}
if (!empty($errores)) {
http_response_code(400);
echo json_encode(["errores" => $errores]);
exit;
}
Lección 12: Códigos de estado correctos para cada situación
Ya usamos varios códigos de estado a lo largo del curso, pero conviene hacer un repaso consciente ahora que ya tienes el CRUD completo, porque es un tema en el que muchas APIs reales fallan por descuido.
Un mapeo que te va a servir como referencia casi cualquier proyecto:
GETexitoso →200POSTque crea algo →201PUT/PATCHexitoso →200DELETEexitoso →204- Datos no encontrados →
404 - Datos inválidos o incompletos →
400 - Falta autenticación →
401 - Autenticado pero sin permiso →
403 - Error inesperado del servidor →
500
Un error común es devolver siempre 200 y meter el detalle del error solo dentro del cuerpo del JSON, algo como {"exito": false, "mensaje": "no encontrado"}. Funciona, pero le quita a tu API una herramienta que HTTP ya te da gratis: cualquier cliente, librería o herramienta de monitoreo puede reaccionar automáticamente según el código de estado, sin tener que abrir y parsear el cuerpo de cada respuesta para saber si algo salió mal.
Lección 13: CORS, consumiendo tu API desde otro dominio
Si alguna vez intentas consumir tu API desde un frontend que vive en un dominio distinto (por ejemplo, tu API en api.midominio.com y tu frontend en app.midominio.com, o simplemente probando desde un archivo local mientras desarrollas), es muy probable que te topes con un error de CORS en la consola del navegador.
CORS (Cross-Origin Resource Sharing) es un mecanismo de seguridad del navegador, no de PHP, que bloquea por defecto las peticiones entre dominios distintos a menos que el servidor indique explícitamente que las permite. Se hace con un header:
<?php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
El asterisco (*) permite cualquier origen, cómodo mientras desarrollas, pero en un proyecto real casi siempre es mejor restringirlo a los dominios específicos que sí deberían poder consumir tu API.
Hay un detalle adicional con PUT, PATCH y DELETE: el navegador, antes de mandar la petición real, suele mandar una petición OPTIONS de verificación (lo que se llama “preflight”). Tu API necesita responder algo válido a esa petición, aunque sea vacío, o el navegador va a bloquear la petición real antes de que le llegue a tu código:
<?php
if ($_SERVER["REQUEST_METHOD"] === "OPTIONS") {
http_response_code(204);
exit;
}
Lección 14: Autenticación básica con API Keys
No toda API necesita autenticación (una que expone datos públicos puede prescindir de ella), pero la mayoría sí. La forma más simple de proteger una API, antes de meterte en algo como JWT o OAuth (temas para otro curso aparte), es usando una API Key: una cadena secreta que el cliente debe mandar en cada petición.
Una convención común es esperarla en un header personalizado:
<?php
$claveRecibida = $_SERVER["HTTP_X_API_KEY"] ?? "";
$claveValida = "mi-clave-secreta-123";
if ($claveRecibida !== $claveValida) {
http_response_code(401);
echo json_encode(["error" => "API Key inválida o faltante"]);
exit;
}
El cliente, entonces, tendría que mandar ese header en cada petición: X-Api-Key: mi-clave-secreta-123.
Esto es suficiente para proyectos pequeños o para proteger una API de uso interno entre tus propios sistemas, pero tiene límites serios: la clave es la misma para todo el mundo que la tenga, no identifica a un usuario en particular, y si se filtra, tienes que cambiarla y avisarle a todos los que la usan. Para una API con usuarios reales que inician sesión, lo normal es dar el salto a autenticación con tokens (JWT), que es justamente el siguiente curso natural después de este.
Lección 15: Consumiendo APIs externas con cURL
Hasta ahora construiste una API. Ahora toca el lado contrario: consumir una API que no es tuya, algo que vas a necesitar constantemente (integrar un servicio de pagos, traer el clima, conectar con redes sociales). Para eso, PHP trae la extensión cURL.
<?php
$ch = curl_init("https://jsonplaceholder.typicode.com/todos/1");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$respuesta = curl_exec($ch);
curl_close($ch);
$datos = json_decode($respuesta, true);
echo $datos["title"];
CURLOPT_RETURNTRANSFER es una opción que casi siempre vas a querer en true: sin ella, cURL imprime la respuesta directamente en pantalla en vez de devolvértela como texto para que tú decidas qué hacer con ella.
Para mandar un POST con JSON, algo muy común al integrar servicios de terceros, la configuración crece un poco:
<?php
$datos = json_encode(["titulo" => "Nueva tarea desde otra API"]);
$ch = curl_init("https://api.ejemplo.com/tareas");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $datos);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Content-Type: application/json",
"X-Api-Key: la-clave-de-esa-api"
]);
$respuesta = curl_exec($ch);
curl_close($ch);
Este es apenas un vistazo de lo que cURL puede hacer; da para un curso completo por sí solo (manejo de errores de red, timeouts, autenticación OAuth, subir archivos), pero con esto ya puedes empezar a integrar servicios externos sin problema.
Prueba consumir la API pública https://jsonplaceholder.typicode.com/todos (no requiere autenticación) y muestra en pantalla los títulos de las primeras 5 tareas que te devuelve.
Lección 16: Probando tu API con Postman
Escribir peticiones a mano con cURL desde la terminal funciona, pero se vuelve tedioso rápido. Postman (o su alternativa gratuita Insomnia) es una herramienta gráfica pensada justo para probar APIs sin tener que escribir código para cada petición.
El flujo básico es: eliges el método HTTP (GET, POST, etc.), escribes la URL de tu endpoint, y si necesitas mandar un cuerpo, lo escribes en la pestaña “Body” seleccionando el formato raw y JSON. Los headers personalizados, como tu X-Api-Key, se agregan en la pestaña “Headers”.
Una práctica muy recomendable conforme tu API crece: guarda tus peticiones organizadas en una “colección” dentro de Postman, con una petición de ejemplo para cada endpoint. Así, cada vez que hagas un cambio, puedes volver a probar todo tu CRUD en un par de clics, en vez de reescribir las peticiones de memoria cada vez.
Si prefieres quedarte en la terminal, el mismo resultado se logra con curl como comando (no confundir con la extensión de PHP que usamos en la lección anterior, aunque comparten nombre por la misma razón):
curl -X POST http://localhost/api/tareas \
-H "Content-Type: application/json" \
-d '{"titulo": "Probada desde la terminal"}'
Prueba tu CRUD completo (las cuatro operaciones) usando Postman o el comando curl desde la terminal, y confirma que cada respuesta trae el código de estado que esperarías según lo que viste en la Lección 12.
Lección 17: Versionado y buenas prácticas
Para cerrar, algunas prácticas que separan una API hecha para aprender de una lista para producción real.
Versiona tu API desde el principio. Aunque tu primera versión te parezca perfecta, en algún momento vas a necesitar cambiar algo de forma que rompa a quien ya la esté consumiendo. Prefijar tus rutas con /api/v1/tareas te deja lanzar después un /api/v2/tareas sin tronar a nadie que siga usando la versión anterior.
Sé consistente en el formato de tus respuestas. Si algunos endpoints devuelven el array de resultados directo y otros lo envuelven en {"data": [...]}, quien consuma tu API va a tener que recordar cuál es cuál. Elige un formato y respétalo en toda la API.
Pagina las listas grandes. Devolver diez mil tareas de un jalón en un solo GET /api/tareas no escala. Aunque sea de forma simple, con parámetros como ?pagina=2&limite=20, tu API te lo va a agradecer en cuanto tenga datos reales.
Documenta cada endpoint, aunque sea de forma sencilla: qué método usa, qué parámetros espera, qué te devuelve y con qué código de estado. No hace falta una herramienta sofisticada como Swagger desde el día uno, pero sí que exista algo escrito en algún lado, porque en unos meses ni tú mismo vas a recordar los detalles de memoria.
Para cerrar
Con estas 18 lecciones ya sabes construir una API REST completa en PHP puro: diseñar rutas, manejar los cuatro verbos del CRUD, validar entradas, responder con los códigos de estado correctos, lidiar con CORS, proteger tu API con una clave básica, y también consumir APIs de terceros con cURL.