Typescript Live Chat: Express and Socket.io Server Setup
How to do real-time chat with Typescript: Part 1 of 2

Typescript has become a great tool for live chat
If you decide to develop real-time communication for your application, Typescript has become a very attractive option to do so, with its type safe guarantees and the range of packages readily available to facilitate the solution.
This two-part series [Part 2 here] will document the entire process of setting up a bare-bones Typescript live chat solution using the latest cutting-edge tooling at our disposal within the open source ecosystem. The setup allows any number of clients to connect to the service and receive incoming messages in real time. The full solution discussed here is available on Github to browse and demo.
This article introduces the project and documents the development process of the backend Express server, whereas part two will document the front end React client. The final solution is a working live chat environment:

Why Develop Typescript Live Chat?
Not only does developing your own chat solutions give you total freedom with what features to integrate, privacy concerns are alleviated by knowing where your messages are being routed to and stored at; a stark contrast to adopting a third party service.
In addition, third parties will undoubtedly be routing your messages for data analysis, weighting neural networks for their AI efforts, and more. Leveraging your own solution opens you up to the same capabilities, boosting your raw data and keeping it private in the process.
An in-house chat solution is not only viable for small teams — or even individuals, it is also maintainable and expandable when developed with Typescript in conjunction with other great tools we’ll document here.
To achieve a real-time chat solution we will utilise the following technologies:
- Typescript Node Express server: Installation and setup will be documented, as well as how to run the server in
dev
mode, reloading the server when changes are made to our code - Typescript React App: The chat interface will be built in a Typescript Create React App project
- Socket.io: A lightweight tool to manage websocket connections between your backend (Express server) and frontend (React App), providing event based communication that we will utilise to emit messages to all the connected clients. Our Socket.io server and client code will be structured in classes for exemplary modularity.
- RxJS: We will also touch on the asynchronous event handling solution of our time, RxJS.
The backbone of the service lies in the Express server, that will listen to incoming Socket.io events and emit chat messages to connected clients. The following sections will walk through the process of installation and setup of this server.
Express Server Setup
The purpose of the Express server is to manage websocket connections, and to route incoming messages to all connected clients.
Your project folder structure can resemble the following to keep our React app and backend Express server within one enclosing folder:
# project folder structure
ts-livechat/
backend/
client/ # we will generate or clone the client template later
Our express server will reside in the backend/
folder. We will be initiating a new package.json
and constructing our server from scratch. The alternative to this of course is to use a program such as express-genera
tor to generate that boilerplate for us; but this is unnecessary for this project.
Installing dependencies
Inside backend/
, initiate package.json
and install the following dependencies:
# generate package.json
yarn init
# install dependencies
yarn add typescript express @types/express socket.io @types/socket.io
yarn add ts-node-dev --dev
We have installed express
and socket.io
, along with their associated types from DefinitelyTyped.
The ts-node-
dev package has also been installed. This is an optimised Typescript recompile tool that relies on node-
dev as a dependency. Node restarts and re-compiles are triggered upon every file change, alleviating the need to manually compiling Typescript and restarting your node.
Note: ts-node-dev
is a more efficient alternative to using packages such as concurren
tly and node
mon; although these are widely used and worth familiarising with if the reader has interest of exploring these further.
Configuring package.json
We will want to add some scripts inside package.json
to be able to run our server with ts-node-dev
. We will call this dev
mode. Along with this command, we’ll also include a prod
command for compiling and running a production build:
// package.json
{
...
"scripts": {
"dev": "ts-node-dev --respawn --transpile ./src/server.ts",
"prod": "tsc && node ./dist/server.js"
},
...
}
dev
will run our server, with re-spawning enabled via the--respawn
flag.--transpile
speeds up compile time, but sacrifices type checking and declaration file generation.prod
will run and compile our Typescript to adist/
folder and then run the resultingserver.js
file. Our server will be run viaserver.ts
, which is where this filename derives from.
Note: If you are developing on a Mac, node-ts-dev
will output notifications in your Notifications sidebar upon every server restart. I opted to disable this, which can be done in System Preferences -> Notifications
, and turning off terminal-notifier
.
Note 2: Although editors are out the scope of this talk, it is worth noting that VS Code is currently the best suited editor for Typescript; installing the TSLint extension will catch type errors as you are writing code.
Although there is no server to compile yet, we can now run yarn run dev
to start our development server with auto-reloading, and yarn run prod
to compile and run a compiled Javascript build of the server.
Configuring tsconfig.json
Housing a tsconfig.json
file within backend/
will configure the Typescript compiler, accessed via the tsc
command. Run tsv -v
in your Terminal to ensure it is installed. The completed configuration has been documented below:
// tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": false,
"target": "es6",
"noImplicitAny": true,
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist",
"baseUrl": ".",
"paths": {
"*": [
"node_modules/",
"src/types/*"
]
}
},
"include": [
"src/**/*"
]
}
This is all standard compiler configuration, adhering to the commonjs
module framework and ES6 target. Note that compiled Javascript is generated in the dist/
folder, with our Typescript originating in the src/
folder.
Note: To explore tsconfig.json
further, refer to the Typescript Handbook page dedicated to the file.
Running tsc
will now adhere to the above configuration. With the environment configured we can now develop the Express server and Socket.io code itself, that can later be compiled into vanilla Javascript for production.
Server Structure
Our server will be developed within a src/
folder with the following structure, adhering to a modular design pattern:
backend/
src/
ChatServer.ts
constants.ts
server.ts
types.ts
dist/
...
We are mainly interested in ChatServer.ts
, hosting a ChatServer
class that manages everything from initialising our express server to our Socket.io connections and events. We will explore this file in more detail next.
server.ts
simply initialises a new ChatServer
object and exports the resulting express application:
// src/server.ts
import { ChatServer } from './ChatServer';
let app = new ChatServer().app;
export { app };
Also included are types
.ts and constants
.ts files that house one interface
and one enum
respectively:
// src/constants.ts
export enum ChatEvent {
CONNECT = 'connect',
DISCONNECT = 'disconnect',
MESSAGE = 'message'
}
Our ChatEvent
enum
contains all the event types that Socket.io will emit:
- The built-in
connect
anddisconnect
events that (in a self explanatory nature) fire when clients connect and disconnect from the server - A
message
event for handling incoming chat messages
// src/types.ts
export interface ChatMessage {
author: string;
message: string;
}
types.ts
stores all type and interface objects that we explicitly define, which for this demonstration only includes one interface
. message
events will receive a ChatMessage
object, consisting of an author and the message itself. An example of such an object could be the following:
{
author: 'Ross',
message: "Testing chat message"
}
Socket.io sends and emits JSON objects, giving the developer flexibility with the complexity of data sent with each event. Once received, this object will then be emitted back to each connected client to update their chat window. This all happens in ChatServer
.
In a production ready solution, it is very likely the emitting ChatMessage
type will be different from the incoming type. For example, a timestamp
could be added and displayed on the client side, to let users know how long it has been since the message was received.
Note: This demo has been designed to be bare-bones, yet fully typed and fully functional so the project can be easily built upon. Add to these files as your events become more complex.
ChatServer Class Implementation
The Express application is configured in ChatServer
in its entirety. Let’s break down what is happening in the class.
Class properties
The class properties house our Express app instance, socket.io instance, and other configuration such as the port number of the socket service, all fully typed utilising the DefinitelyTyped packages we installed previously:
// ChatServer class properties
public static readonly PORT: number = 8080;
private _app: express.Application;
private server: Server;
private io: SocketIO.Server;
private port: string | number;
Note: Where you do not give default values for class properties, Typescript expects the constructor
to provide those values instead. If not, the property must either be defined as optional with ?
, or also have a null
type.
The public PORT
property acts as the default port for our server to listen on, defined as 8080
. Our remaining properties are private, housing our application and server instances, as well as our socket.io instance. The private port
property is a union type, that accepts either a string
or a number
type.
The Express app itself is stored as _app
, which is private. To access _app
externally, a getter has also been defined further down the class, returning _app
as an express.Application
type:
get app (): express.Application {
return this._app;
}
And this is how server.ts
gets _app
and exports the resulting instance:
// src/server.ts
...
let app = new ChatServer().app; // calling the getter method here
export { app };
Constructor
The class constructor
gives values for each of our properties, and initialises our server:
constructor () {
this._app = express();
this.port = process.env.PORT || ChatServer.PORT;
this._app.use(cors());
this._app.options('*', cors());
this.server = createServer(this._app);
this.initSocket();
this.listen();
}
There are a few interesting points we can derive from the constructor:
- The
port
property firstly checks if an environment variable has been defined, and falls back to the defaultPORT
property if it has not - We have included
c
ors middleware to ensure access to the service from any domain, although you may wish to restrict this or remove this completely depending on your security model - A http server is instantiated via the imported
createServer
function from Express’shttp
package. - The
initSocket()
method is called, initialising oursocketIo
instance:
private initSocket (): void {
this.io = socketIo(this.server);
}
- The
listen()
method is called, which opens up communication to our server and Socket.io events.
Socket.io event handling
Let’s look at listen()
in more detail to examine how these events are handled:
private listen (): void {
// server listening on our defined port
this.server.listen(this.port, () => {
console.log('Running server on port %s', this.port);
});
//socket events
this.io.on(ChatEvent.CONNECT, (socket: any) => {
console.log('Connected client on port %s.', this.port);
socket.on(ChatEvent.MESSAGE, (m: ChatMessage) => {
console.log('[server](message): %s', JSON.stringify(m));
this.io.emit('message', m);
});
socket.on(ChatEvent.DISCONNECT, () => {
console.log('Client disconnected');
});
});
}
This boilerplate allows us to define what happens when the server starts listening on the specified port, as well as handle receiving Socket.io events.
Take note of the ChatEvent.MESSAGE
event:
socket.on(ChatEvent.MESSAGE, (m: ChatMessage) => {
console.log('[server](message): %s', JSON.stringify(m));
this.io.emit('message', m);
});
This event expects an incoming object of type ChatMessage
, that we typed previously, to just have message
and author
values. We stringify and log this object, before emitting it to all connected clients via a message
event. This is the mechanism by which messages are delivered to all the connected clients of the app.
As mentioned above, the returning ChatMessage will most likely be different in your production build, with timestamps, additional validations and other metadata associated with the message.
From here, the client side will need to listen to this message
event and react to it to update the chat UX. Part two of this series will explore how this is done in more detail with React, Socket.io Client, and RxJS.
Backend Summary
We have now documented the Express server setup. The completed ChatServer.ts
file along with the other backend/
source files can be found on Github here.
To summarise this process, we have:
- Initiated a new Express project and installed our required dependencies, as well as their types
- Configured the Typescript compiler,
tsc
, and defined build scripts insidepackage.json
for development and production - Examined the usage of
ts-node-dev
and how to re-build and re-compile your Typescript as changes are being made - Introduced a modular project setup, separating exported, types, constants, and the chat server itself into coherent objects
- Documented the
ChatServer
class, that utilises private class properties to store our server instances, with the class constructor initialising the server and Socket.io event listeners. A getter method was also defined for retrieving the Express application from an instantiatedChatServer
, giving access while protecting the underlying private_app
object from being manipulated externally.
In the next article we will hook up this backend service to a client side React app: