基于Node.js、Express、Handlebars实现无刷新点赞/收藏功能咨询
Hey there! Let's tackle this no-refresh favorite/like feature you're building—super common for social sites, and totally doable with your Node.js/Express/Handlebars stack. Here's a step-by-step breakdown tailored to your setup:
First, we'll ditch the full-page form submit and replace it with JavaScript that sends an async request. We'll keep using Handlebars variables to pass creator data, and add data attributes to make targeting easier.
Updated Handlebars Template
{{!-- Add a meta tag for CSRF protection (critical for security) --}} <meta name="csrf-token" content="{{csrfToken}}"> {{!-- For each creator in your list --}} <div class="creator-card"> <h3>{{this.fname}} {{this.lname}}</h3> <button class="favorite-btn" data-creator-id="{{this.id}}" data-is-favorited="{{this.isFavorited}}" > {{#if this.isFavorited}}★ Unfavorite{{else}}☆ Favorite{{/if}} </button> </div>
Frontend JavaScript (No jQuery Needed)
Add this script (either in a separate .js file or inline in your template):
document.addEventListener('DOMContentLoaded', () => { const favoriteBtns = document.querySelectorAll('.favorite-btn'); favoriteBtns.forEach(btn => { btn.addEventListener('click', async (e) => { e.preventDefault(); const creatorId = btn.dataset.creatorId; const csrfToken = document.querySelector('meta[name="csrf-token"]').content; try { // Send async POST request to your backend const response = await fetch('/favoriteCreator', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken }, body: JSON.stringify({ creatorId }) }); const result = await response.json(); // Update the button UI immediately if (result.success) { if (result.isFavorited) { btn.textContent = '★ Unfavorite'; btn.classList.add('favorited'); // Add styling for active state } else { btn.textContent = '☆ Favorite'; btn.classList.remove('favorited'); } } else { alert('Oops, we couldn\'t update your favorite status. Try again later!'); } } catch (err) { console.error('Favorite error:', err); alert('Something went wrong—please check your connection.'); } }); }); });
Your existing /favoriteCreator route needs to accept JSON requests, update the user's favorite status, and return a JSON response instead of redirecting.
Backend Code (Express + Mongoose Example)
const express = require('express'); const csrf = require('csurf'); const router = express.Router(); const csrfProtection = csrf({ cookie: true }); // Make sure to parse JSON requests router.use(express.json()); // Pass CSRF token to Handlebars templates router.use((req, res, next) => { res.locals.csrfToken = req.csrfToken(); next(); }); // Updated favorite route router.post('/favoriteCreator', csrfProtection, async (req, res) => { try { const { creatorId } = req.body; const userId = req.session.userId; // Assume you're using express-session for auth // Check if user already favorited this creator const existingFavorite = await Favorite.findOne({ userId, creatorId }); if (existingFavorite) { // Unfavorite: Remove from database await Favorite.deleteOne({ userId, creatorId }); res.json({ success: true, isFavorited: false }); } else { // Favorite: Add to database await Favorite.create({ userId, creatorId }); res.json({ success: true, isFavorited: true }); } } catch (err) { console.error('Favorite route error:', err); res.status(500).json({ success: false, message: 'Internal server error' }); } }); module.exports = router;
When rendering the creator list, fetch the user's existing favorites and pass that state to the template so buttons load with the correct text:
router.get('/creators', csrfProtection, async (req, res) => { const userId = req.session.userId; // Fetch all creators const creators = await Creator.find({}); // Add isFavorited flag to each creator const creatorsWithStatus = await Promise.all(creators.map(async (creator) => { const isFavorited = await Favorite.exists({ userId, creatorId: creator._id }); return { ...creator.toObject(), isFavorited: !!isFavorited }; })); res.render('creators', { creators: creatorsWithStatus }); });
- Styling: Add CSS to the
.favoritedclass (e.g.,color: #ff4444;) to make the active state obvious. - Performance: If you have a lot of creators, use a bulk query (like
$inin MongoDB) to fetch all favorites at once instead of checking each individually. - Error Handling: Replace alert boxes with a custom toast component for a smoother user experience.
内容的提问来源于stack exchange,提问作者Shooting Stars

