Crear una API REST con PHP: JSON, PDO y autenticación
Una API REST con PHP puede recibir solicitudes HTTP, ejecutar lógica de negocio, consultar MySQL mediante PDO y devolver respuestas JSON a aplicaciones web, móviles u otros sistemas. Para construirla correctamente necesitas comprender rutas, métodos HTTP, códigos de estado, validación, autenticación, autorización, errores, CORS y separación de responsabilidades.
¿Cómo crear una API REST con PHP?
Primero debes definir recursos y endpoints. Por ejemplo: /api/productos y /api/productos/15.
Después debes interpretar el método HTTP utilizado: GET para consultar, POST para crear, PATCH o PUT para modificar y DELETE para eliminar, según el diseño de tu API.
PHP procesa la solicitud, valida los datos, ejecuta la lógica necesaria, consulta MySQL mediante PDO y devuelve una respuesta normalmente codificada como JSON.
Una API lista para producción también necesita autenticación, autorización, manejo de errores, códigos HTTP, CORS cuando corresponda, límites de acceso y seguridad.
Una API conecta clientes, lógica de aplicación y datos
Cliente
Web, app móvil u otro sistema envía una solicitud.
Ruta
La API identifica qué endpoint se está solicitando.
Método HTTP
GET, POST, PATCH, PUT o DELETE.
Lógica
Se validan datos y reglas.
Datos
PDO puede consultar MySQL.
Respuesta
HTTP status + JSON.
Una API permite que diferentes aplicaciones se comuniquen mediante un contrato
Imagina que tienes un sistema de productos.
La información está almacenada en MySQL, pero quieres mostrarla dentro de:
Una aplicación web.
Una aplicación móvil.
Un dashboard interno.
Un sistema externo.
Esos clientes no necesitan conectarse directamente a la base de datos.
Pueden comunicarse con una API PHP.
La API se convierte en una frontera
Decide qué información puede consultarse, qué operaciones están permitidas y bajo qué condiciones.
La capa backend debe controlar autenticación, permisos, reglas y acceso a los datos.
REST organiza la API alrededor de recursos y operaciones HTTP
Supongamos que la aplicación administra productos.
En lugar de crear URLs como:
/obtener-productos.php
/crear-producto.php
/borrar-producto.php
podemos representar el recurso mediante:
/api/productos
/api/productos/15
Y utilizar el método HTTP para expresar la operación.
GET, POST, PUT, PATCH y DELETE expresan diferentes intenciones
| Método | Uso habitual | Ejemplo |
|---|---|---|
| GET | Consultar recursos | GET /productos |
| POST | Crear un recurso | POST /productos |
| PUT | Sustituir o actualizar un recurso según el contrato de la API | PUT /productos/15 |
| PATCH | Modificar parcialmente un recurso | PATCH /productos/15 |
| DELETE | Eliminar un recurso | DELETE /productos/15 |
Especialmente PUT y PATCH pueden implementarse de manera diferente según el diseño del servicio.
PHP puede identificar el método de la solicitud
<?php
$metodo = $_SERVER['REQUEST_METHOD'];
switch ($metodo) {
case 'GET':
// Consultar.
break;
case 'POST':
// Crear.
break;
case 'PATCH':
// Modificar.
break;
case 'DELETE':
// Eliminar.
break;
default:
http_response_code(405);
}
En una aplicación mayor, esta lógica normalmente termina dentro de un router o framework.
Pero comprenderla manualmente primero ayuda a entender qué está automatizando la herramienta.
Una API PHP puede devolver JSON en lugar de una página HTML
<?php
header(
'Content-Type: application/json; charset=utf-8'
);
$respuesta = [
'ok' => true,
'producto' => [
'id' => 15,
'nombre' => 'Teclado',
'precio' => 19990
]
];
echo json_encode(
$respuesta,
JSON_UNESCAPED_UNICODE
);
El cliente recibe datos estructurados y decide cómo presentarlos.
Backend y frontend quedan desacoplados
El mismo endpoint puede ser utilizado por una interfaz web, una app móvil o un sistema externo.
Una solicitud POST o PATCH puede enviar información en formato JSON
El cuerpo de una solicitud podría contener:
{
"nombre": "Monitor",
"precio": 129990
}
PHP puede leer el body mediante php://input.
<?php
$contenido = file_get_contents(
'php://input'
);
$datos = json_decode(
$contenido,
true
);
if (!is_array($datos)) {
http_response_code(400);
echo json_encode([
'error' => 'JSON inválido'
]);
exit;
}
Decodificar JSON no significa que los datos sean válidos
Todavía necesitas comprobar campos, tipos, reglas y permisos.
Crear un endpoint para listar productos
Supongamos que ya tenemos una conexión PDO.
<?php
$sql = "
SELECT
id,
nombre,
precio
FROM productos
ORDER BY id DESC
LIMIT 20
";
$stmt = $pdo->query($sql);
$productos = $stmt->fetchAll(
PDO::FETCH_ASSOC
);
http_response_code(200);
echo json_encode([
'data' => $productos
]);
Hemos separado la representación del recurso del HTML.
La API entrega los datos y el cliente decide cómo utilizarlos.
Consultar un recurso por identificador
<?php
$sql = "
SELECT
id,
nombre,
precio
FROM productos
WHERE id = :id
LIMIT 1
";
$stmt = $pdo->prepare($sql);
$stmt->execute([
'id' => $id
]);
$producto = $stmt->fetch(
PDO::FETCH_ASSOC
);
if (!$producto) {
http_response_code(404);
echo json_encode([
'error' => 'Producto no encontrado'
]);
exit;
}
echo json_encode([
'data' => $producto
]);
Aquí 404 comunica información importante
El cliente no recibe solamente un texto: también recibe un código HTTP que describe el resultado.
Crear un recurso requiere validar antes de insertar
Supongamos que recibimos:
{
"nombre": "Teclado mecánico",
"precio": 39990
}
Primero comprobamos las reglas.
<?php
$nombre = trim(
(string) ($datos['nombre'] ?? '')
);
$precio = $datos['precio'] ?? null;
$errores = [];
if ($nombre === '') {
$errores['nombre'] = 'El nombre es obligatorio.';
}
if (
!is_numeric($precio)
|| (float) $precio < 0
) {
$errores['precio'] = 'El precio es inválido.';
}
if ($errores !== []) {
http_response_code(422);
echo json_encode([
'error' => 'Datos inválidos',
'campos' => $errores
]);
exit;
}
Después insertamos mediante PDO
<?php
$sql = "
INSERT INTO productos (
nombre,
precio
)
VALUES (
:nombre,
:precio
)
";
$stmt = $pdo->prepare($sql);
$stmt->execute([
'nombre' => $nombre,
'precio' => (float) $precio
]);
$id = (int) $pdo->lastInsertId();
http_response_code(201);
echo json_encode([
'data' => [
'id' => $id,
'nombre' => $nombre,
'precio' => (float) $precio
]
]);
201 comunica que se creó un recurso
Una API bien diseñada utiliza también HTTP para comunicar resultados, no solamente una propiedad ok.
Si las consultas preparadas todavía no están claras, profundiza primero en PHP con MySQL
CRUD, relaciones, transacciones y seguridad SQL.
PATCH puede utilizarse para modificar solamente algunos campos
Supongamos que queremos cambiar únicamente el precio.
{
"precio": 34990
}
La aplicación puede permitir modificaciones parciales.
<?php
$precio = $datos['precio'] ?? null;
if (
!is_numeric($precio)
|| (float) $precio < 0
) {
http_response_code(422);
echo json_encode([
'error' => 'Precio inválido'
]);
exit;
}
$sql = "
UPDATE productos
SET precio = :precio
WHERE id = :id
";
$stmt = $pdo->prepare($sql);
$stmt->execute([
'precio' => (float) $precio,
'id' => $id
]);
echo json_encode([
'data' => [
'id' => $id,
'precio' => (float) $precio
]
]);
Una API real debe decidir qué campos pueden modificarse
No utilices automáticamente cualquier clave enviada por el cliente para construir SQL dinámico.
Eliminar un recurso también necesita autorización
<?php
$sql = "
DELETE FROM productos
WHERE id = :id
";
$stmt = $pdo->prepare($sql);
$stmt->execute([
'id' => $id
]);
if ($stmt->rowCount() === 0) {
http_response_code(404);
echo json_encode([
'error' => 'Producto no encontrado'
]);
exit;
}
http_response_code(204);
204 No Content puede utilizarse cuando la operación fue correcta y no necesitas devolver un body.
Debes comprobar quién realiza la solicitud y si posee autorización para ejecutar esa operación.
Los códigos de estado forman parte del contrato de una API
| Código | Significado habitual | Ejemplo |
|---|---|---|
| 200 | Operación correcta | GET exitoso |
| 201 | Recurso creado | POST exitoso |
| 204 | Éxito sin contenido | DELETE exitoso |
| 400 | Solicitud incorrecta | JSON inválido |
| 401 | Falta autenticación válida | Token ausente o inválido |
| 403 | Acceso no autorizado | Usuario sin permiso |
| 404 | Recurso no encontrado | Producto inexistente |
| 405 | Método no permitido | POST donde solo se admite GET |
| 422 | Datos procesables pero inválidos | Campos que no cumplen reglas |
| 500 | Error interno | Fallo inesperado del servidor |
Una API debe diferenciar entre datos técnicamente válidos y datos válidos para el negocio
Tipo
¿El precio realmente es numérico?
Rango
¿Puede ser negativo?
Longitud
¿Un nombre puede contener miles de caracteres?
Unicidad
¿Puede repetirse un email o código?
Regla de negocio
¿Puede cancelarse un pedido que ya fue despachado?
UNIQUE, NOT NULL, claves foráneas y otras restricciones complementan la validación de PHP.
Evita repetir la construcción de respuestas JSON en cada endpoint
Podemos crear una función sencilla:
<?php
function responderJson(
mixed $datos,
int $status = 200
): never {
http_response_code($status);
header(
'Content-Type: application/json; charset=utf-8'
);
echo json_encode(
$datos,
JSON_UNESCAPED_UNICODE
);
exit;
}
Después:
responderJson(
[
'data' => $productos
],
200
);
En proyectos mayores, este concepto puede evolucionar hacia objetos de respuesta o abstracciones proporcionadas por frameworks.
Separar controlador, servicio y acceso a datos evita una API formada por archivos gigantes
Un endpoint no debería convertirse en una mezcla de:
Routing.
Validación.
SQL.
Reglas de negocio.
Autenticación.
JSON.
Podemos empezar separando responsabilidades.
Request
↓
Router
↓
Controller
↓
Service
↓
Repository
↓
PDO / MySQL
Controller
Recibe información relacionada con HTTP y construye la respuesta.
Service
Ejecuta reglas del negocio.
Repository
Encapsula operaciones relacionadas con persistencia.
Las clases empiezan a tener sentido cuando necesitas separar responsabilidades
Interfaces, composición e inyección de dependencias.
El controlador no necesita contener directamente todas las consultas SQL
<?php
final class ProductoRepository
{
public function __construct(
private PDO $pdo
) {}
public function buscar(
int $id
): ?array {
$sql = "
SELECT
id,
nombre,
precio
FROM productos
WHERE id = :id
LIMIT 1
";
$stmt = $this->pdo->prepare($sql);
$stmt->execute([
'id' => $id
]);
$producto = $stmt->fetch(
PDO::FETCH_ASSOC
);
return $producto ?: null;
}
}
El resto del sistema puede pedir un producto sin conocer los detalles SQL de esa operación.
Composer permite organizar las clases de la API mediante autoloading
Podemos utilizar PSR-4 para relacionar namespaces con el directorio src/.
{
"autoload": {
"psr-4": {
"Ciborg\\Api\\": "src/"
}
}
}
Y organizar:
api/
│
├── composer.json
├── composer.lock
├── public/
│ └── index.php
│
├── src/
│ ├── Controllers/
│ ├── Services/
│ ├── Repositories/
│ ├── Http/
│ └── Database/
│
└── vendor/
Composer ayuda a convertir la API en un proyecto PHP organizado
Autoload, namespaces, paquetes y dependencias.
Una API privada necesita saber quién realiza la solicitud
Algunos endpoints pueden ser públicos.
Otros necesitan una identidad autenticada.
Authorization header
Un cliente puede enviar una credencial dentro del header:
Authorization: Bearer <token>
El servidor debe validar ese token según el sistema de autenticación elegido.
Utiliza mecanismos conocidos, bibliotecas mantenidas y prácticas adecuadas para el tipo de autenticación que implementes.
Estar autenticado no significa poder hacer cualquier cosa
Supongamos que un usuario puede consultar sus pedidos.
No debería poder cambiar el identificador de la URL y consultar los pedidos de otra persona.
Debes comprobar propiedad o permisos
La lógica debería responder preguntas como:
¿Quién realiza la solicitud?
¿Qué recurso quiere utilizar?
¿Tiene permiso para verlo?
¿Puede modificarlo?
¿Puede eliminarlo?
CORS controla determinados accesos realizados desde navegadores entre orígenes diferentes
Si tu frontend está en un dominio y tu API en otro, el navegador aplica políticas de origen.
El servidor puede responder con headers CORS adecuados cuando quiere permitir determinadas solicitudes.
<?php
header(
'Access-Control-Allow-Origin: https://app.ejemplo.cl'
);
header(
'Access-Control-Allow-Methods: GET, POST, PATCH, DELETE, OPTIONS'
);
header(
'Access-Control-Allow-Headers: Content-Type, Authorization'
);
Permitir un origen no significa que ese cliente tenga autorización para acceder a datos privados.
Una API debe devolver errores consistentes sin revelar detalles internos
El cliente necesita comprender qué ocurrió.
Pero no necesita recibir:
Credenciales de base de datos.
Rutas internas del servidor.
Stack traces completos.
SQL sensible.
Respuesta pública
{
"error": "internal_error",
"message": "No fue posible completar la operación."
}
El detalle técnico puede registrarse internamente para debugging.
Una colección grande no debería devolverse completa en cada solicitud
En lugar de:
GET /productos
devolviendo cien mil registros, puedes admitir parámetros:
GET /productos?page=2&per_page=20
La respuesta también puede incluir metadata:
{
"data": [],
"meta": {
"page": 2,
"per_page": 20,
"total": 358
}
}
Query parameters pueden permitir búsquedas y filtros
GET /productos?categoria=tecnologia
GET /productos?activo=1
GET /productos?buscar=teclado
Define explícitamente qué filtros acepta la API y cómo se traducen a consultas seguras.
Una API pública puede necesitar evolucionar sin romper clientes existentes
Si otras aplicaciones dependen de tu respuesta, cambiar su estructura puede romperlas.
Una estrategia posible es incorporar versión en la ruta:
/api/v1/productos
/api/v2/productos
El versionado no debería añadirse simplemente por decoración.
Es útil cuando necesitas administrar cambios incompatibles dentro del contrato.
Una API pública debería considerar límites de uso
Un cliente puede realizar miles de solicitudes por accidente o de manera abusiva.
Rate limiting
Permite restringir cuántas solicitudes puede realizar una identidad o cliente dentro de un período.
Límites por plan
En un SaaS, también puedes asociar consumo con planes o créditos.
Este tipo de control pertenece a la arquitectura del servicio, no solamente al routing.
Prueba la API como cliente, no solamente ejecutando funciones PHP
Una API debe comprobarse a nivel HTTP.
Caso exitoso
¿POST crea realmente un recurso y devuelve 201?
Datos inválidos
¿Devuelve un error coherente?
Usuario sin permisos
¿La API bloquea correctamente la operación?
Recurso inexistente
¿Devuelve 404?
Base de datos caída
¿Evita filtrar detalles sensibles?
En qué orden aprender a crear APIs REST con PHP
PHP
Funciones, arrays, tipos y errores.
HTTP
Request, response, headers y status.
JSON
Serialización y parsing.
REST
Recursos y métodos HTTP.
PDO
Persistencia y consultas seguras.
Validación
Protege reglas y datos.
POO
Separa controller, service y repository.
Autenticación
Identifica clientes y usuarios.
Autorización
Controla operaciones permitidas.
Producción
CORS, logs, rate limits y testing.
Si trabajas con WordPress, ya existe una REST API que puedes extender
No siempre necesitas construir una API PHP desde cero.
WordPress proporciona su propia REST API y permite registrar endpoints personalizados.
Los mismos conceptos siguen siendo importantes: HTTP, JSON, autenticación, permisos, validación y códigos de estado.
PHP también permite convertir WordPress en backend para otros sistemas
Plugins, endpoints e integraciones.
Una API PHP puede convertirse en la capa central de múltiples aplicaciones
Frontend web
JavaScript consume JSON mediante HTTP.
App móvil
Android o iOS utiliza la misma API.
Ecommerce
Productos, pedidos, pagos e inventario.
SaaS
Usuarios, planes, consumo y suscripciones.
IA
Intermediario entre clientes y modelos externos.
Integraciones
CRM, ERP, pagos y terceros.
Una API es uno de los mecanismos fundamentales para conectar software
Continúa con arquitectura, webhooks e integraciones.
Qué evitar al crear una API REST con PHP
Crear una API REST con PHP exige comprender HTTP tanto como PHP
Define primero recursos y endpoints.
Utiliza métodos HTTP con una semántica coherente.
Devuelve JSON junto con códigos HTTP adecuados.
Valida todos los datos externos.
Utiliza PDO y consultas preparadas para acceder a MySQL.
Separa routing, lógica de negocio y persistencia cuando el proyecto crezca.
Implementa autenticación y autorización como problemas diferentes.
Añade CORS, logs, paginación, rate limiting y testing según las necesidades reales del servicio.
Una buena API no es solamente un archivo PHP que devuelve JSON: es un contrato HTTP estable, seguro y mantenible entre diferentes aplicaciones.
Continúa profundizando en PHP
Preguntas sobre crear una API REST con PHP
Sí. PHP permite leer solicitudes HTTP, identificar métodos, procesar JSON, consultar bases de datos y devolver respuestas JSON sin utilizar un framework. Hacerlo primero de forma sencilla puede ayudarte a comprender qué automatizan posteriormente los frameworks.
Entre los métodos más habituales se encuentran GET, POST, PUT, PATCH y DELETE. El significado concreto debe quedar definido por el contrato de la API.
Puedes establecer Content-Type como application/json y utilizar json_encode() para convertir arrays u otros datos compatibles en una respuesta JSON.
Puedes utilizar PDO para conectarte con MySQL. Conviene utilizar consultas preparadas, validación, transacciones cuando correspondan y una capa separada para acceso a datos cuando la aplicación crece.
La autenticación determina quién realiza una solicitud. La autorización determina qué recursos y operaciones puede utilizar esa identidad. Una persona puede estar autenticada y aun así no tener permiso para una determinada acción.
CORS es un mecanismo relacionado con solicitudes realizadas desde navegadores entre orígenes diferentes. Una API puede declarar qué orígenes, métodos y headers permite. CORS no reemplaza autenticación ni autorización.
No para un ejemplo pequeño. Sin embargo, cuando una API aumenta de tamaño, POO puede ayudarte a separar controllers, servicios, repositories y otros componentes.
WordPress incluye una REST API y permite registrar endpoints personalizados mediante PHP. Puede utilizarse como backend para aplicaciones externas, siempre que se implementen correctamente permisos, autenticación y validación.
Una API es el punto donde PHP, HTTP, POO y bases de datos empiezan a trabajar juntos
Construye primero endpoints sencillos, añade PDO y validación, después separa responsabilidades, incorpora autenticación, permisos, paginación y testing.
Continuar con APIs e integraciones →