Developing A Chat App With Express, Socket.IO, and Flutter Web
By: I. Berat Kaya
Tags: Nodejs, Chat, Socketio, Flutter, Expressjs

Socket.IO is a widely used JavaScript library that enables bi-directional communication between a client and a server. In this article, we will use it to build a chat app with Express and Flutter.

Setting Up The Project

The Project Folder Hierarchy

Make sure that Flutter and Node.js are installed. First we will create our Express project. We can use Express application generator (npx expresss-generator) to create our project. You can delete the generated routes since we will only we serving our Flutter project. I prefer to write TypeScript instead of JavaScript, but it is optional. Then, we will create a folder named client and put our newly created Flutter project inside client. Our project structure should look like similar to the image in the left. Make sure to be on Flutter’s Beta Channel to develop web apps. You can use FVM to use both the stable channel and beta channel and not have to download it every time you switch a channel. Then we need to add Socket.io as a dependency for both Express and Flutter. For Express, we will use npm and write npm i socket.io in the terminal in our Express project directory. For Flutter, we will use the socket_io_client plugin.

Socket.io Server

We need to create an Express server and connect it to Socket.IO. This can be easily done with these 3 lines:

import express from "express";

import socketio from "socket.io";

import http from "http"; const app = express();

const server = http.createServer(app);

const io = socketio(server);

Now we an Express server with Socket.IO connected. We need to send our Flutter Web project as a response when a request is made to our server. For that we will send the built web project as a response. I created a directory named public in the Flutter project and copied the built web project into that directory. The name of the directory is not important as long as that directory is sent as a response to the request made to Express. This can also be easily done with:

//Serve the static files created in the Flutter build

app.use(express.static(path.join(__dirname, "client", "public"))); //When a get request is made to the server, handle this request

app.get("/", (req, res) => {

//Send the index.html file in our Flutter project's build

res.sendFile(path.join(__dirname, "client",

"public", "index.html"));

});

Our server can send our Flutter Web project as a response and is ready to make Socket.IO connections. We will create multiple chat rooms and every user can either join a room or create a new room. Users will be able to send messages to everyone in that room. This can be easily accomplished with:

//Handle when a socket connection is made

io.on("connection", (socket) => {

//Handle when a socket makes a request to join a room

//Request a roomId and a username for who joined

socket.on("joinRoom", (roomId: string, username: string) => {

//Join the user to the roomId

socket.join(roomId);

//Send a message to everyone in that roomId that

//username has joined the room

io.to(roomId).emit("sendMessage", username +

" joined room " + roomId);

}); //Handle when a socket makes a request to send a message

//Request a message, a roomId, and a username

socket.on(

"sendMessage",

(message: string, roomId: string, username: string) => {

io.to(roomId).emit("sendMessage", message, username);

}

);

});

This is a simple way to join and create rooms and send messages in rooms. This is also not secure, since anyone with a socket connection can change roomId of the message they send. One simple solution would be to request passwords when sending a message from the socket to the room and checking that password. If the password is correct, emit that message. Since this is a simple application and every room is public with no passwords, security is not that important for this case.

Socket.IO Client

In our Flutter project’s Homepage, we will let the user enter a roomId and a username. Then we will join that user to the roomId. We will also listen for sendMessage events in case a user sends a message.

void initSocket() {

try {

//Connect the client to the socket

socket = IO.io('https://ibkchatapp.herokuapp.com/#/',

<String, dynamic>{

'transports': ['websocket'],

});

//Join the user with username to the room with roomId

socket.emit("joinRoom", [widget.roomId, widget.username]);

//Listen to the sendMessage events emitted from the socket

socket.on("sendMessage", (res) {

//If a message comes, add it to a List<Message> to

//display as a ListView in the UI

Message msg = Message(message: res[0], username: res[1]);

setState(() {

messages.add(msg);

});

});

} catch (e) {

print(e);

}

}

For sending a message we will emit a sendMessage event.

//Emit a new message. The message is stored in a

//TextEditingController

socket.emit("sendMessage",

[messageController.text, widget.roomId, widget.username]);

After styling our UI, the application is ready to be deployed. We will run flutter build web, and copy the output to our public directory.

Result

The Homepage

The Chat Screen

The application is deploy to Heroku and is available at ibkchatapp.herokuapp.com. The users can join or create a room and send a message that will be emitted to everyone in that room. The code of the application is available at github.com/iberatkaya/chat_app. One major benefit of Flutter is that this project can also be ran as an iOS or Android application. Fell free to clone the repo and try it out!