0

Please let me know if this is a duplicate.

Description

I'll try to be concise. I have a barebones Express API with a WebSocket endpoint that I set up with express-ws following the docs, but it doesn't work. I can't tell where the problem lies—client-side or server-side.

What I can tell is that FF (v112.0b6 dev ed) does not/cannot connect to my WebSocket endpoint.

Server

Dir structure

src/
  api/
    index.ts
  app.ts
  index.ts

src/index.ts

import app from './app'

const port = process.env.PORT || 80

app.listen(port, () => {
    console.log(`Listening on http://localhost:${port}`)
})

src/app.ts

import express from 'express'
import api from './api'

const app = express()
app.use('/api/v1', api)

export default app

src/api/index.ts

import express from 'express' // v4.18.1
import expressWs from 'express-ws' // v5.0.2

expressWs(express())

const router = express.Router()

router.ws('/', (ws, req) => {
    ws.on('message', msg => {
        console.log(msg)
    })
})

export default router

Server console

Listening on http://localhost:41240

BTW, other HTTP routers work fine on this app.

Client

Browser console

const socket = new WebSocket('ws://localhost:41240/api/v1')

socket.addEventListener('open', () => {
    socket.send('iloveyou')
})

The browser console is open on http://localhost:41240 (same host).

Result

Browser console

Firefox can’t establish a connection to the server at ws://localhost:41240/api/v1.

Sensibly, the client-side code does not execute further than the declaration of the socket, since that's where the connection fails.

Thank you for any help and do let me know if you need additional details.

roberto
  • 15
  • 6
  • It's been a while I don't use `ws` but I don't think `express` routes will not be discoverable from the `expressWs` routing... you need to duplicate them in both calls – balexandre May 07 '23 at 16:22
  • @balexandre as far as [the docs](https://www.npmjs.com/package/express-ws#usage) go, everything seems alright server-side. – roberto May 07 '23 at 16:33
  • from the docs, you have both `app.get('/'...` and `app.ws('/'...` so, duplicating the `/` route in both HTTP and WebSocket availability endpoints, in the "full example", right? – balexandre May 07 '23 at 16:37
  • @balexandre I duplicated the `/` endpoint with `router.get` and placed it right before `router.ws`, but that didn't fix anything, seemingly. Getting the same error. – roberto May 07 '23 at 16:42
  • HTTP and WS behave differently, and you are expecting that an HTTP call (`app.use('/api/v1', api)`) to return something that the WebSocket could understand, you can't use `/api/v1` in your WS call, you need to proxy things out, and return the message, as an example, see that `api` might have a signature of `req, res, next` but in a WS call, you have no such params... you can't use the same.... I hope you can understand what I'm trying to explain – balexandre May 07 '23 at 16:47
  • @balexandre I see, very much indeed, I moved the WS router to `src/app.ts` and the error no longer occurs. I suppose I could figure out to export the router in `src/api/index.ts` such that `express-ws` configures WS routing on the Express app in `src/app.ts` correctly. – roberto May 07 '23 at 17:05
  • another thing I've learned recently, you can add `require('express-ws')` to the [GitHub search](https://github.com/search?q=require%28%27express-ws%27%29&type=code) and see how everyone is using that package – balexandre May 07 '23 at 17:16

2 Answers2

1

This line of /src/api/index.ts:

expressWs(express())

is not correct. That is creating a new app object that is not connected to your running http server. That app needs to be the SAME app object that the rest of your app is using, not a new one. Probably whoever imports this module needs to pass it the app object in some initialization function and you can then initialize expressWs using the shared app object.

In this expressWs doc example, you can see that there's only one app object that both express and expressWs are using, plus it makes sense that expressWs needs to be able to hook into your existing web server.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • 1
    I see that [`express-ws`'s API](https://www.npmjs.com/package/express-ws#api) provides the `wsInstance.app` property, which is the `app` object that it was initialised on. However, can't figure out how to access it correctly. Or more precisely, what exactly the `wsInstance` part is and where to get it from. – roberto May 07 '23 at 17:45
  • NVM, can't read. – roberto May 07 '23 at 17:47
  • @roberto - As the doc says, what they are referring to as `wsInstance` is what is returned when you call `expressWs(app)`. I don't think that will help you here at all. You need to call `expressWs(app)` with the right `app` object which you have to either `import` or you have to pass to an initialization function in your express-ws module file. – jfriend00 May 07 '23 at 17:48
  • @roberto - I would suggest that you export a `wsInit` function from your `src/api/index.ts` file and then import that function from from `src/app.ts` and then call it and pass it the right `app` object as in `wsInit(app)` and then put your expressWs initialition code inside that `wsInit` function. – jfriend00 May 07 '23 at 17:50
1

This comment would be too much for a comment, so I'll post it as an answer, to show what I've finally used as my solution.

Description

I isolated the app object to a file, which exports it. This app is imported in src/api/index.ts, where it is passed to expressWs for initialisation. Later in that file, I define a router.ws and I export this router (it's imported one level up in src/app.ts).

In src/app.ts, I import the app object as well, and src/api/index.ts's router. The rest of the code is left as in the question description.

The WebSocket connection now works properly and can transmit data.

Server

New dir structure

src/
  api/
    index.ts
  lib/
    app/
      index.ts
  app.ts
  index.ts

Updated src/app.ts

import { app } from '#lib/app'
import api from './api'

app.use('/api/v1', api)

export default app

Updated src/api/index.ts

import { app } from '#lib/app'
import express from 'express'
import expressWs from 'express-ws'

expressWs(app)

const router = express.Router()

router.ws('/', (ws, req) => {
    ws.on('message', msg => {
        console.log(msg)
    })
})

export default router

New src/lib/app/index.ts

import express from 'express'
export const app = express()
roberto
  • 15
  • 6