socket.on('connection', (ws: WebSocket, req: http.IncomingMessage) => {
ws.online = true; // for heartbit but typescript throws a error
Expected result:
typescript is happy
Actual result:
typescript is angry
Attachments:
I think you have to extend the existing classes
declare class MyWebSocket extends WebSocket {
online: boolean;
and similarly for WebSocket.Server
. I don't think it's possible to modify existing interfaces but I'm a TypeScript noob so I may be wrong.
Actually it's possible in typescript and works well with other DefinatelyTyped typings also. It just doesn't work with this library..
user.middleware.ts
import { NextFunction, Request, Response } from "express";
export const userMiddleware = (req: Request, res: Response, next: NextFunction) => {
req.userId = 1; // NO error here after using below declaration merging
next();
express-augmented.d.ts
import express from "express";
declare module "express" {
export interface Request {
userId: number;
See last two sections: https://www.typescriptlang.org/docs/handbook/declaration-merging.html
But when i try to do the same with this library, it shows errors. I am not 100% sure but i think it's because of all the interfaces inside namespace using the exported class(exported outside namespace)
I am sorry i don't know how DefinatelyTyped typings work exactly but Is there any specific reason why class WebSocket is declared outside namespace WebSocket ?
Leaving this here since it took too long to figure this out in typescript (i'm still relatively new to it):
All you have to do is declare this in your file:
interface ExtWebSocket extends WebSocket {
isAlive: boolean;
then wherever you're defining that, for example:
setInterval(() => {
wss.clients.forEach((ws: WebSocket) => {
const extWs = ws as ExtWebSocket;
if (!extWs.isAlive) return ws.terminate();
extWs.isAlive = false;
ws.ping(null, undefined);
}, 10000);
You cannot augment it via a module, as this particular library implements using a class and not an interface. That's what causes the type errors when trying to add new fields to the websocket object. That's what I gathered after researching.
This uses type assertion: https://basarat.gitbooks.io/typescript/docs/types/type-assertion.html
dafi87, abumalick, ferares, Badestrand, and AlexStormwood reacted with thumbs up emoji
abumalick reacted with rocket emoji
All reactions
I know that this issue is closed but still want to share it here, maybe someone will find this useful, as it worked for me.
declare module "ws" {
class _WS extends WebSocket { }
export interface WebSocket extends _WS {
online: boolean;
though this will not work (since WebSocket
here refers to the class, not the declared interface):
socket.on('connection', (ws: WebSocket, req: http.IncomingMessage) => {
ws.online = true;
while these do:
without type definition:
socket.on('connection', (ws, req: http.IncomingMessage) => {
ws.online = true;
with type definition (WebSocket.WebSocket
):
socket.on('connection', (ws: WebSocket.WebSocket, req: http.IncomingMessage) => {
ws.online = true;
Buntelrus, alanxp, DevDengChao, Aliph0th, and marcuspuchalla reacted with thumbs up emoji
vkumov, beiguancyc, anotherminh, Buntelrus, TheMachineHerald, j8jacobs, matbind, and Ammaraly reacted with heart emoji
TheMachineHerald and tuanpq1998 reacted with rocket emoji
All reactions
Kudos to @yuriyyakym!
This is only solution I've found for accessing the method signatures of class WebSocket in VSCode.
(instead of getting namespace ws.WebSock, (or the typescriptlib browser-side WebSocket)
import * as ws from "ws"
declare module 'ws' {
export interface WebSocket extends ws { }
I can now declare a ws: ws.WebSocket
and ws.send(...)
shows all the right arg types!
I know that this issue is closed but still want to share it here, maybe someone will find this useful, as it worked for me.
declare module "ws" {
class _WS extends WebSocket { }
export interface WebSocket extends _WS {
online: boolean;
though this will not work (since WebSocket
here refers to the class, not the declared interface):
socket.on('connection', (ws: WebSocket, req: http.IncomingMessage) => {
ws.online = true;
while these do:
1. without type definition:
socket.on('connection', (ws, req: http.IncomingMessage) => {
ws.online = true;
1. with type definition (`WebSocket.WebSocket`):
socket.on('connection', (ws: WebSocket.WebSocket, req: http.IncomingMessage) => {
ws.online = true;
This was very handy and a slick way of bypassing this restriction.
I know this issue is old, but it keeps getting referenced, and none of the above are actually correct. Making a type like that is effectively just lying to the type system to create a type like that, as the actual type of the socket object will still be the default WebSocket
.
// online will be `undefined`
wss.on('connection', (ws) => console.log(ws.online.toString())
Take note of the type parameter for the server:
class Server<
T extends typeof WebSocket.WebSocket = typeof WebSocket.WebSocket,
U extends typeof IncomingMessage = typeof IncomingMessage,
> extends EventEmitter {
It is NOT T extends WebSocket.WebSocket
, but T extends typeof WebSocket.WebSocket
. typeof SomeClass
gives you the type of the class object (constructor function), not the type of an instance of the class. Thus, after extending the original WebSocket
class, the generic type parameter should be Server<typeof CustomWebSocket>
.
Also note the constructor parameter WebSocket
in the options for Server
. Here, you pass the custom class, which is then created here on connection:
ws/lib/websocket-server.js
Line 382
a57e963
import { Server, WebSocket } from 'ws'
export class CustomWebSocket extends WebSocket {
userId!: string
sendStuff() {
this.send('stuff')
export class CustomWebsocketServer extends Server<typeof CustomWebSocket> {
constructor() {
super({
WebSocket: CustomWebSocket,
socketForUser(userId: string): CustomWebSocket | undefined {
// this.clients is of type Set<CustomWebSocket>
for (const socket of this.clients) {
if (socket.userId === userId) {
return socket
const wss = new CustomWebsocketServer()
wss.on('connection', (socket) => {
// socket is of type CustomWebSocket
socket.sendStuff()
Final note: You don't need one, but if you add a constructor to your WebSocket
subclass, it has to be compatible with that of the original WebSocket
class, which is techically:
constructor(...params: ConstructorParameters<typeof WebSocket>) {}
However, it will only ever be called with null
, so you may as well leave it as a no-args constructor()
I found a simple solution to this problem. I use express-ws
as a basis.
Supporting files
package.json
"name": "websocket-example",
"version": "1.0.0",
"description": "Websocket example",
"main": "src/index.ts",
"scripts": {
"dev:server": "nodemon src/index.ts"
"author": "Mykhailenko Serhii",
"dependencies": {
"config": "^3.3.8",
"express": "^4.18.1",
"express-ws": "^5.0.2",
"uuid": "^9.0.1"
"devDependencies": {
"@types/config": "^3.3.0",
"@types/express": "^4.17.14",
"@types/express-ws": "^3.0.4",
"@types/node": "^18.18.5",
"@types/uuid": "^9.0.7",
"@types/ws": "^8.5.10",
"nodemon": "^2.0.20",
"ts-node": "^10.9.2",
"typescript": "^4.8.3"
tsconfig.json
"compilerOptions": {
"target": "es2020",
"module": "commonjs",
"allowJs": true,
"outDir": "./dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
Solution
Create custom types:
For example, an id
field will be added to the ws
object.
types.ts
import {Request, NextFunction} from 'express'
import * as ws from 'ws'
export type MyWs = ws & {id: string}
export type MyWsRequestHandler = (ws: MyWs, req: Request, next: NextFunction) => void
Create router, middleware and controller:
router.ts
import {Router} from 'express'
import {WebsocketRequestHandler} from 'express-ws'
import {MyWsRequestHandler} from './types'
import {v4} from 'uuid'
const generateWsIdMiddleware: MyWsRequestHandler = (ws, _, next) => {
ws.id = v4()
next()
type TController = {
getUser: MyWsRequestHandler
activateUser: MyWsRequestHandler
const controller: TController = {
getUser(ws, req) {
const clientId = ws.id
const {userId} = req.params
console.log(`[ws] + connection | ${clientId}`)
ws.on('close', () => {
console.log(`[ws] - close | ${clientId}`)
ws.on('message', (message) => {
console.log('[ws] message', message)
// your code
// services.onWsMessage(message)
// services.getUser(userId)
activateUser(ws, req) {
const clientId = ws.id
const {link} = req.params
console.log(`[ws] + connection | ${clientId}`)
ws.on('close', () => {
console.log(`[ws] - close | ${clientId}`)
ws.on('message', (message) => {
console.log('[ws] message', message)
// your code
// services.onWsMessage(message)
// services.activateUser(link)
export const wsRouter = Router()
wsRouter.ws(
'/users/:userId',
generateWsIdMiddleware as WebsocketRequestHandler,
controller.getUser as WebsocketRequestHandler
wsRouter.ws(
'/users/activate/:link',
generateWsIdMiddleware as WebsocketRequestHandler,
controller.activateUser as WebsocketRequestHandler
Create a server:
index.ts
import express from 'express'
import expressWs from 'express-ws'
const expressWsServer = expressWs(express())
const app = expressWsServer.app
process.env['NODE_CONFIG_DIR'] = __dirname + '/config/'
import config from 'config' // necessarily after creating the server
import {wsRouter} from './router'
const PORT = config.get('port') || 5000
app.use(express.json({inflate: true}))
app.use('/ws', wsRouter)
async function start() {
try {
app.listen(PORT, () => console.log(`Server listening on ${PORT} ...\n`))
} catch (err) {
console.log(err)
start()
In conclusion
I placed the entire finished test project in the archive, link: websocket.zip
If it helped someone - mark it
import { Server, WebSocket } from 'ws'
export class CustomWebSocket extends WebSocket {
userId!: string
sendStuff() {
this.send('stuff')
export class CustomWebsocketServer extends Server<typeof CustomWebSocket> {
constructor() {
super({
WebSocket: CustomWebSocket,
socketForUser(userId: string): CustomWebSocket | undefined {
// this.clients is of type Set<CustomWebSocket>
for (const socket of this.clients) {
if (socket.userId === userId) {
return socket
const wss = new CustomWebsocketServer()
wss.on('connection', (socket) => {
// socket is of type CustomWebSocket
socket.sendStuff()
Am I missing something here? Having CustomWebSocket in the CustomWebsocketServer constructor creates a circular import where in ends up passing either port, server, or noServer in the options more than once. When you create a new CustomWebsocketServer instance, even though you haven't passed any options arguments, it's passing the default WebsocketServer arguments more than once.
This error goes away when when you remove the Websocket option and add either port, server, noServer into the constructor.
it's passing the default WebsocketServer arguments more than once.
I don't know what that means. How can you pass arguments more than once? Either way, this has nothing to do with those properties. It's just referenced in a single place here:
ws/lib/websocket-server.js
Line 385
d343a0c
This is what your full example is throwing when I run it:
/app/node_modules/ws/lib/websocket-server.js:86
throw new TypeError(
TypeError: One and only one of the "port", "server", or "noServer" options must be specified
at new WebSocketServer (/app/node_modules/ws/lib/websocket-server.js:86:13)
at new CustomWebSocketServer (/app/dist/src/typings/ws.js:21:9)
That error goes away with this for example:
export class CustomWebSocketServer extends Server<typeof CustomWebSocket> {
constructor(...params: ConstructorParameters<typeof Server>) {
super({
noServer: true
Well sure, you still have to configure the rest of the options as appropriate for your use case, but none of those are related to the custom socket type. E.g.
super({
WebSocket: CustomWebSocket,
noServer: true,