Secure Backend Proxy
Use this page when you want a production-safe first integration and do not want raw MediaSFU credentials in the frontend.
Why this matters
MediaSFU is a frontend SDK, but the credential boundary should stay on your backend.
In production, the frontend should:
- collect user intent such as create room or join room
- call your app backend
- receive the backend result
- pass custom
createMediaSFURoomandjoinMediaSFURoomfunctions into the SDK
The backend should:
- hold
MEDIASFU_API_USERNAMEandMEDIASFU_API_KEY - validate the caller and payload
- forward the request to MediaSFU Cloud or your self-hosted MediaSFU Open server
- return only the room result to the frontend
MediaSFU Cloud uses the same upstream endpoint for create and join requests: https://mediasfu.com/v1/rooms/.
Your app can still expose separate /api/mediasfu/create-room and /api/mediasfu/join-room routes if that keeps validation, auth, or auditing simpler. The important contract is that both backend routes forward to the same MediaSFU Cloud rooms URL above.
Recommended request flow
- The user signs into your app.
- Your frontend calls
/api/mediasfu/create-roomor/api/mediasfu/join-room. - Your backend adds MediaSFU credentials server-side.
- Your backend forwards the request to MediaSFU.
- The frontend passes the result into the MediaSFU room flow.
Backend example: Node + Express
This is the smallest production-safe pattern to start from.
import express from 'express';
const app = express();
app.use(express.json());
const mediaSFURoomsUrl =
process.env.MEDIASFU_ROOMS_URL ?? 'https://mediasfu.com/v1/rooms/';
const mediaSFUHeaders = {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.MEDIASFU_API_USERNAME}:${process.env.MEDIASFU_API_KEY}`,
};
async function forwardToMediaSFU(url: string, payload: unknown) {
const response = await fetch(url, {
method: 'POST',
headers: mediaSFUHeaders,
body: JSON.stringify(payload),
});
const data = await response.json();
return { ok: response.ok, status: response.status, data };
}
app.post('/api/mediasfu/create-room', async (req, res) => {
try {
const result = await forwardToMediaSFU(mediaSFURoomsUrl, req.body);
res.status(result.ok ? 200 : result.status).json(result.data);
} catch (error) {
res.status(500).json({
error: `Unable to create room: ${(error as Error).message}`,
});
}
});
app.post('/api/mediasfu/join-room', async (req, res) => {
try {
const result = await forwardToMediaSFU(mediaSFURoomsUrl, req.body);
res.status(result.ok ? 200 : result.status).json(result.data);
} catch (error) {
res.status(500).json({
error: `Unable to join room: ${(error as Error).message}`,
});
}
});
Self-hosted variant
If you are using MediaSFU Open instead of MediaSFU Cloud, point the backend at the room endpoint your server exposes:
MEDIASFU_ROOMS_URL=http://your-mediasfu-open-host/rooms/
The frontend contract stays the same.
Frontend example: custom room hooks
The React package already exposes the right extension points. The important rule is that these functions call your backend, not MediaSFU directly.
type RoomResult = {
data: Record<string, unknown> | null;
success: boolean;
};
export async function createMediaSFURoom({ payload }: { payload: unknown }): Promise<RoomResult> {
const response = await fetch('/api/mediasfu/create-room', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
const data = await response.json();
return { data, success: response.ok };
}
export async function joinMediaSFURoom({ payload }: { payload: unknown }): Promise<RoomResult> {
const response = await fetch('/api/mediasfu/join-room', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
const data = await response.json();
return { data, success: response.ok };
}
Frontend example: MediaSFU room setup
import { useState } from 'react';
import { MediasfuGeneric } from 'mediasfu-reactjs';
export function RoomScreen() {
const [sourceParameters, setSourceParameters] = useState<Record<string, unknown>>({});
return (
<MediasfuGeneric
connectMediaSFU={true}
returnUI={false}
noUIPreJoinOptions={{
action: 'create',
eventType: 'conference',
capacity: 10,
duration: 30,
userName: 'Host',
}}
sourceParameters={sourceParameters}
updateSourceParameters={setSourceParameters}
createMediaSFURoom={createMediaSFURoom}
joinMediaSFURoom={joinMediaSFURoom}
/>
);
}
This keeps credentials on the backend while still using the SDK's normal room flow.
Frontend examples by framework
Use the same backend endpoints regardless of frontend framework. Only the client adapter changes.
React
async function createMediaSFURoom({ payload }: { payload: unknown }) {
const response = await fetch('/api/mediasfu/create-room', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
return { data: await response.json(), success: response.ok };
}
async function joinMediaSFURoom({ payload }: { payload: unknown }) {
const response = await fetch('/api/mediasfu/join-room', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
return { data: await response.json(), success: response.ok };
}
Angular
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { map } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class MediaSfuProxyService {
constructor(private readonly http: HttpClient) {}
createRoom(payload: unknown) {
return this.http
.post('/api/mediasfu/create-room', payload)
.pipe(map((data) => ({ data, success: true })));
}
joinRoom(payload: unknown) {
return this.http
.post('/api/mediasfu/join-room', payload)
.pipe(map((data) => ({ data, success: true })));
}
}
Vue
type RoomResult = { data: unknown; success: boolean };
export async function createMediaSFURoom(payload: unknown): Promise<RoomResult> {
const response = await fetch('/api/mediasfu/create-room', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
return { data: await response.json(), success: response.ok };
}
export async function joinMediaSFURoom(payload: unknown): Promise<RoomResult> {
const response = await fetch('/api/mediasfu/join-room', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
return { data: await response.json(), success: response.ok };
}
For framework-specific component wiring, use the matching SDK page after you confirm the backend boundary is working.
Minimum production checklist
- Keep MediaSFU credentials in backend environment variables only.
- Authenticate the user before allowing create or join actions.
- Validate room payloads before forwarding them.
- Rate-limit your proxy endpoints.
- Log MediaSFU failures on the backend, not only in the browser.
- Treat the frontend
createMediaSFURoomandjoinMediaSFURoomhooks as thin adapters.
Where to go next
- Need help deciding how much UI to keep? Read the Build Style Guide.
- Need custom runtime control after join works? Read Media Lifecycle.
- Want product-shaped examples after the secure path is working? Read Starter Screens.