Instant messaging app made with React, Typescript, Node & Socket.io 🦜
Tags: react, node, typescript, webdev
Published: 2021-01-18 09:58:01

Hello everyone, I am back on Dev.to today to share another of my project! 

We all know how important it is to practice regularly in order to improve our development skills. As I am getting more confident, I try to build more complex and robust applications.

This last project was a lot of fun to build. It took me almost a month to deploy it (I mainly work after school hours).

Enjoy reading 😇

#Table of contents
<ol>
<li><a href="#intro">Project Introduction</a></li>
<li><a href="#features">Features</a></li>
<li><a href="#tech">Tech Stack</a></li>
<li><a href="#design">Wireframe & Design</a></li>
<li><a href="#data">Data modeling & API routing</a></li>
<li><a href="#project">Project Organization</a></li>
<li><a href="#frontend">Sprint 01: Frontend</a></li>
<li><a href="#backend">Sprint 02: Backend</a></li>
<li><a href="#deploy">Sprint 03: Fix & Deploy</a></li>
<li><a href="#conclusion">Conclusion</a></li>
</ol>

<h1 id="intro">Project Introduction 👋</h1>
I am glad to introduce [GroupChat](https://kf-groupchat.com) 🥳

This challenge's wireframes are provided by [devchallenges](https://devchallenges.io/) which offers many cool ideas of projects to build and practice. Take a look if you are missing inspiration!

Ok, let's talk about [GroupChat](https://kf-groupchat.com), it is an instant messaging app that allows users to create channels and chat with people interested in a particular topic.

Sounds simple ? Well, I would not say that it was "complicated" but it is always challenging to try something new.

It was the first time I have worked with [socket.io](https://socket.io/) and it was also my first medium-sized project built with [TypeScript](https://www.typescriptlang.org/).

<h1 id="features">Features 🌟</h1>
✅ Custom Authentication (Email - Password)
✅ Login as guest (limited access)
✅ Random Avatar / Profile image upload
✅ Authorization (json web tokens)
✅ End to End input validation
✅ Create and join channels
✅ Instant messaging
✅ Bug report
✅ Mobile friendly

<h1 id="tech">Tech Stack ⚛️</h1>

Once again, I went for my best friend the [MERN](https://medium.com/@blockchain_simplified/what-is-mern-stack-9c867dbad302) stack which includes:
➡️ [MongoDB](https://www.mongodb.com/)
➡️ [Express](https://expressjs.com/fr/)
➡️ [React](https://fr.reactjs.org/docs/getting-started.html)
➡️ [Node](https://nodejs.org/en/)

In addition to above technologies, I worked with [TypeScript](https://www.typescriptlang.org/) to improve the robustness of my code and with [Redux](https://redux.js.org/) to manage the app state.

I should also mention [socket.io](https://socket.io/) that enables real-time, bidirectional and event-based communication between the browser and the server.

For deployment, an easy and efficient way is to host the frontend on [Netlify](https://app.netlify.com/) and backend with [Heroku](https://dashboard.heroku.com/).

Here is a list of tools I usually work with to enhance my programming experience:
➡️ OS: [MacOS](https://www.apple.com/)
➡️ Terminal: [iterm2](https://iterm2.com/)
➡️ IDE:[VSCode](https://code.visualstudio.com/)
➡️ Versioning: [Git](https://git-scm.com/)
➡️ Package Manager: [NPM](https://www.npmjs.com/)
➡️ Project Organization: [Notion](https://www.notion.so/)

<h1 id="design">Wireframe & Design 🎨</h1>
To be honest, I don't have too much pleasure designing a product's UI. So, I decided to work with existing wireframes and focus on the code instead.

As I said already, I inspired from [devchallenges](https://devchallenges.io/). Quick overview:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/drk1uev9hrmqp7c59qgg.png)

<h1 id="data">Data modeling & API routing 💾</h1>

Database design and API routing are important steps. Make sure you have an action plan before starting coding, or it will be a disaster 🧨

Here is a simple data model made with [Lucidchart](https://lucid.app/lucidchart/):

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/39atp64v3t9iakll4bam.png)

It is indeed simple, but it is enough for this project.

As you could guess, we are building a REST API with Node/Express which involves HTTP requests. 

Let's imagine our routes:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/itzmx6i1c9kmndv52vx8.png)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/df92mve2y20ju45duij3.png)

*Note: API doc made with [Apiary](https://apiary.io/)*

<h1 id="project">Project Organization 🗂️</h1>

I love when everything is clean and well-organized. Here is the folder structure I decided to work with:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/xrlls8w9wokvbrzns7fn.png)

Simple, clean and consistent 💫

In order to keep track of my progress, I made myself a task board on [Trello](https://trello.com) 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/zgeycpgzu617bu7m7afb.png)

Before you head over to the next step, I will briefly talk about the [Git](https://git-scm.com/) workflow.

As I was the only one working on this project, [GitHub flow](https://guides.github.com/introduction/flow/) worked just fine.

Every addition to the code has a dedicated branch and the code is reviewed (by myself only...) for each new PR.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/d1jcb6zrb1vg474da56b.png)

*Note: Around 180 commits and 40 branches were created* 

<h1 id="frontend">Sprint 01: Setup & Frontend 🖥</h1>

It is always so exciting to start coding, this is my favorite part of the process.

I would say that the first week was the easiest.I began with setting up both Frontend and Backend which means install dependencies, environment variables, CSS reset, create a database, ...

Once setup is done, I built every single component that should appear on the screen and made sure they are mobile friendly (flex, media queries, ...).

Speaking of components and UI, here is a simple example:
```typescript
// TopBar/index.tsx
import React from 'react';
import { IconButton } from '@material-ui/core';
import MenuIcon from '@material-ui/icons/Menu';

// Local Imports
import styles from './styles.module.scss';

type Props = {
  title?: String;
  menuClick: () => void;
};

const TopBar: React.FC<Props> = props => {
  return (
    <div className={styles.container}>
      <div className={styles.wrapper}>
        <IconButton className={styles.iconButton} onClick={props.menuClick}>
          <MenuIcon className={styles.menu} fontSize="large" />
        </IconButton>
        <h2 className={styles.title}>{props.title}</h2>
      </div>
    </div>
  );
};

export default TopBar;
``` 
```scss
// TopBar/styles.module.scss
.container {
  width: 100%;
  height: 60px;
  box-shadow: 0px 4px 4px rgba($color: #000, $alpha: 0.2);
  display: flex;
  align-items: center;
  justify-content: center;
}

.wrapper {
  width: 95%;
  display: flex;
  align-items: center;
}

.title {
  font-size: 18px;
}

.iconButton {
  display: none !important;
  @media (max-width: 767px) {
    display: inline-block !important;
  }
}

.menu {
  color: #e0e0e0;
}

```
Nothing fancy, it is a basic implementation of [TypeScript](https://www.typescriptlang.org/) (I still have a lot to learn) and [SCSS](https://sass-lang.com/) modules. 

I like [SCSS](https://sass-lang.com/) a lot and wrote an introduction for anyone interested:

{% post https://dev.to/killianfrappartdev/scss-introduction-8pp %}

You can also notice that some components (icons, inputs, ...) are imported from my favorite UI library out there: [Material UI](https://material-ui.com/).

Speaking of [TypeScript](https://www.typescriptlang.org/), the first days were really painful and tiring but in the end, it appeared to be extremely easy to catch bugs during development.

If you find struggling with [TypeScript](https://www.typescriptlang.org/), you may want to have a look to this post:

{% post https://dev.to/killianfrappartdev/get-started-with-typescript-today-1beo %}

I am not so familiar with [Redux](https://redux.js.org/) and I had to spend some time reading the doc in order to make it right.

Another cool tool I worked with is [Formik](https://formik.org/) which manages form validation in a smart and simple way.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/y1yh93rramcpe39bi2d3.gif)

```typescript
// Login/index.tsx

import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import axios from 'axios';
import { TextField, FormControlLabel, Checkbox, Snackbar, CircularProgress } from '@material-ui/core';
import MuiAlert from '@material-ui/lab/Alert';
import { useDispatch } from 'react-redux';
import { useFormik } from 'formik';
import * as Yup from 'yup';
import { useHistory } from 'react-router-dom';

// Local Imports
import logo from '../../../assets/gc-logo-symbol-nobg.png';
import CustomButton from '../../Shared/CustomButton/index';
import styles from './styles.module.scss';

type Props = {};

type SnackData = {
  open: boolean;
  message: string | null;
};

const Login: React.FC<Props> = props => {
  const dispatch = useDispatch();
  const history = useHistory();

  const [isLoading, setIsLoading] = useState(false);
  const [checked, setChecked] = useState(false);
  const [snack, setSnack] = useState<SnackData>({ open: false, message: null });

  // Async Requests
  const loginSubmit = async (checked: boolean, email: string, password: string) => {
    setIsLoading(true);
    let response;
    try {
      response = await axios.post(`${process.env.REACT_APP_SERVER_URL}/users/login`, {
        checked,
        email: email.toLowerCase(),
        password: password.toLowerCase()
      });
    } catch (error) {
      console.log('[ERROR][AUTH][LOGIN]: ', error);
      setIsLoading(false);
      return;
    }
    if (!response.data.access) {
      setSnack({ open: true, message: response.data.message });
      setIsLoading(false);
      return;
    }
    if (checked) {
      localStorage.setItem('userData', JSON.stringify({ id: response.data.user.id, token: response.data.user.token }));
    }
    dispatch({ type: 'LOGIN', payload: { ...response.data.user } });
    history.push('');
    setIsLoading(false);
  };

  const formik = useFormik({
    initialValues: {
      email: '',
      password: ''
    },
    validationSchema: Yup.object({
      email: Yup.string().email('Invalid email address').required('Required'),
      password: Yup.string()
        .min(6, 'Must be 6 characters at least')
        .required('Required')
        .max(20, 'Can not exceed 20 characters')
    }),
    onSubmit: values => loginSubmit(checked, values.email, values.password)
  });

  return (
    <div className={styles.container}>
      <Link to="/">
        <img className={styles.logo} alt="logo" src={logo} />
      </Link>
      <form className={styles.form}>
        <TextField
          className={styles.input}
          id="email"
          label="Email"
          variant="outlined"
          type="text"
          helperText={formik.touched.email && formik.errors.email}
          error={formik.touched.email && !!formik.errors.email}
          {...formik.getFieldProps('email')}
        />
        <TextField
          className={styles.input}
          id="password"
          label="Password"
          variant="outlined"
          type="password"
          {...formik.getFieldProps('password')}
          helperText={formik.touched.password && formik.errors.password}
          error={formik.touched.password && !!formik.errors.password}
        />
        <FormControlLabel
          className={styles.check}
          control={
            <Checkbox checked={checked} onChange={() => setChecked(prev => !prev)} name="checked" color="primary" />
          }
          label="Remember me"
        />
        <CustomButton type="submit" onClick={formik.handleSubmit} isPurple title="Login" small={false} />
      </form>
      <Link to="/signup">
        <p className={styles.guest}>Don't have an account? Sign Up</p>
      </Link>
      {isLoading && <CircularProgress />}
      <Snackbar open={snack.open} onClose={() => setSnack({ open: false, message: null })} autoHideDuration={5000}>
        <MuiAlert variant="filled" onClose={() => setSnack({ open: false, message: null })} severity="error">
          {snack.message}
        </MuiAlert>
      </Snackbar>
    </div>
  );
};

export default Login;
```
<h1 id="backend">Sprint 02: Backend 📊</h1>
The server is pretty straightforward, it is a classic representation of what a Node/Express server should look like.

I created [mongoose](https://mongoosejs.com/) models and their associations.

Then, I registered routes and connected corresponding controllers. Inside my controllers, you can find classic CRUD operations and some custom functions.

Thanks to [JWT](https://www.npmjs.com/package/jsonwebtoken), it was possible to work on the security, which was an important point for me.

Now comes the coolest feature of this app, bidirectional communication or maybe should I say [socket.io](https://socket.io/) ?

Here is an example:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/6qyqvd0tokd9wpwtosfc.gif)

```javascript
// app.js - Server side

// Establish a connection
io.on('connection', socket => {
  // New user
  socket.on('new user', uid => {
    userList.push(new User(uid, socket.id));
  });

  // Join group
  socket.on('join group', (uid, gid) => {
    for (let i = 0; i < userList.length; i++) {
      if (socket.id === userList[i].sid) userList[i].gid = gid;
    }
  });

  // New group
  socket.on('create group', (uid, title) => {
    io.emit('fetch group');
  });

  // New message
  socket.on('message', (uid, gid) => {
    for (const user of userList) {
      if (gid === user.gid) io.to(user.sid).emit('fetch messages', gid);
    }
  });

  // Close connection
  socket.on('disconnect', () => {
    for (let i = 0; i < userList.length; i++) {
      if (socket.id === userList[i].sid) userList.splice(i, 1);
    }
  });
});

// AppView/index.tsx - Client side

  useEffect(() => {
    const socket = socketIOClient(process.env.REACT_APP_SOCKET_URL!, { transports: ['websocket'] });
    socket.emit('new user', userData.id);
    socket.on('fetch messages', (id: string) => fetchMessages(id));
    socket.on('fetch group', fetchGroups);
    setSocket(socket);
    fetchGroups();
  }, []);
```

I discovered [express-validator](https://express-validator.github.io/docs/index.html) and it helped a lot to provide input validation on the server side. With no doubt, a library that I am going to use again.

<h1 id="deploy">Sprint 03: Fix & Deploy ☁️</h1>

Alright, the app is looking good and features are working fine. It is time to finish this portfolio project and start a new one.

I am not a pro of cloud solution and complex CI/CD methods so I will satisfy with a free hosting service.

[Heroku](https://dashboard.heroku.com/) has a free solution that works fine for the backend. 5 minutes after my node server was uploaded, it was running independantly. Awesome 🌈

I experienced some security issues with the client. Usually, everything is ok when I send my [React](https://fr.reactjs.org/docs/getting-started.html) app to [Netlify](https://app.netlify.com/teams/killianfrappartdev/overview) via [GitHub](https://github.com/) but not this time.

Many of my friends could not reach the given URL because of some "security reasons" and I had to buy a domain name to fix it. No big deal here, 15 euros for a year does not seem overpriced.

Finally, images uploaded by users are stored on my [Cloudinary](https://cloudinary.com/) account via their public API.

<h1 id="conclusion">Conclusion ✅</h1>
Once again, I enjoyed myself so much working on this project and learned a lot.

It was a pleasure to share the process with you and I can't wait to hear your tips and feedbacks.

This project is nothing more than a portfolio project and there is no "production" intention behind. However, the code is open sourced on GitHub, feel free to do whatever you want with it.

{% github killianfrappartdev/groupchat no-readme %}

I know that there is a lot to improve in term of code quality, security, optimization, ... Whatever, I managed to finish this and the result looks pretty cool and I hope you like it as well.

Live version: [GroupChat](https://kf-groupchat.com)

Never stop challenging yourself 🚀