Skip to main content

One post tagged with "WebRTC"

View All Tags

How to put a express server in Browser

Β· 4 min read
A Independent developer

While exploring WebRTC, I had a simple thought:

WebRTC communication feels a lot like HTTP β€” so why not abstract it as an HTTP-like service?

If you compare a typical Node.js HTTP server with WebRTC data channel communication, they’re actually quite similar:

  • Both listen for incoming messages/events
  • Both process requests
  • Both send responses back

πŸ” A Quick Comparison​

HTTP Server (Node.js)​

const http = require('http');

const PORT = 3000;

const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello, World!\n');
});

server.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}/`);
});

WebRTC DataChannel​

const peerConnection = new RTCPeerConnection(configuration);

const dataChannel = peerConnection.createDataChannel('my-channel');

dataChannel.onopen = () => {
console.log('Data channel opened');
};

dataChannel.onmessage = (event) => {
console.log('received data:', event.data);
};

let remoteChannel = null;

peerConnection.ondatachannel = (event) => {
remoteChannel = event.channel;

remoteChannel.onmessage = (event) => {
console.log('received remote message:', event.data);

// reply
remoteChannel.send('gotcha');
};
};

From this, it becomes obvious:

πŸ‘‰ The DataChannel is essentially an event-driven transport layer, just like HTTP.

So I asked:

Can I build an HTTP-like server abstraction on top of WebRTC?

The answer turned out to be: yes.


🧱 Step 1: Define a Message Protocol​

Unlike HTTP, we don’t need to support complex content types.

We can keep things simple and use JSON.

Important: All peers must follow the same message structure.

export type PeerMessage = {
id: string;
src: string;
dest: string;
method: string;
path: string;
body: Record<string, any>;
headers: Record<string, any>;
status: string;
guid?: string;
timestamp: number | string;
metadata?: Record<string, any>;
scope: 'request' | 'response';
};

🧭 Step 2: Build a Router (Like Express)​

If you’ve ever read the source code of Express or Koa, you’ll notice:

A router is surprisingly simple β€” you can build one in under 100 lines.

I built a slightly enhanced version with:

  • Routing (GET / POST)
  • Middleware support
  • Error handling
  • Route caching
  • Performance monitoring

Here’s the core idea:

export class PeerRouter {
protected routes = [];
protected middleware = [];

get(path: string, callback: RequestCallbackFn) {
this.routes.push({ path, method: 'GET', callback });
}

post(path: string, callback: RequestCallbackFn) {
this.routes.push({ path, method: 'POST', callback });
}

use(handler: RequestCallbackFn) {
this.middleware.push(handler);
}

async run(conn: DataConnection, message: PeerMessage) {
const route = this.routes.find((r) => r.path === message.path);

if (!route) {
console.warn(`No route for ${message.method} ${message.path}`);
return;
}

for (const mw of this.middleware) {
await mw(conn, message);
}

await route.callback(conn, message);
}
}

βš™οΈ Step 3: Create a PeerServer​

This is where everything comes together.

  • Handle incoming requests
  • Match routes
  • Track request/response lifecycle
class PeerServer {
private router = new PeerRouter();
private requests = new Map();

use(handler: RequestCallbackFn) {
this.router.use(handler);
}

addRoute(route: RouteItem) {
this.router.add(route);
}

async onMessage(conn: DataConnection, message: PeerMessage) {
if (message.scope === 'request') {
return this.router.run(conn, message);
}

if (message.scope === 'response') {
const cb = this.requests.get(message.id);
if (cb) cb(message);
}
}
}

πŸ§ͺ Step 4: Use It Like Express​

Now comes the fun part.

We can write WebRTC logic just like Express:

const server = new PeerServer();

server.use(async (conn, message, next) => {
if (message.headers['x-device-id'] !== 'xx') {
throw new Error('Invalid device id');
}
return next();
});

server.get('/ping', (conn, message) => {
conn.send({
...message,
body: { message: 'ok' },
status: 200,
});
});

Hook it into WebRTC:

dataChannel.onmessage = (event) => {
server.onMessage(dataChannel, event.data);
};

peerConnection.ondatachannel = (event) => {
const remoteChannel = event.channel;

remoteChannel.onmessage = (event) => {
server.onMessage(remoteChannel, event.data);
};
};

🎯 The Result​

You now have:

An Express-like server running entirely inside the browser, powered by WebRTC.

No ports. No servers. No traditional backend.


🌍 Real-world Usage​

This architecture is already powering πŸ‘‰ https://egosapp.com

All P2P communication in EGOS is built on top of this system.

It lets me:

  • Write WebRTC logic like Express APIs
  • Treat devices as distributed endpoints
  • Build a decentralized file network

🧠 Final Thought​

HTTP changed how we build applications by standardizing communication.

WebRTC opens the door to something new:

Peer-to-peer applications that feel like traditional client-server systems β€” but without servers.


If you're interested, feel free to try it out or share feedback.