如何在PWA网站中向服务器回传数据并更新数据库?
Got it, let's break this down—you're right that most PWA tutorials fixate on pulling data from servers, but pushing changes and syncing updates across all users is just as critical for apps like your contact manager. Here's a practical, step-by-step approach with code examples to make this work:
Your PWA is just a web app at its core, so sending data back to the server works exactly like any other frontend-backend setup. First, build server endpoints to handle create, read, update, and delete (CRUD) actions for contacts.
Here's a simple Node.js/Express example (this logic translates to any backend framework like Django, Rails, or .NET):
// Server-side: Express routes for contact CRUD const express = require('express'); const router = express.Router(); const Contact = require('../models/Contact'); // Your database model // Middleware to authenticate logged-in users (e.g., JWT verification) const authenticateUser = require('../middleware/auth'); // Add a new contact router.post('/contacts', authenticateUser, async (req, res) => { try { const newContact = await Contact.create({ ...req.body, userId: req.user.id // Link contact to the logged-in user }); // We'll add real-time broadcast here later res.status(201).json(newContact); } catch (err) { res.status(400).json({ error: 'Failed to create contact' }); } }); // Update an existing contact router.put('/contacts/:id', authenticateUser, async (req, res) => { try { const updatedContact = await Contact.update(req.body, { where: { id: req.params.id, userId: req.user.id }, returning: true }); res.json(updatedContact[1][0]); // Return the updated contact } catch (err) { res.status(400).json({ error: 'Failed to update contact' }); } }); // Delete a contact router.delete('/contacts/:id', authenticateUser, async (req, res) => { try { await Contact.destroy({ where: { id: req.params.id, userId: req.user.id } }); res.status(204).send(); } catch (err) { res.status(400).json({ error: 'Failed to delete contact' }); } }); module.exports = router;
Since your app uses login, you need to verify the user's identity with every request. For JWT (common in PWAs), include the token in the request headers:
// Client-side: Helper function to send authenticated requests async function sendAuthenticatedRequest(url, options = {}) { const token = localStorage.getItem('authToken'); return fetch(url, { ...options, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, ...options.headers } }); } // Example: Add a contact from the PWA async function addContact(contactData) { try { const response = await sendAuthenticatedRequest('/api/contacts', { method: 'POST', body: JSON.stringify(contactData) }); if (!response.ok) throw new Error('Failed to add contact'); return await response.json(); } catch (err) { console.error(err); alert('Oops, couldn\'t add that contact right now'); } }
PWAs need to work offline, so you can't just fail if the user has no internet. Store pending actions in IndexedDB, then sync them automatically when the user is back online. We'll use the idb library for simpler IndexedDB interactions:
// Client-side: Set up IndexedDB for offline actions import { openDB } from 'idb'; const dbPromise = openDB('ContactApp', 1, { upgrade(db) { db.createObjectStore('pendingActions', { keyPath: 'id', autoIncrement: true }); } }); // Save an action to offline storage async function saveOfflineAction(actionType, data) { const db = await dbPromise; await db.add('pendingActions', { actionType, data, timestamp: Date.now() }); alert('Saved offline—changes will sync when you\'re back online'); } // Sync pending actions when the user goes online window.addEventListener('online', async () => { const db = await dbPromise; const pendingActions = await db.getAll('pendingActions'); for (const action of pendingActions) { try { switch (action.actionType) { case 'add': await addContact(action.data); break; case 'update': await sendAuthenticatedRequest(`/api/contacts/${action.data.id}`, { method: 'PUT', body: JSON.stringify(action.data) }); break; case 'delete': await sendAuthenticatedRequest(`/api/contacts/${action.data.id}`, { method: 'DELETE' }); break; } await db.delete('pendingActions', action.id); } catch (err) { console.error('Failed to sync action:', err); // Leave it in storage to retry later } } });
To show changes to every user using the PWA, use WebSockets (Socket.io is a user-friendly choice for this). It lets the server broadcast updates to all connected clients instantly.
Server-side setup:
// Server-side: Initialize Socket.io const http = require('http'); const express = require('express'); const app = express(); const server = http.createServer(app); const io = require('socket.io')(server, { cors: { origin: '*' } // Adjust for your production domain }); // After creating a contact in the POST route, broadcast the update router.post('/contacts', authenticateUser, async (req, res) => { try { const newContact = await Contact.create({ ...req.body, userId: req.user.id }); io.emit('contactAdded', newContact); // Broadcast to all clients res.status(201).json(newContact); } catch (err) { res.status(400).json({ error: 'Failed to create contact' }); } }); // Repeat this pattern for update/delete routes: // io.emit('contactUpdated', updatedContact); // io.emit('contactDeleted', contactId); server.listen(3000, () => console.log('Server running on port 3000'));
Client-side setup:
// Client-side: Connect to Socket.io after login import { io } from 'socket.io-client'; let socket; function initSocketConnection() { socket = io(); // Listen for updates from the server socket.on('contactAdded', (newContact) => { // Update your local contact list state (e.g., React useState, Vue data) updateContactList(prev => [...prev, newContact]); // Update your service worker cache if you're using one (optional) syncCacheWithNewData(); }); socket.on('contactUpdated', (updatedContact) => { updateContactList(prev => prev.map(c => c.id === updatedContact.id ? updatedContact : c)); }); socket.on('contactDeleted', (contactId) => { updateContactList(prev => prev.filter(c => c.id !== contactId)); }); } // Call this function right after the user logs in initSocketConnection();
If you're using a service worker to cache contact data (for faster loads), make sure to update the cache after receiving real-time updates. Here's a quick example with Workbox:
// Client-side: Send a message to the service worker to update cache async function syncCacheWithNewData() { if ('serviceWorker' in navigator) { const registration = await navigator.serviceWorker.ready; registration.active.postMessage({ type: 'UPDATE_CONTACTS_CACHE', data: await fetch('/api/contacts').then(res => res.json()) }); } } // Service worker: Listen for cache update messages self.addEventListener('message', (event) => { if (event.data.type === 'UPDATE_CONTACTS_CACHE') { caches.open('contacts-cache-v1').then(cache => { cache.put('/api/contacts', new Response(JSON.stringify(event.data.data))); }); } });
If two users edit the same contact at the same time, use optimistic locking to avoid overwrites. Add a version field to your contact model—when updating, send the current version, and the server only applies changes if the version matches:
// Server-side: Update route with optimistic locking router.put('/contacts/:id', authenticateUser, async (req, res) => { const { version, ...contactData } = req.body; const contact = await Contact.findByPk(req.params.id); if (contact.version !== version) { return res.status(409).json({ error: 'Contact was updated by another user—refresh and try again' }); } contactData.version = version + 1; const updatedContact = await contact.update(contactData); io.emit('contactUpdated', updatedContact); res.json(updatedContact); });
Putting it all together: When a user adds/edits/deletes a contact, the client either sends the request immediately (online) or stores it offline. The server updates the database, then broadcasts the change via WebSockets to all connected clients. Each client updates their UI and cache, ensuring everyone sees the latest data. Offline actions sync automatically when the user is back online.
内容的提问来源于stack exchange,提问作者Luke Marks

