*** id: 97ad2e2b-a14d-485a-878b-d9d6ebf551fa title: Make a Zoom like application description: Learn how to make a Zoom alternative using the SignalWire Video API. x-custom: tags: * 'product:video' * 'language:javascript' * 'language:nodejs' * 'language:react' * 'sdk:relaybrowser' sidebar\_custom\_props: platform: javascript github: '[https://github.com/signalwire/browser-videoconf-full-react](https://github.com/signalwire/browser-videoconf-full-react)' slug: /js/guides/zoom-like-application max-toc-depth: 3 *** In this guide, we are going to make a Zoom-like video conferencing system using React, SignalWire APIs, SDKs and other tools. The full source code for this project is available on [GitHub](https://github.com/signalwire/browser-videoconf-full-react). We will use: 1. [The SignalWire Video SDK](/docs/browser-sdk/v3/js/reference/video) will run in the client's browser. It handles the cameras, the microphones, communication with the SignalWire servers, and with other members in the conference. We will also use this SDK to display the video stream in the browser. 2. [The SignalWire REST APIs for Video](/docs/apis/video/rooms/create-room) to provision rooms and access tokens for your conference members from the SignalWire server. SignalWire REST APIs are only available on your server, as they require your SignalWire API tokens to operate which shouldn't be exposed client-side. 3. [The React library from SignalWire Community](https://github.com/signalwire-community/react) to handle the integration between the SDK and React. We will be using [Next.js](https://nextjs.org/) for convenience and brevity, but you should be able to use any React framework to write the frontend and any server-side framework to write the backend. We will use the [React Bootstrap](https://react-bootstrap.github.io) framework to make a neat layout without too much boilerplate. If you are looking for something far simpler to quickly embed on your existing page, please use the UI-included video room from the Dashboard instead. ## Setting Up the project Our starting point will be the Next.js boilerplate on which we will install the packages discussed above: ```bash yarn create next-app --typescript cd yarn add @signalwire-community/react yarn add bootstrap react-bootstrap react-bootstrap-icons swr axios ``` ## Backend While most of the work with respect to capturing and displaying media in the conference happens client-side, you do still need a server to securely proxy the SignalWire REST API. The client SDK needs a token be able to access the SignalWire servers hosting the conference. Your server can query for this token using SignalWire REST API, given that you have the API credentials. Note that this is not the server where all the video streaming and processing happens. All those complex tasks will be handled by powerful SignalWire servers elsewhere. The figure below illustrates how all parts fit.
```mermaid sequenceDiagram participant C as Client (Browser, App, ...) participant YS as Your Server participant SW as SignalWire Servers C->>YS: Join Room X as User Y YS->>SW: GET access token for Room X as User Y and a list of permissions SW-->>YS: { Token } YS-->>C: { Token } C->>SW: Join Room X via RELAY Browser SDK using { Token } SW-->>C: WebRTC Stream (Video & Events) C->>SW: Change Room Layout C->>SW: Mute Audio C->>SW: Leave Room ```
Diagram of the interaction between the client, your server, and SignalWire.
In a production setting, your server should authenticate your users, manage their permissions, get appropriate tokens for members and relay the tokens from the SignalWire's Video REST APIs to the client's browser. The following code will create a new endpoint at `/api/token`, which will query SignalWire and serve tokens given at least a valid `room_name`. It also takes additional `user_name` and `mod` parameters. The `user_name` parameter simply sets the display name for the user requesting the token. The `mod` parameter (short for "moderator" in this case) selects between the two sets of permissions defined in `permissions.ts` which can be assigned to the user. Note that the location of this file ensures that this will run server-side at `api/token` endpoint. Learn more about Next.js routing [here](https://nextjs.org/docs/routing/introduction). ```tsx title="pages/api/token.ts" import axios from "axios"; import { FULL_PERMISSIONS, GUEST_PERMISSIONS } from "../../data/permissions"; const AUTH = { username: process.env.PROJECT_ID as string, password: process.env.API_TOKEN as string, }; const SPACE_NAME = process.env.SPACE_NAME as string; export default async function handler(req: any, res: any) { const { room_name, user_name, mod } = req.query; if (room_name === undefined) return res.status(422).json({ error: true }); try { const tokenResponse = await axios.post( `https://${SPACE_NAME}.signalwire.com/api/video/room_tokens`, { room_name, user_name, enable_room_previews: true, permissions: mod === "true" ? FULL_PERMISSIONS : GUEST_PERMISSIONS, }, { auth: AUTH } // pass {username: project_id, password: api_token} as basic auth ); const token = tokenResponse.data.token; if (token !== undefined) res.json({ token, error: false }); else res.status(400).json({ error: true }); } catch (e) { res.status(400).json({ error: true }); } } ``` ```tsx title="data/permissions.ts" export const FULL_PERMISSIONS = [ "room.hide_video_muted", "room.show_video_muted", "room.list_available_layouts", "room.playback", "room.recording", "room.set_layout", "room.set_position", //Members "room.member.audio_mute", "room.member.audio_unmute", "room.member.deaf", "room.member.undeaf", "room.member.remove", "room.member.set_input_sensitivity", "room.member.set_input_volume", "room.member.set_output_volume", "room.member.video_mute", "room.member.video_unmute", "room.member.set_position", //Self "room.self.additional_source", "room.self.audio_mute", "room.self.audio_unmute", "room.self.deaf", "room.self.undeaf", "room.self.screenshare", "room.self.set_input_sensitivity", "room.self.set_input_volume", "room.self.set_output_volume", "room.self.video_mute", "room.self.video_unmute", "room.self.set_position", ]; export const GUEST_PERMISSIONS = [ //Members "room.member.remove", //Self "room.self.additional_source", "room.self.audio_mute", "room.self.audio_unmute", "room.self.deaf", "room.self.undeaf", "room.self.screenshare", "room.self.set_input_sensitivity", "room.self.set_input_volume", "room.self.set_output_volume", "room.self.video_mute", "room.self.video_unmute", "room.self.set_position", ]; ``` In a production setting, you would want this endpoint to be behind an authentication middleware to make sure only your intended users can use it. For Next.js, an easy addition would be [next-auth](https://next-auth.js.org/). You might also want to check if the users requesting mod permissions have the authorization to actually do so in your system. To quickly go over various parts of this code: 1. The constants `FULL_PERMISSIONS` and `GUEST_PERMISSIONS` are arrays of strings representing the permissions given to the user. So while `FULL_PERMISSIONS` might look like `[..., 'room.member.video.mute', 'room.member.remove', ...]`, `GUEST_PERMISSIONS` would look like `[..., 'room.self.video.mute']`, indicating that guest is not allowed to mute or remove any other user. SignalWire offers a flexible permission system so you can give users all combination of permissions as required. Permissions are described [here](/docs/apis/permissions). 2. The constant `AUTH` is a structure that assigns your SignalWire Project ID as the username, and the API token as password. You will find the Project ID and API token at your SignalWire Dashboard ([explained here](/docs/browser-sdk/v3/js/guides/build-a-video-app#obtaining-your-api-key-and-project-id)). We will use this for [basic auth](https://en.wikipedia.org/wiki/Basic_access_authentication) to authenticate with the SignalWire REST API. The constant `SPACE_NAME` is your SignalWire username which you also use as the subdomain to access your Dashboard. 3. We perform an HTTP POST request using Axios to the [room\_tokens](/docs/apis/video/room-tokens/create-room-token) endpoint. We will send the name of the room, the name of the user, and the array of permissions for the user to this endpoint. We will also give axios the Project ID and the API token to be encoded as basic authentication header. If all goes well, the SignalWire server will send us a token that we can forward to the client. ![Thunder Client showing a GET query being used to test the /api/token endpoint.](https://files.buildwithfern.com/signalwire.docs.buildwithfern.com/docs/888e17b1d44413865f0cbf8924ad045d59e44a2a3fc1f9414bc0434d4469b859/assets/images/project/zoom-clone-2/backendtest.webp) This simple backend will suffice to be able to conduct video conferences. But we will have one more endpoint to add here to support room previews. ## Frontend We will rely heavily on the SignalWire Community React library ([@signalwire-community/react](https://www.npmjs.com/package/@signalwire-community/react)) to write the frontend. ### Basic Video Feed Consider the following piece of code. ```tsx title="pages/rooms/[roomName]/index.ts" // other imports import { Video } from "@signalwire-community/react"; export default function Room() { const router = useRouter(); const { roomName, userName, mod } = router.query; const [roomSession, setRoomSession] = useState(); const { data: token } = useSWRImmutable( roomName !== undefined ? `/api/token?room_name=${roomName}&user_name=${userName}&mod=${mod}` : null ); if (!router.isReady) { return Loading; } if (roomName === undefined || roomName === "undefined") return Error; return ( {token?.token && ( ); } ``` A few things to note about this code are: 1. Next.js router places it at `/rooms/[roomName]` where `roomName` can be any URL-safe string. So `/rooms/guest` should take you to the guest room automatically. The dynamic `roomName` parameter is accessible at `useRouter().query.roomName`. The `userName` and `mod` parameters should come from the URL query string (`/rooms/guest?userName=user&mod=false`) 2. We are using the immutable variant of the [swr library](https://swr.vercel.app/) to load the token. The React hook `useSWRImmutable` sends a GET request to `/api/token` just once after it is instantiated. we made `/api/token` in the previous section. We are using swr for convenience here, but you are free to use any way to `HTTP GET /api/token`. 3. The `