Como crear un sistema de likes con REST JS nonces en WordPress

Contents

Introducción

Este artículo explica paso a paso cómo crear un sistema de likes en WordPress usando la REST API, JavaScript en el cliente y nonces para proteger las operaciones. El objetivo es ofrecer una solución segura, fácil de integrar y escalable para sitios que quieran permitir a los usuarios dar me gusta a entradas (posts). Incluye código de ejemplo para el servidor (PHP) y para el cliente (JavaScript), además de recomendaciones sobre cómo almacenar y validar los likes.

Resumen de la arquitectura

La solución propuesta consta de tres piezas principales:

  • Un endpoint de la REST API que recibe las peticiones para añadir/quitar un like.
  • Lógica de servidor (PHP) que valida nonces, controla permisos, actualiza almacenamiento (postmeta o tabla propia) y devuelve estado y recuento.
  • Un script JS que envía peticiones fetch al endpoint, incluye el nonce en las cabeceras y actualiza la interfaz en tiempo real.

Consideraciones de diseño

Antes de ver el código, ten en cuenta:

  • Autenticación y autorización: lo habitual es exigir login para evitar abusos (bots, múltiples likes). Si se quiere permitir invitados, hay que aplicar limitaciones adicionales (cookies, IP, short-lived transients).
  • Protección CSRF: se utilizará el nonce de WordPress (wp_create_nonce) y el header X-WP-Nonce para que sólo clientes legítimos puedan hacer cambios.
  • Almacenamiento: para proyectos pequeños/medianos se puede usar post meta: un meta con entradas individuales liked_by por usuario (add_post_meta) y/o un meta con contador. Para escala grande, crear una tabla propia es preferible por rendimiento y consultas.
  • Condiciones de carrera: para evitar inconsistencias usar operaciones atómicas cuando sea posible el enfoque usando add_post_meta/delete_post_meta para valores por usuario reduce riesgo de colisiones respecto a sobrescribir un array completo.

1) Registro del endpoint REST

En functions.php de tu tema o mejor en un plugin, registra la ruta REST y su controlador. El endpoint comprobará el nonce en la cabecera y que el usuario esté logueado.

lt?php
// Archivo: wp-content/plugins/likes-system/likes-rest.php (ejemplo)
add_action(rest_api_init, function() {
    register_rest_route(likes/v1, /toggle, [
        methods => POST,
        callback => likes_toggle_handler,
        permission_callback => function(WP_REST_Request request) {
            // Obtén el nonce enviado por la cabecera X-WP-Nonce
            nonce = request->get_header(x_wp_nonce) ?? 
            if (! wp_verify_nonce(nonce, wp_rest) ) {
                return new WP_Error(rest_forbidden, Nonce inválido, [status => 403])
            }
            // Requiere que el usuario esté logueado (opcional: permitir invitados)
            if (! is_user_logged_in()) {
                return new WP_Error(rest_forbidden, Acceso restringido, [status => 401])
            }
            return true
        },
    ])
})

function likes_toggle_handler(WP_REST_Request request) {
    post_id = intval( request->get_param(post_id) )
    if ( post_id <= 0  ! get_post(post_id) ) {
        return new WP_Error(invalid_post, ID de post no válido, [status => 400])
    }

    user_id = get_current_user_id()
    if ( ! user_id ) {
        return new WP_Error(not_logged_in, Debes iniciar sesión, [status => 401])
    }

    // Comprobar si el usuario ya ha hecho like
    liked = user_has_liked_post(user_id, post_id)

    if ( liked ) {
        // Quitar like
        delete_post_meta(post_id, liked_by, user_id)
        liked = false
    } else {
        // Añadir like (guardamos entradas independientes para evitar sobrescrituras)
        add_post_meta(post_id, liked_by, user_id)
        liked = true
    }

    // Actualizar recuento (se puede calcular dinámicamente)
    count = count( get_post_meta(post_id, liked_by) )
    update_post_meta(post_id, likes_count, count)

    return rest_ensure_response([
        liked => (bool) liked,
        count => (int) count,
        post_id => post_id,
    ])
}

function user_has_liked_post(user_id, post_id) {
    // get_post_meta con tercer parámetro false devuelve array con todos los values
    likes = get_post_meta(post_id, liked_by, false)
    if ( empty(likes) ) {
        return false
    }
    return in_array((string) user_id, array_map(strval, likes), true)
}
?gt

Notas de seguridad y buenas prácticas

  • Evita usar arreglos serializados grandes en un solo meta si esperas muchos likes. En su lugar, usa entradas de meta múltiples (como en el ejemplo) o una tabla personalizada.
  • Valida siempre post_id y el usuario. No confíes en datos del cliente.
  • Usa WP_Error para devolver errores con códigos HTTP apropiados.

2) Encolar el script y pasar datos (nonce y root)

Necesitas inyectar desde PHP al JS la URL raíz de la REST API y el nonce. Un método clásico es wp_localize_script (aunque su nombre sugiere traducción, sirve para pasar variables).

lt?php
// En tu plugin o functions.php
function likes_enqueue_scripts() {
    wp_enqueue_script(
        likes-js,
        plugin_dir_url(__FILE__) . likes.js,
        [],
        1.0,
        true
    )

    wp_localize_script(likes-js, wpLikes, [
        root  => esc_url_raw( rest_url() ),
        nonce => wp_create_nonce(wp_rest),
    ])
}
add_action(wp_enqueue_scripts, likes_enqueue_scripts)
?gt

3) HTML mínimo para el botón

Inserta en la plantilla la marca HTML para el botón. Asegúrate de imprimir el recuento inicial del servidor (para SEO y accesibilidad).

ltbutton class=post-like-button data-post-id=123 aria-pressed=falsegt
    ltspan class=like-icongt♥lt/spangt
    ltspan class=like-countgt0lt/spangt
lt/buttongt

4) JavaScript: manejo del clic y petición REST

El script escucha clicks en botones con clase .post-like-button, envía una petición POST al endpoint y actualiza la UI según la respuesta. Usa fetch y el header X-WP-Nonce.

// Archivo: likes.js
document.addEventListener(DOMContentLoaded, function () {
    document.body.addEventListener(click, function (e) {
        var btn = e.target.closest(.post-like-button)
        if (!btn) return

        e.preventDefault()
        var postId = parseInt(btn.getAttribute(data-post-id), 10)
        if (!postId) return

        // Mostrar estado de carga (opcional)
        btn.disabled = true

        fetch( wpLikes.root   likes/v1/toggle, {
            method: POST,
            credentials: same-origin,
            headers: {
                Content-Type: application/json,
                X-WP-Nonce: wpLikes.nonce
            },
            body: JSON.stringify({ post_id: postId })
        })
        .then(function (res) {
            if (!res.ok) throw res
            return res.json()
        })
        .then(function (data) {
            if (data  typeof data.count !== undefined) {
                var countEl = btn.querySelector(.like-count)
                if (countEl) countEl.textContent = data.count

                // Actualizar estado del botón
                btn.setAttribute(aria-pressed, data.liked ? true : false)
                if (data.liked) btn.classList.add(liked) else btn.classList.remove(liked)
            }
        })
        .catch(function (err) {
            // Puedes manejar errores más finamente leyendo err.json()
            console.error(Error al togglear like:, err)
        })
        .finally(function () {
            btn.disabled = false
        })
    })
})

5) Alternativas de almacenamiento

Dependiendo de la dimensión del sitio, considera estas alternativas:

  • Post meta multivalor (ejemplo anterior): fácil de implementar, indicado para cargas moderadas.
  • Tabla personalizada: mejor para muchas filas y consultas frecuentes permite índices y consultas eficientes por usuario/post.
  • Objetos transients/caching: guarda el recuento en memoria y sincroniza periódicamente para reducir lecturas de meta.

6) Soporte para usuarios invitados (opcional)

Si quieres permitir likes sin login, añade controles anti-abuso:

  • Guardar una cookie per-post que impida repetir la acción desde el mismo navegador.
  • Limitar por IP y usar transients para bloquear solicitudes repetidas.
  • Usar un combo cookie IP fingerprinting para reducir fraude.

Recuerda: los nonces protegen contra CSRF, pero no autentican a un bot por eso conviene limitaciones adicionales para invitados.

7) Mejoras y funcionalidades extra

  • Endpoint para consultar estado inicial y si el usuario ya ha dado like (GET), de forma que al cargar la página puedas marcar el botón correctamente sin imprimir en server-side.
  • Animaciones y accesibilidad: usa aria-pressed y mensajes de estado para lectores de pantalla.
  • Logs y métricas: monitoriza el uso para detectar patrones sospechosos.
  • Rate limiting: aplica límites por usuario/IP para evitar abusos.

8) Ejemplo completo resumido

A continuación se muestra un resumen de los elementos clave (ya vistos). Integrar todo en un plugin o en functions.php siguiendo buenas prácticas de WordPress.

Comportamiento esperado

  1. Al cargar la página: el botón muestra el recuento que viene del servidor (meta likes_count).
  2. Al hacer click: el script envía POST al endpoint con X-WP-Nonce. El servidor valida el nonce y el usuario, alterna el estado y devuelve { liked, count }.
  3. El cliente actualiza la UI con el nuevo recuento y marca el estado del botón.

Conclusión

Este enfoque combina la flexibilidad de la REST API de WordPress con nonces para proteger operaciones sensibles, y JavaScript moderno para ofrecer una experiencia ágil e interactiva. Para muchos sitios la solución basada en postmeta multivalor es suficiente para sitios con mucho tráfico o millones de likes, valdrá la pena una tabla dedicada y caching inteligente.

Recursos y enlaces útiles



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 *