AJAX con PHP y JavaScript: usar Fetch API paso a paso
Muchas funcionalidades web necesitan comunicarse con el servidor sin recargar completamente la página.
Un buscador que muestra resultados mientras escribimos, un formulario que se envía sin cambiar de pantalla o un selector que carga información según una opción anterior son ejemplos habituales.
Desde JavaScript podemos realizar estas peticiones mediante la Fetch API y procesarlas posteriormente con PHP en el servidor.
¿Qué es AJAX?
AJAX permite que una página intercambie información con un servidor de forma asíncrona, sin realizar una recarga completa en cada operación.
Aunque el término procede de Asynchronous JavaScript and XML, actualmente es habitual intercambiar los datos mediante JSON.
El flujo básico es:
JavaScript → petición HTTP → PHP → respuesta → JavaScript
El navegador inicia la petición, PHP la procesa y devuelve una respuesta que JavaScript puede utilizar para actualizar únicamente la parte necesaria de la interfaz.
Una petición sencilla con Fetch API
Podemos solicitar un recurso con:
fetch('mensaje.php')
.then(response => response.text())
.then(data => {
console.log(data);
})
.catch(error => {
console.error('Error:', error);
});
Y mensaje.php podría contener:
<?php
echo 'Respuesta enviada desde PHP';
También podemos utilizar async y await, que suele facilitar la lectura:
async function cargarMensaje() {
try {
const response = await fetch('mensaje.php');
if (!response.ok) {
throw new Error(`Error HTTP: ${response.status}`);
}
const data = await response.text();
console.log(data);
} catch (error) {
console.error('Error en la petición:', error);
}
}
cargarMensaje();
Es importante comprobar response.ok, porque fetch() no considera automáticamente un código HTTP como 404 o 500 un error de red.
Mostrar la respuesta de PHP en la página
Supongamos que tenemos:
<button id="cargar">Cargar mensaje</button>
<div id="resultado"></div>
Podemos modificar únicamente ese elemento:
const boton = document.querySelector('#cargar');
const resultado = document.querySelector('#resultado');
boton.addEventListener('click', async () => {
try {
const response = await fetch('mensaje.php');
if (!response.ok) {
throw new Error(`Error HTTP: ${response.status}`);
}
resultado.textContent = await response.text();
} catch (error) {
resultado.textContent = 'No se ha podido cargar la información.';
console.error(error);
}
});
PHP responde sin que sea necesario recargar toda la página.
Enviar un formulario a PHP mediante POST
Tenemos este formulario:
<form id="formulario">
<label for="nombre">Nombre</label>
<input type="text" id="nombre" name="nombre" required>
<button type="submit">Enviar</button>
</form>
<div id="resultado"></div>
JavaScript puede interceptar el envío:
const formulario = document.querySelector('#formulario');
const resultado = document.querySelector('#resultado');
formulario.addEventListener('submit', async (event) => {
event.preventDefault();
const datos = new FormData(formulario);
try {
const response = await fetch('procesar.php', {
method: 'POST',
body: datos
});
if (!response.ok) {
throw new Error(`Error HTTP: ${response.status}`);
}
resultado.textContent = await response.text();
} catch (error) {
resultado.textContent = 'Se ha producido un error.';
console.error(error);
}
});
event.preventDefault() evita el envío tradicional del formulario y FormData recoge automáticamente sus campos.
Cuando enviamos un objeto FormData no debemos establecer manualmente Content-Type: el navegador genera el encabezado adecuado.
Recibir los datos desde PHP
procesar.php puede acceder a los campos mediante $_POST:
<?php
$nombre = trim($_POST['nombre'] ?? '');
if ($nombre === '') {
http_response_code(400);
exit('El nombre es obligatorio.');
}
echo 'Hola, ' . htmlspecialchars(
$nombre,
ENT_QUOTES,
'UTF-8'
);
Aunque el formulario utilice required, PHP debe validar igualmente los datos recibidos.
La validación realizada en el navegador mejora la experiencia de usuario, pero no protege el servidor: cualquiera puede enviar una petición directamente al endpoint.
Trabajar con JSON entre JavaScript y PHP
Para APIs o interfaces más estructuradas puede resultar más práctico utilizar JSON.
Por ejemplo:
async function guardarCliente() {
const cliente = {
nombre: 'Empresa A',
email: 'info@empresa-a.com'
};
try {
const response = await fetch('guardar-cliente.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(cliente)
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error ?? 'Error en la petición');
}
console.log(data);
} catch (error) {
console.error('Error:', error);
}
}
guardarCliente();
JSON.stringify() convierte el objeto JavaScript en JSON antes de enviarlo.
Recibir JSON en PHP
Los datos JSON no aparecen automáticamente en $_POST.
Podemos leer el cuerpo de la petición mediante php://input:
<?php
header('Content-Type: application/json; charset=utf-8');
try {
$input = file_get_contents('php://input');
$datos = json_decode(
$input,
true,
512,
JSON_THROW_ON_ERROR
);
$nombre = trim($datos['nombre'] ?? '');
$email = trim($datos['email'] ?? '');
if ($nombre === '' || $email === '') {
http_response_code(400);
echo json_encode([
'ok' => false,
'error' => 'Faltan datos obligatorios.'
]);
exit;
}
echo json_encode([
'ok' => true,
'mensaje' => 'Cliente recibido correctamente.'
]);
} catch (JsonException $e) {
http_response_code(400);
echo json_encode([
'ok' => false,
'error' => 'El JSON recibido no es válido.'
]);
}
Es recomendable que un endpoint que trabaja con JSON mantenga un formato coherente tanto en respuestas correctas como en errores:
{
"ok": true,
"mensaje": "Datos guardados correctamente"
}
o:
{
"ok": false,
"error": "El email no es válido"
}
También conviene utilizar códigos HTTP adecuados, como 400 para una petición incorrecta, 404 para un recurso inexistente o 500 para un error interno.
Enviar datos mediante GET
Para consultar información podemos incluir parámetros en la URL:
const productoId = 25;
const response = await fetch(
`producto.php?id=${encodeURIComponent(productoId)}`
);
PHP puede recuperarlos mediante:
$id = $_GET['id'] ?? null;
GET es apropiado para consultas que no deberían modificar el estado del servidor.
Para crear, modificar o eliminar información deberíamos utilizar métodos HTTP adecuados al tipo de operación.
Conectar Fetch con MySQL
En muchas aplicaciones PHP utilizará los parámetros recibidos para consultar una base de datos.
El flujo será:
JavaScript → Fetch → PHP → MySQL → PHP → JSON → JavaScript
Por ejemplo:
const response = await fetch('producto.php?id=25');
if (!response.ok) {
throw new Error(`Error HTTP: ${response.status}`);
}
const producto = await response.json();
console.log(producto.nombre);
PHP podría responder:
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'id' => 25,
'nombre' => 'Monitor',
'precio' => 249.90
]);
Si los valores recibidos intervienen en una consulta SQL debemos validarlos y utilizar consultas preparadas.
En nuestra guía sobre cómo conectar PHP con MySQL de forma segura explicamos cómo hacerlo con MySQLi y PDO.
Validar siempre en el servidor
Una comprobación realizada en JavaScript nunca debe considerarse suficiente.
Si esperamos recibir un email, por ejemplo, PHP debe validarlo:
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
http_response_code(400);
echo json_encode([
'ok' => false,
'error' => 'El email no es válido.'
]);
exit;
}
Lo mismo se aplica a identificadores, cantidades, permisos o cualquier otro dato relevante.
Si el endpoint modifica información privada, PHP también debe comprobar que el usuario está autenticado y autorizado para realizar la operación.
En aplicaciones que utilizan sesiones o cookies pueden ser necesarias además medidas de protección frente a CSRF.
AJAX cambia la forma de enviar la petición, pero no elimina los requisitos de seguridad de una aplicación web.
Mostrar un estado de carga
Cuando una petición tarda unos segundos conviene informar al usuario y evitar envíos duplicados.
Por ejemplo:
boton.disabled = true;
resultado.textContent = 'Procesando...';
try {
const response = await fetch('procesar.php', {
method: 'POST',
body: datos
});
if (!response.ok) {
throw new Error(`Error HTTP: ${response.status}`);
}
resultado.textContent = await response.text();
} catch (error) {
resultado.textContent = 'No se ha podido completar la operación.';
} finally {
boton.disabled = false;
}
Así evitamos que el usuario pulse repetidamente el botón mientras la operación está en curso.
¿Qué ocurre si PHP está en otro dominio?
Los ejemplos anteriores asumen que JavaScript y PHP pertenecen al mismo sitio.
Cuando el navegador intenta solicitar información de otro origen puede entrar en juego CORS.
La solución no consiste en añadir automáticamente:
Access-Control-Allow-Origin: *
El servidor debe permitir únicamente los orígenes que realmente necesiten acceder al recurso, especialmente cuando existen autenticación o datos sensibles.
Si frontend y backend pertenecen al mismo origen, normalmente no necesitaremos ninguna configuración CORS adicional.
Ejemplo completo: formulario con Fetch y PHP
HTML:
<form id="contacto">
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
<button type="submit">Enviar</button>
</form>
<p id="mensaje"></p>
JavaScript:
const formulario = document.querySelector('#contacto');
const mensaje = document.querySelector('#mensaje');
const boton = formulario.querySelector('button');
formulario.addEventListener('submit', async (event) => {
event.preventDefault();
boton.disabled = true;
mensaje.textContent = 'Enviando...';
const datos = new FormData(formulario);
try {
const response = await fetch('contacto.php', {
method: 'POST',
body: datos
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error ?? 'Error en la petición');
}
mensaje.textContent = data.mensaje;
formulario.reset();
} catch (error) {
mensaje.textContent = error.message;
} finally {
boton.disabled = false;
}
});
PHP:
<?php
header('Content-Type: application/json; charset=utf-8');
$email = trim($_POST['email'] ?? '');
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
http_response_code(400);
echo json_encode([
'ok' => false,
'error' => 'Introduce un email válido.'
]);
exit;
}
echo json_encode([
'ok' => true,
'mensaje' => 'Datos recibidos correctamente.'
]);
Tenemos así una comunicación completa entre navegador y servidor sin recargar la página, con validación, tratamiento de errores y una respuesta estructurada.
Fetch y PHP: una base sencilla para interfaces dinámicas
JavaScript puede comunicarse directamente con PHP utilizando la Fetch API sin necesidad de añadir una biblioteca únicamente para realizar peticiones HTTP.
Para formularios sencillos, FormData suele ser una opción cómoda. Cuando diseñamos endpoints o APIs, JSON permite intercambiar información de una forma más estructurada.
En ambos casos debemos validar los datos en PHP, gestionar correctamente las respuestas HTTP y aplicar los controles de seguridad que correspondan.
No todas las páginas necesitan funcionar mediante peticiones asíncronas. fetch() resulta especialmente útil cuando queremos actualizar una parte concreta de la interfaz, enviar formularios, aplicar filtros o cargar información sin realizar una navegación completa.
En ENDEOS desarrollamos y mantenemos soluciones web para empresas, incluyendo aplicaciones a medida, integraciones y modernización de desarrollos existentes.
- classic-editor-remember:
- block-editor




