Skip to content

Real-time Notifications ​

Velist includes a complete real-time notification system using WebSocket. Notifications are persisted to the database and delivered instantly to connected users.

Features ​

  • Real-time delivery via WebSocket
  • Persistent storage in SQLite database
  • 4 notification types: info, success, warning, error
  • Unread count badge in navbar
  • Mark as read / delete functionality
  • Mobile responsive dropdown UI

How It Works ​

┌─────────────┐     WebSocket      ┌─────────────┐
│   Browser   │◄──────────────────►│   Server    │
│  (AppLayout)│   ws/notifications │   (Bun)     │
└─────────────┘                    └──────┬──────┘
     │                                    │
     │                                    │ Send
     │                                    │ Notification
     │                              ┌─────▼─────┐
     └──────────────────────────────┤   SQLite  │
         Receive & Display          │ Database  │
                                    └───────────┘

Usage ​

Sending Notifications from Your Feature ​

Use the sendNotification() helper function from the notifications API:

typescript
import { sendNotification } from '../notifications/api'

// In your API route handler
await sendNotification({
  userId: 'user-uuid-here',
  type: 'success',        // 'info' | 'success' | 'warning' | 'error'
  title: 'Invoice Paid',
  message: 'Customer John paid $500 for Invoice #123'
})

Full Example in API Route ​

typescript
import { createProtectedApi } from '../_core/auth/protected'
import { sendNotification } from '../notifications/api'

export const invoicesApi = createProtectedApi('/invoices')

  .post('/', async (ctx) => {
    const { body, inertia } = ctx
    
    // Create invoice logic...
    const invoice = await createInvoice(body)
    
    // Notify the user
    await sendNotification({
      userId: (ctx as any).user.sub,
      type: 'success',
      title: 'Invoice Created',
      message: `Invoice #${invoice.number} has been created successfully.`
    })
    
    return inertia.redirect('/invoices')
  })

WebSocket Events ​

The AppLayout component automatically handles these WebSocket events:

EventDescription
connectedInitial connection, receives unread count
notificationNew notification received
markedAsReadSingle notification marked as read
markedAllAsReadAll notifications marked as read

Client-Side Actions ​

Users can interact with notifications through the dropdown in the navbar:

typescript
// Mark single notification as read
ws.send(JSON.stringify({
  action: 'markAsRead',
  notificationId: 'uuid'
}))

// Mark all as read
ws.send(JSON.stringify({
  action: 'markAllAsRead'
}))

// Ping to keep connection alive
ws.send(JSON.stringify({ action: 'ping' }))

Database Schema ​

typescript
// Table: notifications
{
  id: string           // UUID v7
  user_id: string      // FK to users
  type: string         // info | success | warning | error
  title: string
  message: string
  read_at: string|null // null = unread
  created_at: string
}

API Endpoints ​

MethodEndpointDescription
GET/notificationsView all notifications page
GET/notifications/recentGet recent notifications (dropdown)
PUT/notifications/:id/readMark notification as read
PUT/notifications/read-allMark all as read
DELETE/notifications/:idDelete notification

Demo ​

The dashboard includes a demo section where you can test sending notifications to yourself:

  1. Go to /dashboard
  2. Scroll to "Real-time Notification Demo" section
  3. Click any "Send" button
  4. Watch the bell icon in the navbar update instantly!

Cleanup ​

Notifications older than 90 days are automatically cleaned up. You can manually trigger cleanup:

typescript
import { notificationService } from '../notifications/notificationService'

await notificationService.cleanup()

Best Practices ​

  1. Keep messages concise - Title: max 50 chars, Message: max 200 chars
  2. Use appropriate types:
    • success - Completed actions (payment received, saved)
    • info - General updates (new feature, reminder)
    • warning - Attention needed (storage full, expiring soon)
    • error - Something failed (sync failed, payment error)
  3. Don't over-notify - Too many notifications = user ignores them
  4. Include actionable context - "Invoice #123 paid" not just "Payment received"

Troubleshooting ​

WebSocket not connecting? ​

  • Check browser console for errors
  • Ensure ws:// (dev) or wss:// (prod) protocol matches your setup
  • Verify JWT token is valid (try logging out/in)

Notifications not showing? ​

  • Check database: SELECT * FROM notifications WHERE user_id = '...'
  • Verify WebSocket is connected (green dot in bell icon)
  • Check browser Network tab for WS connection

Performance with many notifications? ​

  • Only last 20 notifications shown in dropdown
  • Full history at /notifications page
  • Auto-cleanup removes 90+ day old notifications

Released under the MIT License.