Create the Express and Socket.IO Server
Initialize the HTTP server, attach Socket.IO, configure connection handling, and create a reusable Socket.IO configuration.
Create the Express Application
Open src/server.js and create the initial Express application.
Express will handle regular HTTP requests, such as loading the chat page and checking application health.
We will create an Express application and serve static frontend files from the public directory.
import 'dotenv/config';
import express from 'express';
import { createServer } from 'node:http';
const app = express();
app.use(express.json());
app.use(express.static('public'));
app.get('/health', (req, res) => {
res.status(200).json({
status: 'ok',
message: 'Chat server is running'
});
});
const httpServer = createServer(app);
const PORT = process.env.PORT || 3000;
httpServer.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`);
});Creates the Express application, enables JSON parsing, serves static frontend files, adds a health endpoint, and creates a Node.js HTTP server.
Express handles HTTP routes and static files.
The HTTP server is created using node:http.
The health endpoint helps verify application availability.
Understand Why Socket.IO Needs the HTTP Server
One important detail is that Socket.IO must be attached to the HTTP server that handles your Express application.
A common beginner mistake is using app.listen() and then attempting to attach Socket.IO to another HTTP server.
Consider this incorrect implementation:
const app = express();
const io = new Server(createServer(app));
app.listen(3000);
This creates two separate HTTP servers:
- The server created by
createServer(app)is attached to Socket.IO. - The server created internally by
app.listen()handles Express requests.
The Socket.IO server and the Express server are therefore not using the same HTTP server.
Instead, explicitly create one HTTP server and use it for both Express and Socket.IO.
Do not call app.listen() after creating a separate HTTP server for Socket.IO. Start the shared HTTP server using httpServer.listen().
import express from 'express';
import { createServer } from 'node:http';
import { Server } from 'socket.io';
const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer);
httpServer.listen(3000);The same HTTP server is shared between Express and Socket.IO. This allows both HTTP requests and Socket.IO connections to use the same listening port.
Socket.IO must be attached to the intended HTTP server.
Use createServer(app) to create the shared server.
Start the application with httpServer.listen().
Initialize the Socket.IO Server
Now integrate Socket.IO into the Express application.
Create src/config/socket.js.
The Socket.IO Server class accepts the Node.js HTTP server as its first argument.
It also accepts configuration options, such as allowed origins, transport settings, and connection timeouts.
For our initial same-origin application, we do not need to enable additional cross-origin access.
CORS is an HTTP browser security mechanism. If your frontend and backend use different origins, configure the exact permitted origin instead of using wildcard access for authenticated applications.
import { Server } from 'socket.io';
export function initializeSocket(httpServer) {
const io = new Server(httpServer, {
cors: {
origin: false
}
});
return io;
}Creates a reusable function that initializes and returns the Socket.IO server. The application serves its frontend from the same origin, so no cross-origin browser access is required.
The Socket.IO Server class attaches real-time communication to an HTTP server.
Socket.IO configuration can be separated into its own module.
CORS configuration should match the frontend deployment architecture.
Connect Socket.IO with Express
Import the Socket.IO initialization function into src/server.js.
Initialize Socket.IO using the HTTP server before starting the application.
The io instance will be used throughout the backend to emit events, manage rooms, and communicate with connected clients.
import 'dotenv/config';
import express from 'express';
import { createServer } from 'node:http';
import { initializeSocket } from './config/socket.js';
const app = express();
app.use(express.json());
app.use(express.static('public'));
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
const httpServer = createServer(app);
const io = initializeSocket(httpServer);
httpServer.listen(process.env.PORT || 3000, () => {
console.log('HTTP and Socket.IO server started');
});The Express application and Socket.IO server now share the same HTTP server. The io variable represents the Socket.IO server instance.
Initialize Socket.IO before listening for incoming connections.
Keep the io instance accessible to event handlers.
Express and Socket.IO share one HTTP server.
Handle Client Connections
Socket.IO provides a connection event that fires whenever a client successfully establishes a connection.
The callback receives a socket object representing that particular client connection.
The socket object provides methods and properties for:
- Receiving events.
- Sending events.
- Joining rooms.
- Leaving rooms.
- Disconnecting clients.
- Accessing connection information.
Add a connection listener to the Socket.IO server.
Important Socket Properties
Unique connection identifier
Identifies a particular connection. It is not a permanent user identity.
Connection status
Indicates whether the socket is currently connected.
Handshake information
Contains connection details and authentication data supplied during the handshake.
Joined rooms
A Set containing the rooms joined by the socket.
io.on('connection', (socket) => {
console.log('New client connected:', socket.id);
socket.on('disconnect', (reason) => {
console.log('Client disconnected:', socket.id, reason);
});
});The connection callback executes once for each connected client. Each client receives a unique socket ID for its current connection. The disconnect callback runs when that connection closes.
The connection event executes for every new client.
Each connection has its own socket object.
The disconnect event helps clean up connection-specific resources.
Start and Verify the Server
Start the application using the development script.
Run the following command from the project root:
npm run dev
You should see a message similar to:
HTTP and Socket.IO server started
Open the health endpoint in your browser:
http://localhost:3000/health
Expected response:
{
"status": "ok"
}
At this point, the HTTP server and Socket.IO server are initialized. No browser client has connected yet, so the Socket.IO connection callback will not execute until we implement the frontend.
If port 3000 is already occupied, change the PORT value in your .env file and restart the application.
npm run devRuns the Node.js application using the configured development script.
The application should start without errors.
The health endpoint verifies HTTP availability.
A Socket.IO connection appears only after a client connects.