Define TS Types for your Laravel Echo Events
Hey guys,
I've been working on a Laravel project (personal) for fun, focusing on the latest cool stuff: Laravel 11 & Reverb (from Broadcasting) โญ๏ธ.
Using Laravel Broadcasting means you should use the client library provided by Laravel - Laravel Echo, to save a lot of your time setting up stuff ๐.
Even though Laravel Echo has been written in TypeScript, however, it doesn't give us any opportunity or battery to "type" our events ๐ฃ.
So that's why we have this topic and I'll show you how to define the types for your events ๐.
Before going deeper
I suppose you already have some knowledge about:
TypeScript
Websocket & how it works
๐ฅน๐ฅน๐ฅน
The Define Types for Laravel Echo
Note: I'm demonstrating on the PresenceChannel (private channel with auth & more features).
Extending the "PresenceChannel"
I'll create a file called echo.ts
and extend the PresenceChannel
interface
type BaseEvent = {
type: string;
payload: unknown;
};
export interface StrictPresenceChannel<Event extends BaseEvent>
extends PresenceChannel {
listen<EventType extends Event['type']>(
event: EventType,
callback: (data: Extract<Event, { type: EventType }>['payload']) => void
): this;
}
This would be our base interface wrapper for the PresenceChannel
.
In the real-life app, we would have multiple channels for communicating between client & server.
Each channel should extend the base interface and have its own Events declaration.
Defining Events
Following the BaseEvent
structure, we need to define all of the Events that would be sent to the client.
For a sample chat app, I've added these events:
type ChatMessageAddedEvent = {
type: 'ChatMessageAdded';
payload: {
fromUser: {
id: string; // ULID
},
chat: {
id: string;
message: string;
}
}
}
type ChatImageAddedEvent = {
type: 'ChatImageAdded';
payload: {
fromUser: {
id: string; // ULID
},
chat: {
id: string;
imageUrl: string;
}
}
}
type ChatEvent = ChatMessageAddedEvent | ChatImageAddedEvent;
Don't forget to add a final type - a union of all events ๐, we'll use it in the part below.
Create a Channel and Extend the StrictPresenceChannel
I created a ChatChannel
interface and added the ChatEvent
union type.
export interface ChatChannel extends StrictPresenceChannel<ChatEvent> {}
A function to join the Typed-Channel
// getEchoInstance() is simply returning the new Echo({...})
export const getChatChannel = (channelId: string): => {
return getEchoInstance().join(channelId) as ChatChannel;
};
Here we have to hack a bit - using as
to force the type.
This is the shortcut for a quick win & hassle-free while it won't result in any error or failure.
Use the Typed-Channel instance ๐
const channel = getRoomChannel(myChatChannel.id);
channel.listen('ChatMessageAdded', (data) => {
// access data. & IDE will suggest the fields for you
}).listen('ChatImageAdded', (data) => {
// access data. & IDE will suggest the fields for you
});
Then you're all set ๐ IDEs now will handle auto-suggestion, eslint
/tsc
can do the type check stuff in build time ๐ช
An example from my project:
Notes
Note: this is purely defining types, it won't guarantee that your data is 100% valid and it could go wrong from the end user ๐.
I'll make another topic for schemas definition & data validation for Laravel Echo soon, ensuring the data is legit to use โญ๏ธ.
Stay tuned ๐
Conclusion
Well, that's basically it for defining types for your Broadcast Events ๐.
Have fun guys!
Subscribe to my newsletter
Read articles from Seth Chen directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Seth Chen
Seth Chen
develops awesome software, contributes to OSS, writes tech tips, and loves Vietnamese milk coffee!