This content originally appeared on DEV Community and was authored by Askarbek Zadauly
In this article I wanted to share my experience of building a Web Application that uses WebSocket connection instead of HTTP request/response. As a sample Web Application we'll be building Tetris game, because... why not?
You can checkout the full source code right away on GitHub
Key Features
- Full Duplex. Both the client and the server can send messages at the same time. So when we send a request from frontend to backend we will not wait for a response. Instead we will subscribe to an event (Subject) that will be triggered by backend request (which can be considered as a response).
- Synchronized Svelte Stores. When backend sends a request to frontend, all we have to do is to update a corresponding Svelte Store. Which in turn will trigger a UI update.
Tech Stack
- Backend: NodeJS with Typescript
- Socket Server: uWebSockets
- Validations: Zod
- Frontend: SvelteKit project used as a SSG
- Styling: TailwindCSS
- package manager: pnpm
Starting the uWebSockets (backend) server
Here's how I start our WebSocket server:
import { App, WebSocket } from 'uWebSockets.js';
const SocketServer = App({});
const startServer = () => {
console.log('starting backend server...');
SocketServer.ws<{ socket_id: string }>('/tetris', {
open: () => {
console.log('opening connection');
},
close: () => {
console.log('closing connection');
},
message: (ws: WebSocket<{ socket_id: string }>, message: ArrayBuffer, isBinary: boolean) => {
console.log('message received')
},
}).listen(8080, () => {
console.log('backend server is running on: ', 8080);
});
};
startServer();
uWebSockets socket server
uWebSockets is web server written in C++. In our case we'll be using uWebSockets.js which has bindings to NodeJS. It can be used as a usual web server but the main feature is its WebSocket server. It is 10x faster than Socket.IO and 8.5x faster than Fastify. I have to say I haven't benchmarked it myself. I decided to use uWebSockets just because it feels more pure to my taste. For example it doesn't require Express it runs on it's own, also it doesn't have any additional wrappers or helper functions that "babysit" you. You just get a message and you handle it whatever the way you want it. So if you're ok with that approach and you need a faster WebSocket server then you should use uWebSockets.
Messages
Frontend and Backend will be sending each other a data which we'll be calling "messages". A Message is a JSON object like this:
export type TMessage = {
type: MessageType;
body: any;
};
export enum MessageType {
MOVE_LEFT,
MOVE_RIGHT,
}
Depending on a Message Type our backend will be executing a corresponding handler with parameters supplied in "body" field. The function that executes handler depending on a Message Type we'll call the "MessageBroker" and it can look like this:
import { MessageType } from './message.js';
export const messageBroker = (type: MessageType, body: any) => {
switch (type) {
case MessageType.MOVE_LEFT:
{
moveLeftHandler(body);
}
break;
case MessageType.MOVE_RIGHT:
{
moveRightHandler(body);
}
break;
}
};
export const moveLeftHandler = (params: any) => {
console.log('moving left');
};
export const moveRightHandler = (params: any) => {
console.log('moving right');
};
Now we would like to send a request from backend to frontend. In our case we need to send the blocks of the tetris game. If user clicks left or right button we will redraw all blocks by sending an array, like this:
export const blocks: number[][] =[
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1],
[0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1],
[1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1],
]
This array will be "synchronized" with a Svelte Store which has same type and will be used in UI to show this:
here's the UI source:
export const Rows = writable<number[][]>([]);
<div class="flex flex-col items-center border-l border-r border-b border-neutral-500">
{#each $Rows as row, indexRow}
<div class="flex items-center border-b border-neutral-200 last:border-b-0">
{#each row as column, indexColumn}
<Cell value="{column}" />
{/each}
</div>
{/each}
</div>
In order to understand how all "other stuff" work you're welcome to checkout the full source code.
Here are some main features of the GitHub repo you need to keep in mind.
Monorepo
The project is a monorepo, containing both backend and frontend in "modules" folder.
packages:
- 'modules/*'
There are some advantages and disadvantages to using monorepo, the reason I used monorepo is that I share Typescript types and Zod shapes (more on this later) across both projects. So I declare all types in backend project and import them in frontend project, that way I have one source for all types.
Validating data using Zod
Zod is a TypeScript-first schema validation with static type inference.
So let's say for example we have this basic type for a User:
type TUser = {
email: string;
password: string;
}
Now we want to know if the provided email field is a valid email and password field is at least 6 character length. Instead of writing a check function we can use a special library that does it for us. So I've chosen Zod to do that. Here's how:
const userShape = z.object({
email: z.string().email(),
password: z.string().min(6),
});
const data = {
email: 'some@example.com',
password: '123456',
};
const user = userShape.parse(data);
Now if the data is incorrect the userShape.parse
function will throw an exception.
But the best feature of Zod is that it can infer types:
type TUser = z.infer<typeof userShape>
Now TUser
is a proper Typescript type.
I'm using Zod validation and infered types for all data that goes between frontend and backend.
Final thoughts
Some of my decisions on building this WebApp might not be the best, I'm aware of that, so consider this article more like a conversation starter rather than a How-To guide.
Cheers!
This content originally appeared on DEV Community and was authored by Askarbek Zadauly
Askarbek Zadauly | Sciencx (2024-10-09T12:56:58+00:00) Building Tetris using WebSocket and Svelte Stores. Retrieved from https://www.scien.cx/2024/10/09/building-tetris-using-websocket-and-svelte-stores/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.