A complete CMS-ready example of implementing web push notifications in an Astro project with user authentication, role-based permissions, and AstroDB for data storage.
- ✅ User Authentication with session management
- ✅ Role-Based Access Control (Admin, Moderator, User, Subscriber)
- ✅ Service Worker for handling push notifications
- ✅ AstroDB for storing users, sessions, subscriptions, and logs
- ✅ Targeted Notifications - Send to specific users or roles
- ✅ Admin Dashboard for managing notifications
- ✅ Notification History with tracking and analytics
- ✅ Web Push with VAPID authentication
- ✅ Device Management - Track multiple devices per user
- ✅ TypeScript support throughout
| Role | Can Subscribe | Can Send Notifications | Can Access Admin |
|---|---|---|---|
| Admin | ✅ | ✅ (all features) | ✅ |
| Moderator | ✅ | ✅ (all features) | ✅ |
| User | ✅ | ❌ | ❌ |
| Subscriber | ✅ | ❌ | ❌ |
npm installVAPID keys are required for web push authentication:
npm run generate-vapidThis will output something like:
VAPID_PUBLIC_KEY=BOX...
VAPID_PRIVATE_KEY=abc...
VAPID_SUBJECT=mailto:your-email@example.com
Create a .env file in the project root:
VAPID_PUBLIC_KEY=your_public_key_here
VAPID_PRIVATE_KEY=your_private_key_here
VAPID_SUBJECT=mailto:your-email@example.com.env file or share your private key!
Initialize the AstroDB schema and seed demo users:
npx astro db pushThis creates 4 demo accounts (all use password: password123):
- admin@example.com - Full admin access
- moderator@example.com - Can send notifications
- user@example.com - Regular user
- subscriber@example.com - Basic subscriber
For a remote database (production), use:
npx astro db push --remoteSee the Astro DB documentation for connecting to libSQL databases like Turso.
npm run devVisit http://localhost:4321 and login with one of the demo accounts.
- Login at
/loginwith your credentials - Enable Notifications - Click the button and grant permission
- Receive Notifications - You'll get push notifications sent by admins/moderators
- Manage Subscription - Disable notifications anytime from your dashboard
- Access Admin Dashboard at
/admin - Send Targeted Notifications:
- All Users - Broadcast to everyone
- Specific Role - Send to admins, moderators, users, or subscribers only
- Specific User - Send to individual user by ID
- View History - See all sent notifications with delivery stats
- Quick Actions - One-click send to role groups
POST /api/auth/login- Login with email/passwordPOST /api/auth/logout- Logout current sessionGET /api/auth/me- Get current user info
POST /api/subscribe- Save user's push subscription (requires auth)POST /api/unsubscribe- Remove subscription (requires auth)
-
POST /api/push/send-targeted- Send targeted notifications (moderator+ only){ "targetType": "role", // "all", "role", or "user" "targetValue": "admin", // role name or user ID "title": "Important Update", "message": "Your notification message", "url": "/page", "icon": "/icon.png" } -
GET /api/push/history- Get notification history (moderator+ only)
├── db/
│ ├── config.ts # AstroDB schema (Users, Sessions, Subscriptions, Logs)
│ └── seed.ts # Demo user seed data
├── scripts/
│ └── generate-vapid.js # VAPID key generation utility
├── src/
│ ├── components/
│ │ └── PushManager.astro # Client-side subscription management
│ ├── lib/
│ │ └── auth.ts # Authentication utilities
│ └── pages/
│ ├── index.astro # User dashboard
│ ├── login.astro # Login page
│ ├── admin.astro # Admin dashboard
| ├── sw.js.ts # Service worker for push events
│ └── api/
│ ├── auth/
│ │ ├── login.ts # Login Endpoint
│ │ ├── logout.ts # Logout endpoint
│ │ └── me.ts # Endpoint for info about current user
│ └── push/
│ ├── send-targeted.ts # Targeted push endpoint
│ └── history.ts # Notification history
├── astro.config.mjs # Astro config with DB integration
└── package.json
id- Unique user IDemail- User email (unique)username- Username (unique)passwordHash- Bcrypt password hashrole- User role (admin/moderator/user/subscriber)displayName- Display nameactive- Account status
id- Session IDuserId- Foreign key to Usertoken- Session token (unique)expiresAt- Expiration date
id- Subscription IDuserId- Foreign key to Userendpoint- Push endpoint URL (unique)keys- Push keys (p256dh, auth)deviceName- Device identifier
id- Log IDtitle- Notification titlebody- Notification bodytargetType- Target type (all/role/user)targetValue- Target value (role or user ID)sentBy- Foreign key to User (sender)recipientCount- Number of target userssuccessCount- Successful sendsfailureCount- Failed sends
The client (src/component/PushManager) registers a service worker (src/pages/sw.js.ts) that:
- Listens for
pushevents - Displays notifications using the Notifications API
- Handles notification clicks
When a user clicks "Enable Notifications":
- Browser requests notification permission
- Service worker subscribes to push using the VAPID public key
- Subscription details are sent to
/api/subscribe - Subscription is stored in AstroDB
The /api/send-push endpoint:
- Retrieves all subscriptions from AstroDB
- Uses the
web-pushlibrary with VAPID keys - Sends push notifications to each subscription
- Removes invalid/expired subscriptions
const PushSubscription = defineTable({
columns: {
id: column.text({ primaryKey: true }),
endpoint: column.text({ unique: true }),
keys: column.json(), // { p256dh, auth }
createdAt: column.date({ default: new Date() }),
}
});Returns the VAPID public key for client-side subscription.
Saves a push subscription to the database.
Body:
{
"endpoint": "https://...",
"keys": {
"p256dh": "...",
"auth": "..."
}
}Removes a subscription from the database.
Body:
{
"endpoint": "https://..."
}Sends a push notification to all subscriptions.
Body (optional):
{
"title": "Custom Title",
"body": "Custom message",
"icon": "/icon.png",
"url": "/some-page"
}Quick endpoint to send a test notification to all subscriptions.
# Login as admin
curl -X POST http://localhost:4321/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@example.com","password":"password123"}'
# Check current user
curl http://localhost:4321/api/auth/me --cookie "session_token=YOUR_TOKEN"- Login to the app
- Click "Enable Notifications"
- Grant permission when prompted
Via Admin UI:
- Go to
/admin - Fill out the form
- Select target type (all/role/user)
- Click "Send Notification"
Via API:
# Send to all users
curl -X POST http://localhost:4321/api/push/send-targeted \
-H "Content-Type: application/json" \
-H "Cookie: session_token=YOUR_TOKEN" \
-d '{
"targetType":"all",
"title":"System Update",
"message":"Maintenance scheduled for tonight"
}'
# Send to specific role
curl -X POST http://localhost:4321/api/push/send-targeted \
-H "Content-Type: application/json" \
-H "Cookie: session_token=YOUR_TOKEN" \
-d '{
"targetType":"role",
"targetValue":"admin",
"title":"Admin Alert",
"message":"Please review pending items"
}'
# Send to specific user
curl -X POST http://localhost:4321/api/push/send-targeted \
-H "Content-Type: application/json" \
-H "Cookie: session_token=YOUR_TOKEN" \
-d '{
"targetType":"user",
"targetValue":"user-1",
"title":"Personal Message",
"message":"You have a new message"
}'curl http://localhost:4321/api/push/history?limit=10 \
-H "Cookie: session_token=YOUR_TOKEN"This example can be integrated into any CMS by:
-
User System Integration:
- Replace the demo users with your CMS user system
- Map CMS roles to notification permissions
- Use existing authentication instead of the built-in system
-
Admin UI Integration:
- Embed the admin dashboard in your CMS admin panel
- Customize the UI to match your CMS theme
- Add role management for notification permissions
-
Notification Triggers:
- Call
/api/push/send-targetedfrom your CMS events - Trigger notifications on content publish, user actions, etc.
- Schedule notifications using your CMS cron/scheduler
- Call
// Send notification from your CMS backend
async function sendPushNotification(targetType, targetValue, title, message) {
const response = await fetch('https://your-astro-app.com/api/push/send-targeted', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cookie': `session_token=${process.env.ADMIN_SESSION_TOKEN}`
},
body: JSON.stringify({
targetType,
targetValue,
title,
message,
url: '/'
})
});
return response.json();
}
// Usage examples
await sendPushNotification('all', null, 'Maintenance', 'Site will be down tonight');
await sendPushNotification('role', 'admin', 'Admin Alert', 'New user signup');
await sendPushNotification('user', 'user-123', 'Message', 'You have a new comment');Set these in your production environment:
VAPID_PUBLIC_KEYVAPID_PRIVATE_KEYVAPID_SUBJECT
For production, connect to a libSQL database:
-
Using Turso:
# Create database turso db create my-push-db # Get connection details turso db show my-push-db turso db tokens create my-push-db # Set environment variables ASTRO_DB_REMOTE_URL=libsql://... ASTRO_DB_APP_TOKEN=... # Push schema npm run db:push
-
Configure in your hosting platform (Vercel, Netlify, Cloudflare, etc.)
npm run buildThe built site will be in dist/ with server-side rendering enabled.
Push notifications require:
- Service Worker support
- Push API support
- HTTPS (or localhost for development)
Supported browsers:
- Chrome/Edge 42+
- Firefox 44+
- Safari 16+ (macOS 13+, iOS 16.4+)
- Opera 29+
- Ensure
.envfile exists with valid VAPID keys - Restart the dev server after creating
.env
- User must grant notification permission
- Check browser settings if permission was previously denied
- Ensure you're on HTTPS or localhost
- Check browser console for errors
- Clear browser cache and service workers
- Run
npx astro db pushto initialize schema - Check that
@astrojs/dbis installed - Ensure
astro.config.mjsincludesdb()integration
MIT