TypeScript
Starting with v3, Socket.IO now has first class support for TypeScript.
Types for the server
First, declare some types:
interfaceServerToClientEvents{
noArg:()=>void;
basicEmit:(a:number, b:string, c: Buffer)=>void;
withAck:(d:string,callback:(e:number)=>void)=>void;
}
interfaceClientToServerEvents{
hello:()=>void;
}
interfaceInterServerEvents{
ping:()=>void;
}
interfaceSocketData{
name:string;
age:number;
}
And use them when creating your server:
const io =newServer<
ClientToServerEvents,
ServerToClientEvents,
InterServerEvents,
SocketData
>();
Then, profit from the help of your IDE!
The events declared in the ServerToClientEvents interface are used when sending and broadcasting events:
io.on("connection",(socket)=>{
socket.emit("noArg");
socket.emit("basicEmit",1,"2", Buffer.from([3]));
socket.emit("withAck","4",(e)=>{
// e is inferred as number
});
// works when broadcast to all
io.emit("noArg");
// works when broadcasting to a room
io.to("room1").emit("basicEmit",1,"2", Buffer.from([3]));
});
The ones declared in the ClientToServerEvents interface are used when receiving events:
io.on("connection",(socket)=>{
socket.on("hello",()=>{
// ...
});
});
This also works with the Promise-based API:
interfaceServerToClientEvents{
// [...]
withAck:(d:string,callback:(result:number)=>void)=>void;
}
try{
const result =await socket.timeout(5_000).emitWithAck("withAck","some string");
// result is inferred as <number>
}catch(err){
// handle error
}
The events declared in the InterServerEvents interface are used for inter-server communication (added in socket.io@4.1.0):
io.serverSideEmit("ping");
io.on("ping",()=>{
// ...
});
And finally, the SocketData type is used to type the socket.data attribute (added in socket.io@4.4.0):
io.on("connection",(socket)=>{
socket.data.name ="john";
socket.data.age =42;
});
These type hints do not replace proper validation/sanitization of the input. As usual, never trust user input.
Types for the client
On the client side, you can reuse the same ServerToClientEvents and ClientToServerEvents interfaces:
import{ io, Socket }from"socket.io-client";
// please note that the types are reversed
const socket: Socket<ServerToClientEvents, ClientToServerEvents>=io();
Similarly, the events declared in the ClientToServerEvents interface are used when sending events:
socket.emit("hello");
And the ones declared in ServerToClientEvents are used when receiving events:
socket.on("noArg",()=>{
// ...
});
socket.on("basicEmit",(a, b, c)=>{
// a is inferred as number, b as string and c as buffer
});
socket.on("withAck",(d, callback)=>{
// d is inferred as string and callback as a function that takes a number as argument
});
Custom types for each namespace
Since each Namespace can have its own set of events, you can also provide some types for each one of them:
import{ Server }from"socket.io";
// types for the main namespace
const io =newServer<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>();
// types for the namespace named "/my-namespace"
interfaceNamespaceSpecificClientToServerEvents{
foo:(arg:string)=>void
}
interfaceNamespaceSpecificServerToClientEvents{
bar:(arg:string)=>void;
}
interfaceNamespaceSpecificInterServerEvents{
// ...
}
interfaceNamespaceSpecificSocketData{
// ...
}
const myNamespace: Namespace<
NamespaceSpecificClientToServerEvents,
NamespaceSpecificServerToClientEvents,
NamespaceSpecificInterServerEvents,
NamespaceSpecificSocketData
>= io.of("/my-namespace");
myNamespace.on("connection",(socket)=>{
socket.on("foo",()=>{
// ...
});
socket.emit("bar","123");
});
And on the client side:
import{ io, Socket }from"socket.io-client";
const socket: Socket<
NamespaceSpecificServerToClientEvents,
NamespaceSpecificClientToServerEvents
>=io("/my-namespace");
socket.on("bar",(arg)=>{
console.log(arg);// "123"
});