Skip to content

OpenVidu Meet Registered Members Tutorial#

Source code

This tutorial extends the External Members tutorial to show how to create OpenVidu Meet users with the Users API and grant them access to a room as registered members.

Whereas an external member is an anonymous participant who joins through a unique link, a registered member is a real OpenVidu Meet user added to a specific room: all registered members share the room's authenticated access URL and prove their identity by logging in with their own OpenVidu Meet credentials.

Building on the External Members tutorial, it adds the following:

  • Users can create, list and delete OpenVidu Meet users.
  • Users can add a registered user (instead of an external one) as a member of a room.
  • A registered member joins the room by logging in with their OpenVidu Meet credentials.

The application uses the OpenVidu Meet API to manage users, rooms and room members, and the OpenVidu Meet WebComponent to embed the meeting.

Anonymous access vs. room members

OpenVidu Meet rooms can be accessed either through anonymous access links (used by the other tutorials) or by adding room members with personalized access and permissions. Registered members are one of the two kinds of room members (the other being external members). See Room Access for the full picture.

Running this tutorial#

1. Run OpenVidu Meet#

You need Docker Desktop. You can install it on Windows , Mac or Linux .

Run this command in Docker Desktop's terminal:

docker compose -p openvidu-meet -f oci://openvidu/local-meet:3.7.0 up -y openvidu-meet-init

Info

For a detailed guide on how to run OpenVidu Meet locally, visit Try OpenVidu Meet locally .

2. Download the tutorial code#

git clone https://github.com/OpenVidu/openvidu-meet-tutorials.git -b 3.7.0

3. Run the application#

To run this application, you need Node.js (≥ 18) installed on your device.

  1. Navigate into the application directory
cd openvidu-meet-tutorials/meet-registered-members
  1. Install dependencies
npm install
  1. Run the application
npm start

Once the server is up and running, you can test the application by visiting http://localhost:6080. You should see a screen like this:

Understanding the code#

This tutorial builds upon the External Members tutorial: the room and member management, the application views and the shared helper functions are the same. Here we focus only on what is new — the Users API — and on the small changes needed to manage registered members instead of external ones.


Backend modifications#

This tutorial adds three endpoints to manage users, and slightly changes two of the existing member endpoints:

  • POST /users: Create a new OpenVidu Meet user. (new)
  • GET /users: List the available users. (new)
  • DELETE /users/:userId: Delete a user. (new)
  • POST /rooms/:roomId/members: now adds a registered member, identified by userId instead of name.
  • GET /rooms/:roomId/members: now filters by type=registered.

The room endpoints (POST /rooms, GET /rooms, DELETE /rooms/:roomId) and the DELETE /rooms/:roomId/members/:memberId endpoint are identical to the External Members tutorial.


Create user#

The POST /users endpoint creates a new OpenVidu Meet user:

index.js
// Create a new user
app.post('/users', async (req, res) => {
    const { userId, name, password } = req.body; // (1)!

    if (!userId || !name || !password) {
        res.status(400).json({ message: `'userId', 'name' and 'password' are required` }); // (2)!
        return;
    }

    try {
        // Create a new OpenVidu Meet user using the API.
        // The 'room_member' role lets the user access only the rooms where they are added as a member;
        // they cannot create or manage rooms.
        const user = await httpRequest('POST', 'users', {
            userId,
            name,
            password,
            role: 'room_member' // (3)!
        });

        console.log('User created:', user);
        res.status(201).json({ message: `User '${userId}' created successfully`, user }); // (4)!
    } catch (error) {
        handleApiError(res, error, `Error creating user '${userId}'`);
    }
});
  1. The userId, name and password are obtained from the request body.
  2. If any of them is missing, the server returns a 400 Bad Request response.
  3. The user is created with the room_member role. Among the available roles (admin, user and room_member), room_member is the most restricted: it can only access the rooms where it has been explicitly added as a member, and cannot create or manage rooms.
  4. The server returns a 201 Created response with the created user object.

This endpoint creates a user with the OpenVidu Meet API by sending a POST request to the users endpoint, including the userId, name, password and role. The userId must be between 5 and 20 characters and contain only lowercase letters, numbers and underscores (this is validated in the frontend form). The role is set to room_member. Among the available roles (admin, user and room_member), room_member is the most restricted: it can only access the rooms where it has been explicitly added as a member, and cannot create or manage rooms.


List users#

The GET /users endpoint retrieves the list of users:

index.js
// List users
app.get('/users', async (_req, res) => {
    try {
        // List OpenVidu Meet users using the API (100 max).
        // We only list 'room_member' users, because they are the ones this tutorial creates
        const { users } = await httpRequest('GET', 'users?role=room_member&maxItems=100'); // (1)!
        res.status(200).json({ users });
    } catch (error) {
        handleApiError(res, error, 'Error fetching users');
    }
});
  1. Fetch the users by sending a GET request to the users endpoint, filtering by the room_member role (with a maximum of 100 users).

This endpoint lists only the room_member users. This keeps the tutorial focused on the users it creates and, in particular, excludes the root admin user, which cannot be deleted nor added as a room member.


Delete user#

The DELETE /users/:userId endpoint deletes a user:

index.js
// Delete a user
app.delete('/users/:userId', async (req, res) => {
    const { userId } = req.params; // (1)!

    try {
        await httpRequest('DELETE', `users/${userId}`); // (2)!
        res.status(200).json({ message: `User '${userId}' deleted successfully` });
    } catch (error) {
        handleApiError(res, error, `Error deleting user '${userId}'`);
    }
});
  1. The userId is obtained from the request parameters.
  2. Delete the user by sending a DELETE request to the users/:userId endpoint.

Deleting a user removes their account from OpenVidu Meet. Note that this will automatically remove the user from rooms where they are a member


Adapting the member endpoints#

Adding a member uses the same POST /rooms/:roomId/members endpoint as the External Members tutorial, but provides a userId instead of a name. This is the only difference, and it is what tells the API to create a member of type registered (linked to a user account) instead of external:

index.js
// Add a registered user as a member of a room
app.post('/rooms/:roomId/members', async (req, res) => {
    const { roomId } = req.params;
    const { userId, baseRole } = req.body; // (1)!

    if (!userId || !baseRole) {
        res.status(400).json({ message: `'userId' and 'baseRole' are required` });
        return;
    }

    try {
        // Add the registered user as a member of the room.
        // Providing 'userId' (and no 'name') creates a member of type 'registered':
        // the member is linked to the user account and identified through authentication.
        const member = await httpRequest('POST', `rooms/${roomId}/members`, {
            userId, // (2)!
            baseRole
        });

        console.log('Member added:', member);
        res.status(201).json({ message: `User '${userId}' added to room '${roomId}'`, member });
    } catch (error) {
        handleApiError(res, error, `Error adding user '${userId}' to room '${roomId}'`);
    }
});
  1. The request body now carries a userId (the registered user to add) instead of a name.
  2. Providing userId (and not name) is what tells the API to create a member of type registered. The member is linked to the user account, identified through authentication, and shares the same authenticated room access URL with the rest of the registered members.

Info

As in the External Members tutorial, you can fine-tune the member's permissions beyond the base role by including a customPermissions object in the request. See the addRoomMember operation in the REST API reference for the full list of permissions.

Listing members works the same way, but filters by type=registered instead of type=external:

index.js
// List the members of a room (only registered members)
app.get('/rooms/:roomId/members', async (req, res) => {
    const { roomId } = req.params;

    try {
        // List the registered members of the room using the API (100 max)
        const { members } = await httpRequest('GET', `rooms/${roomId}/members?type=registered&maxItems=100`); // (1)!
        res.status(200).json({ members });
    } catch (error) {
        handleApiError(res, error, `Error fetching members of room '${roomId}'`);
    }
});
  1. The only change from the External Members tutorial is the type=registered filter, which retrieves the registered members instead of the external ones.

Removing a member (DELETE /rooms/:roomId/members/:memberId) is exactly the same as in the External Members tutorial: it revokes the member's access immediately and expels them if they are currently in the meeting.


Frontend modifications#

The frontend adds a panel to manage users and adapts the members view to add existing users as members. The joinRoom() function also changes to join through the room's authenticated URL. The rest (rooms management, view switching and the httpRequest() wrapper) is unchanged from the External Members tutorial.


Managing users#

When the "Create User" form is submitted, the createUser() function is called:

app.js
async function createUser(e) {
    // Prevent the default form submission
    e.preventDefault(); // (1)!

    // Clear previous error message
    const errorDiv = document.querySelector('#create-user-error');
    errorDiv.textContent = '';
    errorDiv.hidden = true;

    try {
        const userId = document.querySelector('#user-id').value; // (2)!
        const name = document.querySelector('#user-name').value;
        const password = document.querySelector('#user-password').value;

        const { user } = await httpRequest('POST', '/users', {
            userId,
            name,
            password
        }); // (3)!

        // Add new user to the list
        users.set(user.userId, user); // (4)!
        renderUsers();

        // Reset the form
        e.target.reset();
    } catch (error) {
        console.error('Error creating user:', error.message);

        // Show error message
        errorDiv.textContent = error.message || 'Error creating user'; // (5)!
        errorDiv.hidden = false;
    }
}
  1. Prevent the default form submission so the page is not reloaded.
  2. Get the userId, name and password from the form inputs. The user-id input enforces the format constraints (5-20 characters, lowercase letters, numbers and underscores) through HTML validation attributes.
  3. Make a POST request to the /users endpoint to create the user.
  4. Add the new user to the local users map and re-render the list.
  5. If the request fails (for example, a duplicated userId), the error message returned by the API is shown in the form.

The renderUsers() and deleteUser() functions follow the same pattern as their room counterparts in the External Members tutorial (renderRooms() and deleteRoom()): one renders the list of users from the users map, and the other calls DELETE /users/:userId and removes the user from the list.


Adapting the members view#

In the External Members tutorial you typed a free-text name to add a member. Here you instead pick one of the existing users from a dropdown, which is populated with the users that are not yet members of the room:

app.js
// Populate the "add member" select with the users that are not already members of the room
function renderMemberUserOptions() {
    const select = document.querySelector('#member-user');
    const availableUsers = Array.from(users.values()).filter((user) => !members.has(user.userId)); // (1)!

    if (availableUsers.length === 0) {
        select.innerHTML = `<option value="" disabled selected>No users available</option>`;
        return;
    }

    select.innerHTML =
        `<option value="" disabled selected>Select a user</option>` +
        availableUsers.map((user) => `<option value="${user.userId}">${user.userId} · ${user.name}</option>`).join(''); // (2)!
}
  1. Filter out the users that are already members of the room (a registered member's memberId equals the user's userId).
  2. Build one <option> per available user, using the userId as the option value.

When the form is submitted, the addMember() function sends the selected userId (instead of a name) to the backend:

app.js
async function addMember(e) {
    // Prevent the default form submission
    e.preventDefault();

    // Clear previous error message
    const errorDiv = document.querySelector('#add-member-error');
    errorDiv.textContent = '';
    errorDiv.hidden = true;

    try {
        const userId = document.querySelector('#member-user').value; // (1)!
        const baseRole = document.querySelector('#member-role').value;

        const { member } = await httpRequest('POST', `/rooms/${currentRoom.roomId}/members`, {
            userId,
            baseRole
        }); // (2)!

        // Add new member to the list
        members.set(member.memberId, member); // (3)!
        renderMembers();
    } catch (error) {
        console.error('Error adding member:', error.message);

        // Show error message
        errorDiv.textContent = error.message || 'Error adding member';
        errorDiv.hidden = false;
    }
}
  1. Get the selected userId from the dropdown and the chosen baseRole.
  2. Make a POST request to the /rooms/:roomId/members endpoint to add the member to the current room (currentRoom is the room whose members are being managed).
  3. Add the returned member to the local members map and re-render the list.

The way each member is rendered also changes slightly: since registered members do not have an individual access link, the getMemberListItemTemplate() function shows the member's id (its userId) and a single button to remove them — there is no copy-link or per-member join button like in the External Members tutorial. Instead, joining is done once per room, as shown next.


Joining the room as a registered member#

All registered members of a room share the same authenticated access URL, available in the access.registered.url property of the room object. The joinRoom() function embeds the OpenVidu Meet WebComponent pointing to that URL:

app.js
function joinRoom() {
    // All registered members share the same authenticated access URL for the room.
    // Each member proves their identity by logging in with their OpenVidu Meet credentials.
    const roomUrl = currentRoom.access.registered.url; // (1)!
    console.log(`Joining room through URL: ${roomUrl}`);

    // Hide the members screen and show the room screen
    const membersScreen = document.querySelector('#members');
    membersScreen.hidden = true;
    const roomScreen = document.querySelector('#room');
    roomScreen.hidden = false;

    // Inject the OpenVidu Meet component into the meeting container specifying the room URL.
    // Since this URL requires authentication, OpenVidu Meet will show its own login form
    // inside the component until the member logs in.
    const meetingContainer = document.querySelector('#meeting-container');
    meetingContainer.innerHTML = `
        <openvidu-meet
            room-url="${roomUrl}"
        >
        </openvidu-meet>
    `; // (2)!

    // Add event listener for when the OpenVidu Meet component is closed
    const meet = document.querySelector('openvidu-meet');
    meet.once('closed', () => {
        // (3)!
        console.log('OpenVidu Meet component closed');

        // Clear the component and go back to the members screen
        meetingContainer.innerHTML = '';
        roomScreen.hidden = true;
        membersScreen.hidden = false;
    });
}
  1. Get the room's authenticated access URL from the access.registered.url property. Unlike the External Members tutorial (where each member had its own unique URL), this URL is the same for all registered members.
  2. Inject the OpenVidu Meet WebComponent with the room-url attribute set to that URL.
  3. Add a listener for the closed event so that, when the component is closed, the meeting is cleared and the members view is shown again.

Registered members log in inside the meeting

The authenticated access URL does not carry any secret. When the WebComponent loads it, OpenVidu Meet renders its own login form inside the component, and the member logs in with their OpenVidu Meet credentials (the userId and password created earlier). After logging in, OpenVidu Meet verifies that the user is a member of the room and lets them join with the permissions of their base role. Your application never handles the member's password: identity is proven through OpenVidu Meet's login, while membership and permissions are managed through the Room Members API.

Accessing this tutorial from other computers or phones#

To access this tutorial from other computers or phones, follow these steps:

  1. Ensure network connectivity: Make sure your device (computer or phone) is connected to the same network as the machine running OpenVidu Meet and this tutorial.

  2. Configure OpenVidu Meet for network access: Start OpenVidu Meet by following the instructions in the Accessing OpenVidu Meet from other computers or phones section.

  3. Update the OpenVidu Meet server URL: Modify the OV_MEET_SERVER_URL environment variable in your .env file to match the URL shown when OpenVidu Meet starts.

    # Example for IP address 192.168.1.100
    OV_MEET_SERVER_URL=https://192-168-1-100.openvidu-local.dev:9443/meet
    
  4. Update the OpenVidu Meet WebComponent script URL: In the public/index.html file, update the <script> tag that includes the OpenVidu Meet WebComponent to use the same base URL as above.

    <script src="http://192-168-1-100.openvidu-local.dev:9443/meet/v1/openvidu-meet.js"></script>
    
  5. Restart the tutorial to apply the changes:

    npm start
    
  6. Access the tutorial: Open your browser and navigate to https://192-168-1-100.openvidu-local.dev:6443 (replacing 192-168-1-100 with your actual private IP) on the computer where you started the tutorial or any device in the same network.

Connecting this tutorial to an OpenVidu Meet production deployment#

If you have a production deployment of OpenVidu Meet (installed in a server following deployment steps ), you can connect this tutorial to it by following these steps:

  1. Update the server URL: Modify the OV_MEET_SERVER_URL environment variable in the .env file to point to your OpenVidu Meet production deployment URL.

    # Example for a production deployment
    OV_MEET_SERVER_URL=https://your-openvidu-meet-domain.com/meet
    
  2. Update the API key: Ensure the OV_MEET_API_KEY environment variable in the .env file matches the API key configured in your production deployment. See Generate an API Key section to learn how to obtain it.

    OV_MEET_API_KEY=your-production-api-key
    
  3. Update the OpenVidu Meet WebComponent script URL: In the public/index.html file, update the <script> tag that includes the OpenVidu Meet WebComponent to use the same base URL as above.

    <script src="https://your-openvidu-meet-domain.com/meet/v1/openvidu-meet.js"></script>
    
  4. Restart the tutorial to apply the changes:

    npm start
    

Make this tutorial accessible from other computers or phones

By default, this tutorial runs on http://localhost:6080 and is only accessible from the local machine. If you want to access it from other computers or phones, you have the following options:

  • Use tunneling tools: Configure tools like VS Code port forwarding , ngrok , localtunnel , or similar services to expose this tutorial to the internet with a secure (HTTPS) public URL.
  • Deploy to a server: Upload this tutorial to a web server and configure it to be accessible with a secure (HTTPS) public URL. This can be done by updating the source code to manage SSL certificates or configuring a reverse proxy (e.g., Nginx, Apache) to serve it.