How to Build a Chat App
- 🧑💻 You will build this
- ▶️ Tutorial Video
- 🗂️ Source Files
What you will get from this workshop
- A great starting app you can use to make your own private chat application
- 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:
- HTML
- Mobile Layout ⇒ Creating web app that is laid out for a mobile device
- CSS
- Flex Box ⇒ More practice using flex box to make sure things are centered inside of elemets.
- Grid System ⇒ More practice using the grid system to make sure things are laided out aesthetically
- Javascript:
- Importing 3rd party libraries ⇒ Building off of tools built from others so that you can focus on building what you need
- Sockets Using sockets to communicate with other people using other browsers on other devices
Creating your account
- Go to https://www.qoom.io
- Click the
Sign Up
button - 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:
- Change the text in the
h1
element.
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:
- Change the fonts using Google Fonts https://fonts.google.com/
- Change the colors
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:
- Open up the webpage you created and open up
Dev Tools
by right clicking and selectingInspect
- Send the webpage to someone else
- See in the
console
when someone joins
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:
- Test it
- Go to the
storage
tab in theDev Tools
and see the data saved inlocalStorage
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:
- Remove the hard coded chat messages on top
- Test your chat application
- To create different rooms just add
?ROOM_NAME
to the end of the url.ROOM_NAME
is what ever you want to call it.- Send images or videos by just entering the
html
for that image or video. For example: -<img src="https://media.tenor.co/images/0872d90502401d2260bf8704ae77a2a9/tenor.gif">
will send a gif of a sleepy minion -<iframe width="560" height="315" src="https://www.youtube.com/embed/5uNujDrsEMQ" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
will send a youtube video
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
- HTML Elements:
https://developer.mozilla.org/en-US/docs/Web/HTML/Element
CSS References
- Flex Box:
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox - Grid Layout:
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Basic_Concepts_of_Grid_Layout
Javascript References
- Socket IO
https://socket.io/