Como usar AJAX con la REST API y fetch en JavaScript en WordPress

Contents

Introducción

Este artículo explica con todo lujo de detalles cómo utilizar AJAX moderno con la REST API de WordPress usando fetch en JavaScript. Cubriremos desde registrar endpoints personalizados en PHP y asegurar las rutas, hasta encolar scripts, pasar nonces y ejemplos reales de consumo con fetch para GET, POST, PUT/DELETE y subida de archivos. También veremos buenas prácticas de seguridad, manejo de errores y soluciones para autenticación externa.

Requisitos previos

  • WordPress 4.7 (introducción de la REST API en el core).
  • Conocimientos básicos de PHP y JavaScript.
  • Acceso para editar functions.php de tu tema o crear un plugin pequeño.
  • Entender la diferencia entre peticiones públicas (lectura) y peticiones que requieren autenticación (crear/editar/eliminar).

Por qué usar la REST API y fetch en lugar de admin-ajax.php

  • REST API ofrece rutas estandarizadas y control fino de permisos y formatos JSON.
  • fetch es la API moderna de JavaScript para realizar peticiones asíncronas y trabaja naturalmente con promesas y JSON.
  • Mejor rendimiento y claridad: endpoints REST claros y separación de frontend/backend.

Registrar un endpoint REST en PHP (ejemplo práctico)

Este ejemplo muestra cómo registrar una ruta personalizada que responda tanto a GET como a POST, valide datos, compruebe permisos y devuelva respuestas JSON adecuadas.

 WP_REST_Server::READABLE, // GET
            callback            => mi_plugin_get_mensaje,
            permission_callback => __return_true, // lectura pública
        ),
        array(
            methods             => WP_REST_Server::CREATABLE, // POST
            callback            => mi_plugin_post_mensaje,
            permission_callback => mi_plugin_can_edit, // usuario autenticado con permiso
            args                => array(
                contenido => array(
                    required          => true,
                    validate_callback => is_string,
                    sanitize_callback => sanitize_text_field,
                ),
            ),
        ),
    ) )
} )

function mi_plugin_get_mensaje( request ) {
    data = array(
        mensaje => Hola desde la REST API personalizada,
        time    => current_time( mysql ),
    )
    return rest_ensure_response( data )
}

function mi_plugin_post_mensaje( request ) {
    contenido = request->get_param( contenido )
    // Aquí harías sanitización y guardado en DB si hace falta
    respuesta = array(
        guardado  => true,
        contenido => contenido,
        user      => wp_get_current_user()->user_login,
    )
    return rest_ensure_response( respuesta )
}

function mi_plugin_can_edit() {
    // Solo usuarios logueados con la capacidad de editar posts
    return current_user_can( edit_posts )
}
?>

Puntos importantes del ejemplo PHP

  • namespace usado: mi-plugin/v1 para versionado.
  • permission_callback define quién puede acceder: puede devolver true/false o comprobar capacidades.
  • args se usan para definir validaciones y sanitizaciones automáticas por la API.
  • Siempre usar funciones de sanitización como sanitize_text_field y validar entrada.

Encolar scripts y pasar variables útiles (REST URL y nonce)

Para que tu JavaScript conozca la URL base de la REST API y el nonce para autenticación, encola el script y pasa variables desde PHP.

 esc_url_raw( rest_url() ),
        nonce => wp_create_nonce( wp_rest ),
    ) )
}
?>

Resultado: en tu JS podrás acceder a miApiSettings.root y miApiSettings.nonce.

Consumir la REST API con fetch: ejemplos prácticos

En todos los ejemplos JavaScript se asume que el archivo fue encolado como en el ejemplo anterior y que existe la variable global miApiSettings.

1) GET — Obtener posts públicos

// GET simple: obtener los 5 últimos posts
fetch( miApiSettings.root   wp/v2/posts?per_page=5 )
  .then( response => {
    if ( ! response.ok ) throw new Error(Error en la respuesta:    response.status)
    return response.json()
  })
  .then( posts => {
    console.log(Posts recibidos, posts)
    // Renderiza posts en tu UI
  })
  .catch( error => {
    console.error(Error al obtener posts:, error)
  })

2) POST — Crear un post (usuario autenticado) usando X-WP-Nonce

Para acciones que modifican datos en el sitio, usa la ruta core /wp/v2/posts y manda el nonce en el header X-WP-Nonce. Además incluye credentials si necesitas enviar cookies (p. ej. para autenticación por sesión).

// Crear un post privado/publicado
const newPost = {
  title: Título creado desde fetch,
  content: Contenido HTML desde JavaScript,
  status: publish // draft o publish según permisos
}

fetch( miApiSettings.root   wp/v2/posts, {
  method: POST,
  credentials: same-origin, // incluye cookies si el usuario está logueado por sesión
  headers: {
    Content-Type: application/json,
    X-WP-Nonce: miApiSettings.nonce
  },
  body: JSON.stringify( newPost )
})
.then( response => {
  if ( ! response.ok ) return response.json().then( err => Promise.reject(err) )
  return response.json()
})
.then( data => {
  console.log(Post creado:, data)
})
.catch( err => {
  console.error(Error creando post:, err)
})

3) PUT/PATCH — Actualizar un post

// Actualizar título de un post con ID 123
const update = { title: Nuevo título desde fetch }

fetch( miApiSettings.root   wp/v2/posts/123, {
  method: POST, // WordPress acepta POST (con _method=PUT) o usar directamente PUT si tu servidor lo soporta
  credentials: same-origin,
  headers: {
    Content-Type: application/json,
    X-WP-Nonce: miApiSettings.nonce
  },
  body: JSON.stringify(update)
})
.then( r => {
  if (!r.ok) return r.json().then(e => Promise.reject(e))
  return r.json()
})
.then( res => console.log(Actualizado:, res))
.catch( e => console.error(Error al actualizar:, e))

4) DELETE — Eliminar un post

fetch( miApiSettings.root   wp/v2/posts/123?force=true, {
  method: DELETE,
  credentials: same-origin,
  headers: {
    X-WP-Nonce: miApiSettings.nonce
  }
})
.then( r => {
  if (!r.ok) return r.json().then(e => Promise.reject(e))
  return r.json()
})
.then( res => console.log(Eliminado:, res))
.catch( e => console.error(Error al eliminar:, e))

5) Subir archivos (media) con FormData

Para subir archivos al endpoint core /wp/v2/media utiliza FormData y no fijes manualmente el header Content-Type: fetch lo hará correctamente con el boundary. Usa X-WP-Nonce para autenticación.

// fileInput es un input[type=file]
const file = fileInput.files[0]
const form = new FormData()
form.append(file, file)
form.append(title, file.name)

fetch( miApiSettings.root   wp/v2/media, {
  method: POST,
  credentials: same-origin,
  headers: {
    X-WP-Nonce: miApiSettings.nonce
    // NO pongas Content-Type fetch lo establece automáticamente al enviar FormData
  },
  body: form
})
.then( r => {
  if (!r.ok) return r.json().then(e => Promise.reject(e))
  return r.json()
})
.then( media => {
  console.log(Media subida:, media)
  // media.source_url para usarla en el front
})
.catch( e => console.error(Error al subir media:, e))

6) Manejo de errores específicos de WP REST API

Las respuestas de error de WordPress suelen tener la forma { code, message, data: { status } }. Comprueba si la respuesta no es ok y parsea JSON para mostrar error útil.

fetch( miApiSettings.root   wp/v2/some-route )
  .then( async response => {
    const payload = await response.json()
    if (!response.ok) {
      // payload puede contener code/message/data
      throw payload
    }
    return payload
  })
  .then( data => { / ok / })
  .catch( err => {
    if ( err  err.message ) {
      console.error(WP Error:, err.code, err.message, err.data )
    } else {
      console.error(Otro error:, err)
    }
  })

Protección, nonces y autenticación

  • Nonces: para peticiones desde el frontend de usuarios autenticados, usa wp_create_nonce(wp_rest) y envíalo como header X-WP-Nonce. Esto protege contra CSRF para operaciones que requieren autenticación.
  • Credentials: si confías en cookies de sesión, añade credentials: same-origin para que fetch incluya cookies de WordPress.
  • Autenticación externa: para clientes externos (apps, servicios) puedes usar Basic Auth (malo para producción), JWT (recomendado con HTTPS) o implementar un token personalizado. Si usas JWT, añade el Bearer token en Authorization: Bearer TOKEN.
  • permission_callback: en tus endpoints personalizados, utiliza permission_callback para verificar capacidades con current_user_can() u otros checks, nunca confíes sólo en verificación del nonce en el cliente.

Seguridad en el servidor: sanitización, validación y permisos

  1. Sanitiza todos los campos de entrada: sanitize_text_field, wp_kses_post, validaciones numéricas con intval o floatval.
  2. Valida con validate_callback en args del register_rest_route cuando sea posible.
  3. Comprueba capacidades con current_user_can en permission_callback.
  4. Devuelve mensajes de error estandarizados con new WP_Error( code, message, array( status => 403 ) ) y usa rest_ensure_response.

Consideraciones sobre CORS

Si tu cliente corre en otro dominio, habilita CORS adecuadamente. Añadir cabeceras CORS abiertas puede exponer tu API, por lo que hazlo con cuidado y limita a orígenes concretos y métodos permitidos. Ejemplo básico en PHP:

// No pongas esto en producción sin restringir origen
add_action( rest_api_init, function() {
    remove_filter( rest_pre_serve_request, rest_send_cors_headers )
    add_filter( rest_pre_serve_request, function( value ) {
        header( Access-Control-Allow-Origin: https://tu-frontend-ejemplo.com )
        header( Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETE )
        header( Access-Control-Allow-Credentials: true )
        header( Access-Control-Allow-Headers: X-WP-Nonce, Content-Type, Authorization )
        return value
    })
}, 15 )

Buenas prácticas y recomendaciones

  • Preferir endpoints core (/wp/v2/…) cuando existan en lugar de reimplementar la funcionalidad.
  • Versiona tus endpoints personalizados con un namespace tipo mi-plugin/v1.
  • Mantén la menor cantidad de lógica posible en el frontend realiza las validaciones críticas en el servidor.
  • Mide y limita la paginación para evitar consultas pesadas (usa per_page y pagina). Usa transients o cache para respuestas costosas.
  • Documenta tus endpoints y sus parámetros la REST API core ya auto-documenta con /wp-json/.

Ejemplo completo: flujo típico

  1. Registrar endpoint y encolar script (PHP) — ya mostrado.
  2. En frontend, el usuario rellena un formulario. Cuando envía, JavaScript hace fetch con X-WP-Nonce y cuerpo JSON o FormData.
  3. El servidor valida permiso y sanitiza datos en permission_callback y callback.
  4. El servidor responde JSON con éxito o error estructurado frontend muestra mensajes al usuario.

Alternativa para progreso de subida (fetch no tiene progreso de subida sencillo)

Si necesitas mostrar progresos de subida reales en el frontend puedes usar XMLHttpRequest en lugar de fetch para aprovechar onprogress de upload.

// Ejemplo con XMLHttpRequest para progreso de subida
const xhr = new XMLHttpRequest()
xhr.open(POST, miApiSettings.root   wp/v2/media, true)
xhr.setRequestHeader(X-WP-Nonce, miApiSettings.nonce)

xhr.upload.onprogress = function(e) {
  if (e.lengthComputable) {
    const percent = (e.loaded / e.total)  100
    console.log(Progreso upload:    percent.toFixed(2)   %)
  }
}

xhr.onload = function() {
  if (xhr.status >= 200  xhr.status < 300) {
    console.log(Subida completada, JSON.parse(xhr.responseText))
  } else {
    console.error(Error en la subida, xhr.responseText)
  }
}

xhr.onerror = function() {
  console.error(Error en la petición XHR)
}

const fd = new FormData()
fd.append(file, fileInput.files[0])
xhr.send(fd)

Resumen útil (tabla corta)

GET No requiere nonce para endpoints públicos usar fetch simple y parsear JSON.
POST/PUT/DELETE Usar X-WP-Nonce credentials:same-origin o token (JWT). Validar permisos en servicio.
Media upload Usar FormData no poner Content-Type manualmente usar X-WP-Nonce.

Conclusión

La combinación de la REST API de WordPress y fetch en el navegador ofrece una manera moderna, eficiente y segura de construir interacciones AJAX. Registra y versiona tus endpoints, protege con permission_callback y nonces, usa sanitización en PHP y maneja errores detalladamente en el frontend. Para subir archivos usa FormData y, si necesitas progreso de subida, valora XMLHttpRequest.

Lecturas recomendadas



Acepto donaciones de BAT's mediante el navegador Brave :)



Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *