# Build your first call Create a room on your server, then join it from React. ## Requirements - A running RelayRTC API, signaling service and media service. - A secret API key with `rooms:create` and `tokens:create`. - Node.js 22 or newer and a React application. - Camera and microphone permission. Use HTTPS in deployment; localhost works for development. [Install the SDKs](/installation) before following this page. ## Configure your server Keep these values on your backend. These URLs are for a local RelayRTC deployment. ```bash RELAYRTC_API_URL=http://localhost:8082/v1 RELAYRTC_SIGNALING_URL=ws://localhost:8081/v1/connect RELAYRTC_SECRET_KEY=your-secret-api-key ``` > **Keep secret keys on your server** > Never place a secret key in frontend code, a public environment variable or a participant token response. Authenticate your own users before issuing tokens. ## Create a room ```ts import { RelayServer } from '@relayrtc/node' const relay = new RelayServer({ apiUrl: process.env.RELAYRTC_API_URL!, apiKey: process.env.RELAYRTC_SECRET_KEY!, }) const room = await relay.rooms.create({ name: 'Team call', maxParticipants: 10, }) ``` Save `room.id` in your application. Everyone in the same call needs a token for that room. ## Issue participant tokens Create a token for each person, not one token shared by the whole room. ```ts const issued = await relay.tokens.create(room.id, { participantName: 'Alice', ttlSeconds: 3600, permissions: [ 'room:join', 'audio:publish', 'video:publish', 'screen:publish', 'messages:send', 'metadata:update', ], }) const joinResponse = { token: issued.token, expiresAt: issued.expiresAt, signalingUrl: process.env.RELAYRTC_SIGNALING_URL, } ``` Return `joinResponse` from your authenticated backend endpoint. Do not return the secret key. ## Connect from React In Next.js, put this component in a client module. ```tsx 'use client' import { RelayProvider, RelayRoom, useLocalMedia, useLocalParticipant, useRoom } from '@relayrtc/react' function Controls() { const local = useLocalParticipant() const { camera, microphone } = useLocalMedia() const { connectionState, error } = useRoom() return (

{connectionState}

{error &&

{error.message}

}
) } export function Call({ token, signalingUrl }: { token: string; signalingUrl: string }) { return ( ) } ``` Joining a room does not open the camera or microphone. The controls request capture permission when clicked. The next two sections define RemoteCameras and RemoteAudio. Keep the React examples in the same client module, or export and import those components from separate files. ## Render remote video ```tsx import { MediaLoader, useMediaTrack, useTracks } from '@relayrtc/react' import type { RoomRemoteTrack } from '@relayrtc/react' function RemoteVideo({ track }: { track: RoomRemoteTrack }) { const media = useMediaTrack(track) return (
) } export function RemoteCameras() { const tracks = useTracks({ types: ['camera_video'] }) return tracks.map((track) => ) } ``` ## Render remote audio Audio uses a separate element, including audio captured with a shared screen. Use the same imports as the video example. ```tsx function RemoteSound({ track }: { track: RoomRemoteTrack }) { const media = useMediaTrack(track) return (
) } export function RemoteAudio() { const tracks = useTracks({ types: ['audio', 'screen_audio'] }) return tracks.map((track) => ) } ``` The browser may require a click before playing remote audio. See [useMediaTrack](/reference/sdk/client/react/hooks/useMediaTrack). ## Test with a second participant Open your app in another browser and issue another token for the same room. Enable the microphone and camera on both sides. Next, add [screen sharing](/guides/screen-sharing), [messages](/guides/messages-and-events) and [camera-off placeholders](/guides/camera-and-microphone).