Skip to main content
WebSockets Tutorial
CHAPTER 14 Beginner

Real-Time Notifications System

Updated: May 14, 2026
25 min read

# CHAPTER 14

Real-Time Notifications System

1. Introduction

Real-time notifications are a staple of modern web applications. Whether it is a red badge indicating a new email, a pop-up toast when someone likes your post, or an alert that a system task has finished, users expect instant feedback without refreshing the page. In this chapter, we will build a notification system using WebSockets, integrating Alpine.js to manage a dynamic list of unread alerts.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Architect a one-way notification push system.
  • Build a responsive Notification Bell UI.
  • Use Alpine.js to track unread notification counts.
  • Display transient "toast" popups for new events.

3. Beginner-Friendly Explanation

Think of your smartphone. When you get a text message, your phone vibrates (a transient popup), and a little red circle with the number "1" appears on the Messages app icon (the unread count). With WebSockets, the server is acting like the cell tower. The moment an event happens in the database, the server pushes a tiny JSON message to your browser. Your browser catches it, updates the red circle on your website's navigation bar, and slides a nice little message onto the screen.

4. Real-World Examples

  • Facebook / LinkedIn: The globe or bell icon in the top navigation bar immediately updates its counter when someone interacts with your profile.
  • E-commerce: A popup showing "Your order has been shipped!" while you are browsing the store.

5. Step-by-Step Tutorial

Let's build a UI with a Notification Bell and a dropdown list of alerts.

Step 1: Create an Alpine component to store an array of notifications and calculate the unreadCount. Step 2: Build the HTML/Tailwind UI for the Bell icon and the dropdown menu. Step 3: Connect the WebSocket. Step 4: In the onmessage event, listen for type: "notification". Step 5: Push the incoming payload into the Alpine array, which will automatically update the UI.

6. The Notification Application

Here is the complete implementation of a real-time notification bell.

7. HTML, Tailwind & Alpine.js Example

html
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Real-Time Notifications</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
</head>
<body class="bg-gray-100 p-8 font-sans">

    <!-- Alpine App Scope -->
    <div x-data="notificationApp()" class="max-w-3xl mx-auto">
        
        <!-- Navigation Bar -->
        <div class="bg-white p-4 shadow flex justify-between items-center rounded">
            <h1 class="text-xl font-bold text-gray-700">My Dashboard</h1>
            
            <!-- Notification Bell with Dropdown -->
            <div class="relative" x-data="{ open: false }">
                
                <!-- Bell Button -->
                <button @click="open = !open; markAsRead()" class="relative p-2 focus:outline-none">
                    <svg class="w-6 h-6 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"></path>
                    </svg>
                    
                    <!-- Red Badge -->
                    <template x-if="unreadCount > 0">
                        <span class="absolute top-0 right-0 bg-red-500 text-white text-xs font-bold w-5 h-5 rounded-full flex items-center justify-center animate-bounce" 
                              x-text="unreadCount"></span>
                    </template>
                </button>

                <!-- Dropdown Menu -->
                <div x-show="open" @click.away="open = false" x-cloak
                     class="absolute right-0 mt-2 w-64 bg-white border rounded shadow-xl z-50 overflow-hidden">
                    <div class="p-2 border-b font-bold bg-gray-50 text-sm">Notifications</div>
                    <div class="max-h-64 overflow-y-auto">
                        <template x-if="notifications.length === 0">
                            <div class="p-4 text-center text-gray-500 text-sm">No new notifications</div>
                        </template>
                        <template x-for="note in notifications" :key="note.id">
                            <div class="p-3 border-b hover:bg-blue-50 text-sm">
                                <span class="block font-bold text-gray-800" x-text="note.title"></span>
                                <span class="block text-gray-600" x-text="note.message"></span>
                                <span class="block text-xs text-gray-400 mt-1" x-text="note.time"></span>
                            </div>
                        </template>
                    </div>
                </div>

            </div>
        </div>
        
        <!-- Tool to simulate incoming notifications -->
        <div class="mt-8 p-4 bg-white rounded shadow text-center">
            <p class="mb-4 text-gray-600">Waiting for live notifications...</p>
            <button @click="simulateIncoming()" class="bg-blue-500 text-white px-4 py-2 rounded text-sm hover:bg-blue-600">
                Simulate Server Push
            </button>
        </div>
    </div>

    <!-- Logic -->
    <script>
        function notificationApp() {
            return {
                notifications: [],
                unreadCount: 0,
                socket: null,

                init() {
                    // In a real app, this connects to your server
                    // this.socket = new WebSocket("wss://yourserver.com");
                    // this.socket.onmessage = (e) => this.handleMessage(e);
                },

                handleMessage(event) {
                    const data = JSON.parse(event.data);
                    
                    if(data.type === &#039;notification') {
                        // Add to top of list
                        this.notifications.unshift(data.payload);
                        this.unreadCount++;
                        
                        // Optional: Show a browser toast/popup here
                    }
                },

                markAsRead() {
                    this.unreadCount = 0;
                    // In a real app, send an API request to mark as read in the DB
                },

                // Fake a server push for testing
                simulateIncoming() {
                    const fakeEvent = {
                        data: JSON.stringify({
                            type: &#039;notification',
                            payload: {
                                id: Date.now(),
                                title: "System Alert",
                                message: "Your report has finished generating.",
                                time: new Date().toLocaleTimeString()
                            }
                        })
                    };
                    this.handleMessage(fakeEvent);
                }
            }
        }
    </script>
</body>
</html>

8. Server-Side Push Architecture

How does the WebSocket server know when to push a notification? Often, a standard HTTP API or a background worker triggers the event. For example, a user likes a post via an HTTP POST request. The PHP backend processes the "like", saves it to the database, and then uses a system like Redis Pub/Sub to whisper to the WebSocket Server: "Hey, User B just got a like. Push a notification to their socket connection."

9. Transient Toasts

While the dropdown stores notifications, you often want a popup notification that appears and fades away. You can easily add this to the handleMessage function using a library like Toastify.js or building an Alpine component that setTimeouts itself out of existence after 3 seconds.

10. Best Practices

  • Persistent Storage: WebSockets only handle the *delivery* of the notification in real-time. You must still save the notification to your MySQL database. If a user is completely offline, they need to see the notification the next time they log in.
  • Limit UI Arrays: If a user receives 1,000 notifications, storing them all in the Alpine array might lag the browser. Keep the UI array limited to the last 20 items.

11. Common Mistakes

  • Relying solely on WebSockets: If the connection drops for 5 seconds and a notification fires on the server during that time, the user misses it. Always fetch initial unread notifications via a standard HTTP API request on page load, and use WebSockets only for *new* alerts.

12. Mini Exercises

  1. 1. Copy the code from Section 7 into a file.
  1. 2. Click the "Simulate Server Push" button multiple times.
  1. 3. Watch the red badge counter increase and animate. Click the Bell icon to see the dropdown menu populate, and watch the counter reset to 0.

13. Coding Challenges

Challenge 1: Add a new property isNew: true to the notification payload. Update the Alpine template so that items with isNew: true have a faint blue background (bg-blue-50), and when markAsRead() is clicked, loop through the array and set all isNew flags to false so the backgrounds turn white.

14. MCQs with Answers

Question 1

In a robust notification system, where should notifications be stored permanently?

Question 2

What is the primary purpose of the WebSockets in a notification system?

15. Interview Questions

  • Q: Explain how a standard HTTP API (like a user submitting a comment) triggers a WebSocket push notification to a different user.
  • Q: Why must you fetch unread notifications via a REST API on initial page load, even if you are using WebSockets for live notifications?

16. FAQs

Q: Can WebSockets push notifications to the user even when the browser is completely closed? A: No. WebSockets require an active browser tab. If you want to push notifications to a phone or browser when the app is closed, you must use Push API and Service Workers (a different browser technology).

17. Summary

In Chapter 14, we built a dynamic Real-Time Notification system. Using WebSockets to deliver the data payload instantly and Alpine.js to reactively update the UI, we created an engaging user experience with an updating badge counter and a dropdown feed.

18. Next Chapter Recommendation

We've sent text and notifications, but WebSockets are incredibly fast—fast enough to stream rapid data. Proceed to Chapter 15: Live Dashboard and Data Streaming to learn how to update charts and graphs in real-time.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·