This is a quick step by step tutorial about WebSocket, Node/Express and Typescript. The full source code provided in these examples is lovely hosted by Github.
WebSocket is a communications protocol that provides a full-duplex communication channels over a single TCP connection established between a web browser (client) and a web server (this take place through the “classic HTTP mechanism” of handshaking, implemented using request/response headers).
This allows the server to send content to the browser without being called by the client, pushing data through the opened connection, defining ws and wss as URI schemes used respectively for unencrypted and encrypted connections.
In this tutorial we are going to use ws, a simple client/server library for Node.js that helps us to manage all the stuff under the protocol.
I choose this library instead of the well known Socket.IO because today the WebSocket protocol is natively supported in most major browsers (see the screenshot below) allowing to exclude all the overhead introduced by the several features of Socket.IO (see here for a head to head comparison).
Another useful thing that comes with the usage of HTTP concerns the possibility of use the “good old” Authorization header for Basic/Bearer Token Auth.
Now that we know a little more about what is hidden under the hood, let’s write some code.
First step
Let’s assume that we want to create a simple WebSocket server using Node.js and Express. Open your favorite console ad type the following commands (notice that this tutorial supposes that you have node installed on your machine: if it is not the case get it here:)
mkdir websocket-node-express
cd websocket-node-express
npm init
// add the details of your project
npm i ws express --save
// install the necessary types (and typescript)...
Now we can add some code to understand how it works. The minimal script in order to get a basic result is the following (copy it in src/server.ts):
import * as express from 'express';
import * as http from 'http';
import * as WebSocket from 'ws';
const app = express();
//initialize a simple http server
const server = http.createServer(app);
//initialize the WebSocket server instance
const wss = new WebSocket.Server({ server });
wss.on('connection', (ws: WebSocket) => {
//connection is up, let's add a simple simple event
ws.on('message', (message: string) => {
//log the received message and send it back to the client
console.log('received: %s', message);
ws.send(`Hello, you sent -> ${message}`);
});
//send immediatly a feedback to the incoming connection
ws.send('Hi there, I am a WebSocket server');
});
//start our server
server.listen(process.env.PORT || 8999, () => {
console.log(`Server started on port ${server.address().port} :)`);
It starts a new instance of a simple http server using express. Later we add the WebSocket server specifying a (fat arrow) function that will be triggered by the ‘connection’ event (line 13): it is responsible to handle all the incoming connections from clients.
In this case we immediately send back a welcome message (line 24), registering at the same time the ‘message’ event: this function will be called on every message sent by the client. Here we log the received message and then we send it back to the client (like an echo service — this time using the Typescript string interpolation ${}).
Ok let me see if it works…
Before that add a minimal tsconfig.json (we’re using Typescript, aren’t we?)
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "./dist/server",
"strict": true,
"sourceMap": true,
"typeRoots": [
"node_modules/@types"
]
},
"exclude": [
"dist",
"node_modules"
]
}
Open your terminal and type
// please compile my code
./node_modules/.bin/tsc // or simply tsc (if installed globally)
// then run the server
node ./dist/server/server.js
반응형
728x90
You should get something like this in your console (here I’m on Windows10 usingcmderand asimple bash extension).
Now it’s time to make some connection using a WebSocket test client: these screenshots come from Smart Websocket Client (an extension of Chrome from the Chrome Web Store).
Add the server address ws://localhost:8999, press connect and send a message.
The server logs the message sent by the client and return it back. Obviously now we can go on and create another client and see if the server can manage another incoming connection.
Going deeper — Broadcasting
Now that we are able to start our server let’s add some code to enable a new feature: message broadcasting.
Modify the code in server.ts substituting the 'message’ event with this new version:
//connection is up, let's add a simple simple event
ws.on('message', (message: string) => {
//log the received message and send it back to the client
The trick here is to fetch the request sent by a client in order to understand if it is a broadcast message (there are a lot of different ways to do this: sniffing headers, using a common message structure in json, etc… but let’s keep things simple, we’re not in production!).
Here I’m using a simple regex to figure out if this is a broadcast message and to remove the “label” from the message content.
If this is the case forward the message content to the other connected clients.
Bonus track — Handle broken connections
The link between the server and the client can be interrupted in a way that both are unaware of the broken state of the connection. To avoid this situation we can simply use ping messages to check if the client is still responsive.
wss.on('connection', (ws: ExtWebSocket) => {
ws.isAlive = true;
ws.on('pong', () => {
ws.isAlive = true;
});
//connection is up, let's add a simple simple event
ws.on('message', (message: string) => {
//[...]
}
});
setInterval(() => {
wss.clients.forEach((ws: ExtWebSocket) => {
if (!ws.isAlive) return ws.terminate();
ws.isAlive = false;
ws.ping(null, false, true);
});
}, 10000);
//start our server
server.listen(process.env.PORT || 8999, () => {
console.log(`Server started on port ${server.address().port} :)`);
As you can see here we set ws.isAlive = true when the ‘connection’ starts, doing the same when the ‘pong’ event is called (more on this later).
Before the server startup we set an interval (10 seconds in this case) that checks if the client is alive:
if the value of isAlive is false we terminate the client connection;
if the value of isAlive is true, we set its value to false and then we execute a ping. Pings and Pongs are just regular frames, but they are specific control frames defined by the specs of WebSocket protocol in order to check if the remote endpoint is still connected. In this case we are setting the isAlive to false to understand if the client pong event sets it back to true, completing the connection check.
import { msg, hello } from "./hello"; console.log(msg); hello();
実行
スクリプトを実行してみましょう。
node --experimental-modules main.mjs (node:49617) ExperimentalWarning: The ESM module loader is experimental. hello hello
実行するスクリプトの拡張子が .js の場合、次のようなエラーメッセージが表示されます。
node --experimental-modules main.js /Users/masakielastic/test/esm-project/main.js:1 (function (exports, require, module, __filename, __dirname) { import { msg, hello } from "./hello"; ^^^^^^ SyntaxError: Unexpected token import at createScript (vm.js:80:10) at Object.runInThisContext (vm.js:139:10) at Module._compile (module.js:564:28) at Object.Module._extensions..js (module.js:611:10) at Module.load (module.js:521:32) at tryModuleLoad (module.js:484:12) at Function.Module._load (module.js:476:3) at Function.Module.runMain (module.js:641:10) at startup (bootstrap_node.js:201:16) at bootstrap_node.js:626:3
読み込もうとするモジュールの拡張子が .js の場合、次のようなエラーメッセージが表示されます。
(node:49783) ExperimentalWarning: The ESM module loader is experimental. SyntaxError: The requested module does not provide an export named 'msg' at checkComplete (internal/loader/ModuleJob.js:86:27) at moduleJob.linked.then (internal/loader/ModuleJob.js:69:11) at <anonymous>
標準モジュールを読み込む
url モジュールの URLSearchParams を試してみましょう。
client.mjs
import url from "url"; const params = new url.URLSearchParams(); params.append("msg", "hello world"); console.log(params.toString()); // msg=hello+world
記事の執筆時点では次の書き方はエラーになります。
import { URLSearchParams } from "url"; const params = new URLSearchParams(); console.log(params.toString()); // msg=hello+world
エラーメッセージは次のとおりです。
SyntaxError: The requested module does not provide an export named 'URLSearchParams' at checkComplete (internal/loader/ModuleJob.js:86:27) at moduleJob.linked.then (internal/loader/ModuleJob.js:69:11) at <anonymous>
開発環境における代替策
開発環境でモジュールローダーを手軽に試したい場合の代替案が複数あります。
@std/esm
Node v4 から ESM を利用できるようにするツールです。スクリプトの実行時に r オプションで読み込みます。