# Node.js

> Integrate crossws with Node.js (manually) or uWebSockets.js

<tip>

You can use `serve` function from `crossws/server` to **automatically** integrate crossws with Node.js!

</tip>

To manually integrate crossws with your Node.js HTTP server, you need to connect the `upgrade` event to the `handleUpgrade` method returned from the adapter. crossws uses a prebundled version of [ws](https://github.com/websockets/ws).

```ts
import { createServer } from "node:http";
import crossws from "crossws/adapters/node";

const ws = crossws({
  hooks: {
    message: console.log,
  },
});

const server = createServer((req, res) => {
  res.end(
    `<script>new WebSocket("ws://localhost:3000").addEventListener('open', (e) => e.target.send("Hello from client!"));</script>`,
  );
}).listen(3000);

server.on("upgrade", (req, socket, head) => {
  if (req.headers.upgrade === "websocket") {
    ws.handleUpgrade(req, socket, head);
  }
});
```

<read-more>

See [`test/fixture/node.ts`](https://github.com/h3js/crossws/blob/main/test/fixture/node.ts) for demo and [`src/adapters/node.ts`](https://github.com/h3js/crossws/blob/main/src/adapters/node.ts) for implementation.

</read-more>

## Delegating to an existing Node.js upgrade handler

If you already have a Node.js WebSocket library that exposes a raw `(req, socket, head)` upgrade handler (e.g. [`ws`](https://github.com/websockets/ws), `socket.io`, `express-ws`), you can route to it through crossws using `fromNodeUpgradeHandler`. This lets you keep crossws's upgrade-time request handling while delegating the WebSocket lifecycle to your existing library.

```ts
import { WebSocketServer } from "ws";
import { fromNodeUpgradeHandler } from "crossws/adapters/node";
import { serve } from "crossws/server/node";

const wss = new WebSocketServer({ noServer: true });
wss.on("connection", (ws) => {
  ws.on("message", (data) => ws.send(data));
});

serve({
  fetch: () => new Response("ok"),
  websocket: fromNodeUpgradeHandler((req, socket, head) => {
    wss.handleUpgrade(req, socket, head, (ws) => {
      wss.emit("connection", ws, req);
    });
  }),
});
```

The underlying handler takes full ownership of the socket, so crossws's other lifecycle hooks (`open`, `message`, `close`, `error`) are **not** invoked for connections routed through it — manage the WebSocket lifecycle inside your own library as usual.

<note>

`fromNodeUpgradeHandler` only works on the Node.js runtime, and must be used via the crossws node server plugin so the request carries `runtime.node.upgrade.{socket, head}`.

</note>

## uWebSockets

You can alternatively use [uWebSockets.js](https://github.com/uNetworking/uWebSockets.js) for Node.js servers.

First add `uNetworking/uWebSockets.js` as a dependency.

```ts
import { App } from "uWebSockets.js";
import crossws from "crossws/adapters/uws";

const ws = crossws({
  hooks: {
    message: console.log,
  },
});

const server = App().ws("/*", ws.websocket);

server.get("/*", (res, req) => {
  res.writeStatus("200 OK").writeHeader("Content-Type", "text/html");
  res.end(
    `<script>new WebSocket("ws://localhost:3000").addEventListener('open', (e) => e.target.send("Hello from client!"));</script>`,
  );
});

server.listen(3001, () => {
  console.log("Listening to port 3001");
});
```

<read-more>

See [`test/fixture/node-uws.ts`](https://github.com/h3js/crossws/blob/main/test/fixture/node-uws.ts) for demo and [`src/adapters/node-uws.ts`](https://github.com/h3js/crossws/blob/main/src/adapters/node-uws.ts) for implementation.

</read-more>

## Socket.IO

[Socket.IO](https://socket.io/) speaks two transports: a WebSocket upgrade and an HTTP long-polling fallback served under `/socket.io/`. Both can be routed through crossws by combining `fromNodeUpgradeHandler` with srvx's `fetchNodeHandler` helper to forward the polling path (and the optional client bundle at `/socket.io/socket.io.js`) to socket.io's own Node-style `(req, res)` listener:

```ts
import { createServer } from "node:http";
import { Server as SocketIOServer } from "socket.io";
import { fromNodeUpgradeHandler } from "crossws/adapters/node";
import { serve } from "crossws/server/node";
import { fetchNodeHandler } from "srvx/node";

// Attach socket.io to a throwaway http.Server — it never `.listen()`s,
// but the dummy server now holds socket.io's own `request` listener,
// which handles the client bundle AND delegates polling to engine.io.
const dummyServer = createServer();
const io = new SocketIOServer(dummyServer, { serveClient: true });
const [socketIoRequestListener] = dummyServer.listeners("request");

io.on("connection", (socket) => {
  socket.emit("welcome", { id: socket.id });
  socket.on("message", (text) => io.emit("message", { from: socket.id, text }));
});

const server = serve({
  port: 3000,
  async fetch(req) {
    const url = new URL(req.url);
    if (url.pathname.startsWith("/socket.io/")) {
      return fetchNodeHandler(socketIoRequestListener, req);
    }
    return new Response("ok");
  },
  websocket: fromNodeUpgradeHandler((req, socket, head) => {
    io.engine.handleUpgrade(req, socket, head);
  }),
});

await server.ready();
```

If you only need the WebSocket transport, force the client to skip polling with `io(url, { transports: ["websocket"] })` and you can drop the `fetch`-side delegation entirely.

<read-more>

See [`examples/socket.io`](https://github.com/h3js/crossws/tree/main/examples/socket.io) for a runnable demo including a browser client.

</read-more>
