Message Composability
The Messages<R> type allows you to define reusable, type-safe message handlers that can be composed and spread into your Room’s messages property. This is useful for:
- Code reuse: Share common message handlers across multiple rooms
- Modularity: Organize handlers into separate files or modules
- Third-party libraries: Create and distribute reusable message handler packages
This pattern requires TypeScript for full type-safety.
Defining Reusable Message Handlers
Use the Messages<R> generic type to define a set of message handlers outside of your Room class. The generic parameter R should be your Room class to ensure proper typing for this context.
import { Room, validate, type Client, type Messages } from "colyseus";
import { z } from "zod";
import { MyRoom } from "./MyRoom";
// Define a custom client type if needed
type MyClient = Client<{ userData: { name: string } }>;
export const thirdPartyMessages: Messages<MyRoom> = {
// Handler with validation
chat: validate(
z.object({
text: z.string().max(500),
}),
function (client: MyClient, message) {
// message.text is guaranteed to be a string (max 500 chars)
this.broadcast("chat", {
sender: client.sessionId,
text: message.text,
});
}
),
};When using validate() with the Messages<R> type, use a regular function expression instead of an arrow function if you need to access this (the Room instance).
Composing Messages in Your Room
Use the spread operator to compose multiple message handler objects into your Room’s messages property:
import { Room, Client, validate } from "colyseus";
import { z } from "zod";
import { thirdPartyMessages } from "./thirdPartyMessages";
import { MyRoomState } from "./MyRoomState";
type MyClient = Client<{ userData: { name: string } }>;
export class MyRoom extends Room<{ client: MyClient }> {
state = new MyRoomState();
messages = {
// Spread reusable handlers from external modules
...thirdPartyMessages,
// Define room-specific handlers with validation
move: validate(
z.object({
x: z.number(),
y: z.number(),
z: z.number().optional(),
}),
(client, message) => {
const player = this.state.players.get(client.sessionId)!;
player.x = message.x;
player.y = message.y;
}
),
// Simple handler defined inline
notify_something: (client: MyClient, message: any) => {
this.broadcast("notify", { sessionId: client.sessionId, message: "I'm a notification!" });
},
};
}Overriding Composed Handlers
When spreading multiple handler objects, handlers defined later will override earlier ones with the same name:
messages = {
...thirdPartyMessages, // Contains "chat" handler
// This overrides the "chat" handler from thirdPartyMessages
chat: validate(
z.object({ text: z.string() }),
(client, message) => {
// Custom chat implementation
}
),
};See Also
- Room - Message Handling - Basic message handler syntax
- Room - Message Input Validation - Using
validate()with Zod schemas