Chapter 20 - Build Real-Time Chat App: WebSockets, Firebase, and Authentication Made Easy

Real-time chat app using WebSockets and Firebase. Includes authentication, message storage, and instant updates. Key features: Express server, Socket.io for real-time communication, Firebase for authentication and data persistence.

Chapter 20 - Build Real-Time Chat App: WebSockets, Firebase, and Authentication Made Easy

Ready to build a real-time chat app? Let’s dive in! This guide will walk you through creating a chat application with all the bells and whistles - authentication, message storage, and instant updates. We’ll use WebSockets and Firebase to make it happen.

First things first, let’s set up our project. Create a new directory and initialize a Node.js project:

mkdir chat-app
cd chat-app
npm init -y

Now, let’s install the necessary dependencies:

npm install express socket.io firebase

Great! We’ve got our foundation. Let’s start by creating a basic Express server:

const express = require('express');
const app = express();
const http = require('http').createServer(app);
const io = require('socket.io')(http);

app.get('/', (req, res) => {
  res.sendFile(__dirname + '/index.html');
});

http.listen(3000, () => {
  console.log('Server running on port 3000');
});

This sets up a simple server that serves our HTML file. Speaking of which, let’s create a basic HTML structure for our chat app:

<!DOCTYPE html>
<html>
<head>
  <title>Real-Time Chat App</title>
</head>
<body>
  <ul id="messages"></ul>
  <form id="chat-form">
    <input id="chat-input" type="text" autocomplete="off" />
    <button>Send</button>
  </form>
  <script src="/socket.io/socket.io.js"></script>
  <script src="client.js"></script>
</body>
</html>

Now, let’s add some client-side JavaScript to handle sending and receiving messages:

const socket = io();

const chatForm = document.getElementById('chat-form');
const chatInput = document.getElementById('chat-input');
const messages = document.getElementById('messages');

chatForm.addEventListener('submit', (e) => {
  e.preventDefault();
  if (chatInput.value) {
    socket.emit('chat message', chatInput.value);
    chatInput.value = '';
  }
});

socket.on('chat message', (msg) => {
  const li = document.createElement('li');
  li.textContent = msg;
  messages.appendChild(li);
});

This code sets up event listeners for sending messages and displays received messages in the chat window. Now, let’s update our server to handle these messages:

io.on('connection', (socket) => {
  console.log('A user connected');

  socket.on('chat message', (msg) => {
    io.emit('chat message', msg);
  });

  socket.on('disconnect', () => {
    console.log('User disconnected');
  });
});

At this point, we have a basic real-time chat application! But let’s make it more robust by adding authentication and message persistence with Firebase.

First, we need to set up a Firebase project. Head over to the Firebase Console, create a new project, and grab your configuration details. Then, let’s update our server code to initialize Firebase:

const firebase = require('firebase/app');
require('firebase/auth');
require('firebase/firestore');

const firebaseConfig = {
  // Your Firebase configuration object
};

firebase.initializeApp(firebaseConfig);
const db = firebase.firestore();

Now, let’s add authentication to our app. We’ll use Firebase Authentication for this. Update the HTML to include a login form:

<div id="login-form">
  <input id="email" type="email" placeholder="Email" />
  <input id="password" type="password" placeholder="Password" />
  <button id="login">Login</button>
  <button id="signup">Sign Up</button>
</div>

And add the corresponding JavaScript:

const loginForm = document.getElementById('login-form');
const emailInput = document.getElementById('email');
const passwordInput = document.getElementById('password');
const loginButton = document.getElementById('login');
const signupButton = document.getElementById('signup');

loginButton.addEventListener('click', () => {
  const email = emailInput.value;
  const password = passwordInput.value;
  firebase.auth().signInWithEmailAndPassword(email, password)
    .then((userCredential) => {
      console.log('Logged in:', userCredential.user);
      loginForm.style.display = 'none';
      chatForm.style.display = 'block';
    })
    .catch((error) => {
      console.error('Login error:', error);
    });
});

signupButton.addEventListener('click', () => {
  const email = emailInput.value;
  const password = passwordInput.value;
  firebase.auth().createUserWithEmailAndPassword(email, password)
    .then((userCredential) => {
      console.log('Signed up:', userCredential.user);
      loginForm.style.display = 'none';
      chatForm.style.display = 'block';
    })
    .catch((error) => {
      console.error('Signup error:', error);
    });
});

Great! Now we have authentication in place. Let’s update our message handling to store messages in Firestore and retrieve them when a user connects:

io.on('connection', (socket) => {
  console.log('A user connected');

  // Fetch and send existing messages
  db.collection('messages').orderBy('timestamp').get()
    .then((snapshot) => {
      snapshot.forEach((doc) => {
        socket.emit('chat message', doc.data().text);
      });
    });

  socket.on('chat message', (msg) => {
    // Store the message in Firestore
    db.collection('messages').add({
      text: msg,
      timestamp: firebase.firestore.FieldValue.serverTimestamp()
    });

    // Broadcast the message to all connected clients
    io.emit('chat message', msg);
  });

  socket.on('disconnect', () => {
    console.log('User disconnected');
  });
});

Now our chat app stores messages persistently and loads them when a user connects. Pretty cool, right?

Let’s add one more feature: real-time updates when new messages are added to Firestore. Update the client-side code:

db.collection('messages')
  .orderBy('timestamp')
  .onSnapshot((snapshot) => {
    snapshot.docChanges().forEach((change) => {
      if (change.type === 'added') {
        const li = document.createElement('li');
        li.textContent = change.doc.data().text;
        messages.appendChild(li);
      }
    });
  });

This code listens for changes in the Firestore ‘messages’ collection and updates the chat window in real-time.

And there you have it! We’ve built a real-time chat application with authentication, message persistence, and real-time updates using WebSockets and Firebase. It’s been quite a journey, hasn’t it?

Of course, there’s always room for improvement. You could add features like user profiles, private messaging, or even file sharing. The sky’s the limit!

Remember, building a chat app is more than just coding. It’s about creating a space for people to connect and communicate. As you continue to develop your app, always keep your users in mind. What features would make their experience better? How can you ensure their privacy and security?

I hope this guide has been helpful in getting you started with your real-time chat application. Happy coding, and may your chat app bring people closer together!