How to Build a Chat App

What you will get from this workshop

  1. A great starting app you can use to make your own private chat application
  2. A list of resources for further help and continued learning

About the project

In this workshop you will learn how to build and customize a private chat application

While building this app you will learn how to do the following:

Creating your account

  1. Go to https://www.qoom.io
  2. Click the Sign Up button
  3. Follow the instructions

HTML: Creating our Web App Layout

Copy the following HTML:

<body>
	<header>
		<h1>🔒 Private Chat </h1>
	</header>
	<main>
		<div class='you'>Welcome!</div>
		<div class='me'></div>
	</main>
	<footer>
		<textarea placeholder='Type your message here...'></textarea>
		<button>Send</button>
	</footer>
</body>

In this HTML page we used the Semantic elements to describe our layout. The main section will hold the messages and the footer section will be where the user enters their messages.

Exercise:

Adding a title element so that your app has an title in the tab:

<head>
	<title>🔒 Private Chat</title>
</head>

CSS: Styling our Web App

Now lets use the Flex and Grid Systems to make everything lay out nicely

<head>
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<link href="https://fonts.googleapis.com/css2?family=Inconsolata&family=Fredericka+the+Great&family=Mali&display=swap" rel="stylesheet">
	<title>Private Chat</title>
	<style>
		* {
			box-sizing:border-box;
			margin:0;
			padding:0;
		}
		body {
			display:grid;
			grid-template-rows: 64px auto 100px;
			grid-template-columns: auto 600px auto;
			width:100vw;
			height:100vh;
			overflow: hidden;
			font-family: system-ui, san-serif;
			background-color: #fcd411;
		}
		header {
			grid-column-start: 2;
			grid-column-end: 3;
			text-align:center;
			display:flex;
			align-items:center;
			justify-content:center;
			background-color: #2e2e30;
			color: #fcd411;
		}
		header > h1 {
			font-family: 'Fredericka the Great', cursive;
			font-size: 2em;
		}
		main {
			grid-column-start: 2;
			grid-column-end: 3;
			list-style:none;
			padding: 24px;
			display:flex;
			flex-direction:column;
			align-items:center;
			justify-content:flex-start;	
			background-color: white;
			overflow-y: auto;
			overflow-x: hidden;
		}
		main > div {
			width: 100%;
			font-size: 18px;
			margin: 8px 0;
		}
		main > div.me {
			font-family: 'Mali', cursive;
			text-align: right;
		}
		main > div.you {
			font-family: 'Inconsolata', monospace;
			text-align:left;
		}
		footer {
			grid-column-start: 2;
			grid-column-end: 3;
			display:grid;
			grid-template-columns: auto 80px;
			background-color: #fcd411;
		}
		footer > textarea {
			font-family: 'Mali', cursive;
			font-size: 18px;
			padding: 8px 16px;
			resize: none;
		}
		footer > button {
			font-family: 'Fredericka the Great', cursive;
			font-size: 20px;
			background-color: #fcd411;
		}
		@media only screen and (max-width: 768px) {
			body {
				height: calc(100vh - 114px);
				grid-template-columns: auto 100% auto;
			}
		}
	</style>
</head>

Exercise:


Javascript: Creating a socket connection with other browsers

Since we are using sockets we will need to import the library that makes this easy. Please add the following code at the bottom of the head element:

<script src='/libs/socketio.js'></script>

Then we can initialize the socket connections using the following function:

<script src='/libs/socketio.js'></script>
<script type='module'>
	
	// Creating a socket variable to talk to our server with
	const socket = io('/tunnel');
	
	async function initializeSockets() {
	
		// Telling our webserver to create a `chat` channel to talk with other browsers with
		const res = await fetch('/tunnel/register', {
			method: 'POST',
			headers: {
				'Content-Type': 'application/json'
			},
			body: JSON.stringify({
				events: ['chat']
			})
		});
		
		// Waiting for our server to respond
		const resp = await res.json();
	
		// Handle any errors from our server
		if(resp.error) return alert(resp.error)
		
		// Define what to do when someone sends us a message
		socket.on('chatresponse', function(data) {
			
		});
		
		// Define what to do when someone joins the chat room
		socket.on('userconnected', (socketId) => {
			console.log('userconnected: ' + socketId);
		});
		
		// Define what to do when someone leaves the chat room
		socket.on('userdisconnected', (socketId) => {
			console.log('disconnected: ' + socketId);
		})

	}
	
	// Initializing the socket connection
	initializeSockets();
</script>

Exercise:


Javascript: Asking for the name of the person joining the chat

In the script tag created above add the following code at the top:

// Checks `localStorage` if the person name is saved
let name = localStorage.getItem('name');

// If no name is found, ask the user for their name and save it
if(!name) {
	name = prompt("What is your name?")
	localStorage.setItem('name', name);
}

Exercise:


Javascript: Displaying the chat messages

At the bottom of the script tag created above add the following code:

// Putting all the elements into variables
const $button = document.querySelector('button');
const $textarea = document.querySelector('textarea');
const $main = document.querySelector('main');

function sendMessage() {
	// Letting all the other browsers know what you typed
	socket.emit('chat', {name, text: $textarea.value});
	
	// Displaying the message into a new div and adding it to the main element
	const div = document.createElement('div');
	div.className = 'me';
	div.innerHTML = `<b>${name}:</b> <span>${$textarea.value}</span>`;
	$main.appendChild(div);
	$textarea.value = '';
	
	// Automatically scroll to the bottom of the message
	$main.scrollTo(0,$main.scrollHeight);
}

// Sending the message when the button is clicked
$button.addEventListener('click', sendMessage);

// Moving the cursor to the textbox so the user can start typing
$textarea.focus();

Finally we have to display the message that someone sent to us:

socket.on('chatresponse', function(data) {
	const div = document.createElement('div');
	div.className = 'you';
	div.innerHTML = `<b>${data.data.name}:</b> <span>${data.data.text}</span>`;
	$main.appendChild(div);
	$main.scrollTo(0,$main.scrollHeight);
});

Exercise:


The final product

<!DOCTYPE html>
<html>
	<head>
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<link href="https://fonts.googleapis.com/css2?family=Inconsolata&family=Fredericka+the+Great&family=Mali&display=swap" rel="stylesheet">
		<title>Private Chat</title>
		<style>
			* {
				box-sizing:border-box;
				margin:0;
				padding:0;
			}
			body {
				display:grid;
				grid-template-rows: 64px auto 100px;
				grid-template-columns: auto 600px auto;
				width:100vw;
				height:100vh;
				overflow: hidden;
				font-family: system-ui, san-serif;
				background-color: #fcd411;
			}
			header {
				grid-column-start: 2;
				grid-column-end: 3;
				text-align:center;
				display:flex;
				align-items:center;
				justify-content:center;
				background-color: #2e2e30;
				color: #fcd411;
			}
			header > h1 {
				font-family: 'Fredericka the Great', cursive;
				font-size: 2em;
			}
			main {
				grid-column-start: 2;
				grid-column-end: 3;
				list-style:none;
				padding: 24px;
				display:flex;
				flex-direction:column;
				align-items:center;
				justify-content:flex-start;	
				background-color: white;
				overflow-y: auto;
				overflow-x: hidden;
			}
			main > div {
				width: 100%;
				font-size: 18px;
				margin: 8px 0;
			}
			main > div.me {
				font-family: 'Mali', cursive;
				text-align: right;
			}
			main > div.you {
				font-family: 'Inconsolata', monospace;
				text-align:left;
			}
			footer {
				grid-column-start: 2;
				grid-column-end: 3;
				display:grid;
				grid-template-columns: auto 80px;
				background-color: #fcd411;
			}
			footer > textarea {
				font-family: 'Mali', cursive;
				font-size: 18px;
				padding: 8px 16px;
				resize: none;
			}
			footer > button {
				font-family: 'Fredericka the Great', cursive;
				font-size: 20px;
				background-color: #fcd411;
			}
			@media only screen and (max-width: 768px) {
				body {
					height: calc(100vh - 114px);
					grid-template-columns: auto 100% auto;
				}
			}
		</style>
		<script src='/libs/socketio.js'></script>
		<script type='module'>
			// GET THE PERSON'S NAME
			
			let name = localStorage.getItem('name');
			if(!name) {
				name = prompt("What is your name?")
				localStorage.setItem('name', name);
			}
			
			
			// REGISTER COMMUNICATIONS
			const socket = io('/tunnel');
			async function initializeSockets() {
				const res = await fetch('/tunnel/register', {
					method: 'POST',
					headers: {
						'Content-Type': 'application/json'
					},
					body: JSON.stringify({
						events: ['chat']
					})
				});
				
				const resp = await res.json();
			
				if(resp.error) return alert(resp.error)
				
			
				socket.on('chatresponse', function(data) {
					const div = document.createElement('div');
					div.className = 'you';
					div.innerHTML = `<b>${data.data.name}:</b> <span>${data.data.text}</span>`;
					$main.appendChild(div);
					$main.scrollTo(0,$main.scrollHeight);
				});
				
				socket.on('userconnected', (socketId) => {
					console.log('userconnected: ' + socketId);
				});
				
				socket.on('userdisconnected', (socketId) => {
					console.log('disconnected: ' + socketId);
				})
	
			}
			initializeSockets();
			
			// SENDING CHAT MESSAGE
			const $button = document.querySelector('button');
			const $textarea = document.querySelector('textarea');
			const $main = document.querySelector('main');
			
			function sendMessage() {
				socket.emit('chat', {name, text: $textarea.value});
				
				const div = document.createElement('div');
				div.className = 'me';
				div.innerHTML = `<b>${name}:</b> <span>${$textarea.value}</span>`;
				$main.appendChild(div);
				$textarea.value = '';
				$main.scrollTo(0,$main.scrollHeight);
			}

			$button.addEventListener('click', sendMessage);
			$textarea.focus();
		</script>
	</head>
	<body>
		<header>
			<h1>🔒 Private Chat </h1>
		</header>
		<main>
			<div class='you'>Welcome!</div>
			<div class='me'></div>
		</main>
		<footer>
			<textarea placeholder='Type your message here...'></textarea>
			<button>Send</button>
		</footer>
	</body>
</html>

List of Resources

HTML References

  1. HTML Elements:
    https://developer.mozilla.org/en-US/docs/Web/HTML/Element

CSS References

  1. Flex Box:
    https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox
  2. Grid Layout:
    https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Basic_Concepts_of_Grid_Layout

Javascript References

  1. Socket IO
    https://socket.io/