← Back to TypeScript Course | Chapter 28: Real World Projects | Lesson 10 of 14

Real-time App with Socket.io

Socket.io can provide real-time communication between clients and servers. TypeScript can define event names and payload shapes shared by both sides.
Syntax
typescript
interface ServerToClientEvents {
  eventName: (payload: PayloadType) => void;
}

interface ClientToServerEvents {
  eventName: (arg: ArgType) => void;
}

Core Concept

Typing a Socket.IO app means declaring the exact shape of every event name and payload, so emitting an event with the wrong argument type — on either the client or server — is caught at compile time instead of failing silently at runtime.

Example: Core Concept

typescript
interface ServerToClientEvents {
  message: (payload: { user: string; text: string }) => void;
}
// socket.emit("message", { user: "Ravi", text: "hi" }) is now type-checked
console.log("Event names and payloads are typed, not stringly-typed");

Basic Setup

A basic setup uses Socket.IO's generic Server<ClientToServerEvents, ServerToClientEvents> and Socket types, defining each event as a method signature in a shared events interface.

Example: Basic Setup

typescript
interface ClientToServerEvents {
  join: (room: string) => void;
}
interface ServerToClientEvents {
  message: (payload: { user: string; text: string }) => void;
}
// const io: Server<ClientToServerEvents, ServerToClientEvents> = new Server();
console.log("Server<ClientToServerEvents, ServerToClientEvents> types every event");

Typed Example

A typed example: interface ServerToClientEvents { message: (payload: { user: string; text: string }) => void } means calling socket.emit(message, { user, text }) is checked against that exact payload shape, and a missing field is a compile error.

Example: Typed Example

typescript
interface ServerToClientEvents {
  message: (payload: { user: string; text: string }) => void;
}
function emitMessage(payload: { user: string; text: string }) {
  console.log("emit('message', ", payload, ")");
}
emitMessage({ user: "Ravi", text: "Hello" });

Project Usage

In a real project, sharing the events interface between the Node server and the browser client (via a shared-types package) keeps both sides in sync automatically whenever a new real-time event is added or changed.

Example: Project Usage

typescript
// shared/events.ts
export interface ServerToClientEvents {
  message: (payload: { user: string; text: string }) => void;
}
console.log("Shared events interface keeps server and client in sync");

Best Practices

Keep event names as a single shared union type used by both emit and listen calls, so renaming an event is a compile-time-checked change everywhere it's used instead of a string that has to be manually grepped for.

Example: Best Practices

typescript
type EventName = "message" | "join" | "leave";
function emit(event: EventName, payload: unknown) {
  console.log(event, payload);
}
emit("message", { text: "hi" });
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
Common Mistakes
  1. Emitting an event name that is not in the events interface, like "mesage", which the generic types catch only if you added them.
  2. Defining the events interface separately on server and client so they do not match.
  3. Forgetting that event payload types are not checked at runtime, so a client can still send bad data.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.