Have you ever wanted to include video in your app but weren’t sure how to get started? Well, Twilio Programmable Video provides a friendly API you can use to make this happen.
This tutorial will show you how to build an API to create and manage video rooms programmatically using Twilio Programmable Video, Node.js, TypeScript, and Express.
Prerequisites
- A free Twilio account. (If you register here, you'll receive $10 in Twilio credit when you upgrade to a paid account!)
- Node.js installed on your machine.
- HTTPie or cURL.
Set up your environment
Open your terminal, find the place you want to set up your project, and create a new directory called express-video-api where your project will live:
mkdir express-video-api cd express-video-api
Next, set up a new Node.js project with a default package.json file by running the following command:
Install dependencies
Express is the backend framework for Node.js that you'll be using in this tutorial to build out your API. Add Express to your project by running the following command:
Then, add TypeScript and the required types for Node.js and Express to your project. Since you only need these during development, you can add them as devDependencies
by running the following command:
npm install --save-dev typescript @types/express @types/node
Add ts-node
and node-dev
to the project as well, using the command below. These packages will allow your Node.js process to reload whenever you change something in your files. Adding dotenv
also allows you to load environment variables into your project from a .env file.
npm install --save-dev node-dev ts-node dotenv
Finally, install the twilio
package, using the command below. This package will let you access the Twilio Programmable Video APIs.
If you check your package.json file now, you should notice that all of the packages above have been installed as either dependencies
or devDependencies
.
Secure and configure environment variables
Store environment variables in a .env file
Create a .env file at the root of your project. This is where you will keep your Twilio account credentials.
Open the .env file in your text editor and add the following variables:
TWILIO_ACCOUNT_SID=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX TWILIO_AUTH_TOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
You’ll need to replace the placeholder text above with your actual Twilio credentials, which can be found in the Twilio Console. Log in to the Twilio console and find your Account SID and Auth Token.
Copy and paste the values for Account SID and Auth Token into your .env file. It’s very important to keep these credentials safe, so you’ll want to create a .gitignore file to avoid committing the .env file to your git repository.
In the root directory of your project, create the .gitignore file using the following command:
Then open .gitignore in your code editor and add the .env file:
Access environment variables from a config.ts file
Now that you’ve got your environment variables set up, create a new directory named src, where your source files will live. Inside that directory, create a new file called config.ts that will allow your API to access these variables:
mkdir src cd src touch config.ts
Adding the following code to src/config.ts:
import * as dotenv from 'dotenv'; dotenv.config(); const config = { TWILIO_ACCOUNT_SID: process.env.TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN: process.env.TWILIO_AUTH_TOKEN }; export default config;
Now you’re ready to configure your TypeScript setup.
Set your TypeScript configuration with a tsconfig.json file
Move up one directory, if you’re still inside the src folder. Then run the following command to create a tsconfig.json file:
node_modules/.bin/tsc --init
If you review this file in your code editor, you’ll find that there are a ton of possible settings for your TypeScript compiler there. To learn more about these settings, you can check out the documentation for TSConfig.
You’ll only need to set a few of the settings for this project, so replace the contents of tsconfig.json with the lines below:
{ "compilerOptions": { "target": "es5", "module": "commonjs", "outDir": "./build", "rootDir": "./src", "strict": true, "esModuleInterop": true, } }
The most important settings to notice here are:
target
: This sets the particular version of JavaScript you want to target in your project. For this project we’re targeting ES5. rootDir
and outDir
: In this project, the compiler will look for TypeScript files in src (rootDir
) and output the processed files to the build directory (outDir
).
Okay, now that you’ve got your project set up, it’s time to start building the video API!
Build the video API
Configure the Express server
Start by setting up your Express server and getting it running. Inside the src directory, create a file called index.ts.
Create another directory inside src called routes. Inside this folder, create a file called room.ts. This is where you'll build the routes for your video room API.
mkdir routes cd routes touch room.ts
When you’ve completed this step, you should have a file structure like this:
├── node_modules │ └── ... (long list of modules here) ├── .env ├── .gitignore ├── package-lock.json ├── package.json ├── src │ ├── config.ts │ ├── index.ts │ └── routes │ └── room.ts └── tsconfig.json
Open src/index.ts in your code editor and add the following code to create your Express server:
import express from 'express'; const app = express(); app.use(express.json()); app.listen(3000, () => { console.log('Express server listening on port 3000'); });
Locate the scripts
section of your package.json file. Add a comma after the test
script, then add the following start
script on the line below that:
"start": "node-dev src/index.ts"
It’s time to run your server! From the command line, run the following command:
You should notice log records similar to these printed in your terminal:
> [email protected] start > node-dev src/index.ts Express server listening on port 3000
You may or may not have a longer file path before
[email protected] start
, but as long as the
Express server listening on port 3000
log is present, you’re ready for the next step.
Because you're using node-dev
here, the server will automatically reload when you change the code in your *.ts
files. So there's no need to worry about stopping and restarting the server every time you make a small change. It’ll happen automatically! Isn’t that cool?
Leave the server running in one tab of your terminal window. Open another tab to continue on with the rest of the tutorial.
Build the routes
To manage your video rooms, you'll want a way to create a new room, list all of the in-progress rooms, get a specific room by its unique ID, and change a room's status from in-progress
to completed
when it's time for the video call to end. In this tutorial, you'll be using the Express router to handle these requests. The following section will walk you through building each of these routes in your API.
Create a new video room
Begin by setting up a new router. Open src/routes/room.ts and add these imports at the top of the file:
import config from '../config'; import { Router } from 'express'; import twilio from 'twilio';
Then, create an interface to work with room data. Interfaces are TypeScript’s way of defining the syntax that a particular data type—in this case, an object—should conform to.
Add the following to the room.ts file:
interface Room { name: string; sid: string; }
Next, create a new router called roomsRouter
below the newly defined interface.
const roomsRouter = Router();
Create a new instance of the Twilio client, using the environment variables from src/config.ts.
const twilioClient = twilio(config.TWILIO_ACCOUNT_SID, config.TWILIO_AUTH_TOKEN);
Export roomsRouter
so it can be used elsewhere in your API.
export default roomsRouter;
Your file should look like this now:
import config from '../config'; import { Router } from 'express'; import twilio from 'twilio'; interface Room { name: string; sid: string; } const roomsRouter = Router(); const twilioClient = twilio(config.TWILIO_ACCOUNT_SID, config.TWILIO_AUTH_TOKEN); export default roomsRouter;
It's time to add your first route. In room.ts and right above the export
line, add the following code:
/** * Create a new video room */ roomsRouter.post('/create', async (request, response, next) => { // Get the room name from the request body. // If a roomName was not included in the request, default to an empty string to avoid throwing errors. const roomName: string = request.body.roomName || ''; try { // Call the Twilio video API to create the new room. const room = await twilioClient.video.rooms.create({ uniqueName: roomName, type: 'group' }); // Return the room details in the response. return response.status(200).send(room) } catch (error) { // If something went wrong, handle the error. return response.status(400).send({ message: `Unable to create new room with name=${roomName}`, error }); } });
If you haven't worked with async/await before, or if you want a refresher, check out the explanations in this other great tutorial about async and await in JavaScript.
When you POST your request to this /create
endpoint with a room name in the request body, the code will call the Twilio Video Rooms API to create the video room. The API will respond with a 200 OK
response along with the details of the created room, which you will be able to find in your terminal window. Once you create a room, it will stay active for 5 minutes even if no one joins the video chat.
In the case that something goes wrong, the error is caught in the catch
statement, and a helpful error message will show up in your terminal.
Now that you've got a new route in your router, make sure to include the router in your main src/index.ts file so that the file looks like the example code below.
import express from 'express'; import roomsRouter from './routes/room'; /* NEW */ const app = express(); app.use(express.json()); // Forward requests for the /rooms URI to our rooms router app.use('/rooms', roomsRouter); /* NEW */ app.listen(3000, () => { console.log('Express server listening on port 3000'); });
Now seems like a great time to test your API. I personally like HTTPie, but you can also use cURL if you’re not up for installing a new package.
In your second terminal tab, try calling the /create
endpoint with HTTPie by running the command below:
http POST localhost:3000/rooms/create roomName="Room 1"
Or, if you prefer to use a cURL request, run the following command:
curl -X POST localhost:3000/rooms/create \ -d '{"roomName":"Room 1"}' \ -H "Content-Type: application/json"
When you make your request to the /create
endpoint, a response should appear in your terminal similar to the one below:
HTTP/1.1 200 OK Connection: keep-alive Content-Length: 864 Content-Type: application/json; charset=utf-8 Date: Thu, 18 Mar 2021 19:17:34 GMT ETag: W/"<ETAG>" Keep-Alive: timeout=5 X-Powered-By: Express { "accountSid": "<ACCOUNT_SID_HERE>", "dateCreated": "2021-03-18T19:17:34.000Z", "dateUpdated": "2021-03-18T19:17:34.000Z", "duration": null, "enableTurn": true, "endTime": null, "links": { "participants": "https://video.twilio.com/v1/Rooms/<ROOM_SID_HERE>/Participants", "recording_rules": "https://video.twilio.com/v1/Rooms/<ROOM_SID_HERE>/RecordingRules", "recordings": "https://video.twilio.com/v1/Rooms/<ROOM_SID_HERE>/Recordings" }, "maxConcurrentPublishedTracks": null, "maxParticipants": 50, "mediaRegion": "us1", "recordParticipantsOnConnect": false, "sid": "<ROOM_SID_HERE>", "status": "in-progress", "statusCallback": null, "statusCallbackMethod": "POST", "type": "group", "uniqueName": "Room 1", "url": "https://video.twilio.com/v1/Rooms/<ROOM_SID_HERE>", "videoCodecs": [ "VP8", "H264" ] }
If you check the uniqueName
field for this created room, it should match the "Room 1" that you entered above. This means that your video room is created and alive online!
List active video rooms
Now that you are able to create new video rooms, wouldn’t it be great to get a list of all the in-progress rooms?
This next route has a similar structure to the one you wrote in the previous section. In this one, however, you’ll use the interface you created earlier in the tutorial to return only select data about each room—the room’s unique identifier (sid
) and its name (uniqueName
).
Add the following code below the /create
route and just above the export
line:
/** * List active video rooms * * You can also select other ways to filter/list! * For the purposes of this tutorial, though, only the in-progress rooms are returned. */ roomsRouter.get('/', async (request, response, next) => { try { // Get the last 20 rooms that are still currently in progress. const rooms = await twilioClient.video.rooms.list({status: 'in-progress', limit: 20}); // If there are no in-progress rooms, return a response that no active rooms were found. if (!rooms.length) { return response.status(200).send({message: 'No active rooms found'}); } // If there are active rooms, create a new array of `Room` objects that will hold this list. let activeRooms: Room[] = []; // Then, for each room, take only the data you need and push it into the `activeRooms` array. rooms.forEach((room) => { const roomData: Room = { sid: room.sid, name: room.uniqueName } activeRooms.push(roomData); }); return response.status(200).send({activeRooms}); } catch (error) { return response.status(400).send({ message: `Unable to list active rooms`, error }); } });
Using the HTTPie or cURL commands from the previous section, create new video rooms called “Room 2” and “Room 3”.
When you've created the new rooms, try listing these in-progress rooms by running the following command with HTTPie:
http GET localhost:3000/rooms/
If you’re using cURL, you can run this command:
curl -X GET localhost:3000/rooms/
The response will contain a list of the active rooms, as shown below:
HTTP/1.1 200 OK Connection: keep-alive Content-Length: 139 Content-Type: application/json; charset=utf-8 Date: Thu, 18 Mar 2021 20:42:57 GMT ETag: W/"<ETAG>" Keep-Alive: timeout=5 X-Powered-By: Express { "activeRooms": [ { "name": "Room 3", "sid": "<ONE_ROOM_SID_HERE>" }, { "name": "Room 2", "sid": "<ANOTHER_ROOM_SID_HERE>" } ] }
If there are no active rooms, you’ll see the message No active rooms found
.
Get details about an existing video room
Now that you can list the video rooms, what if you wanted to get details about a specific room? It’s time to add a route that will return that room for you.
In the room.ts file, below the route for listing rooms, add the following code to retrieve a room by its unique identifier (sid
):
/** * Get a specific room by its SID (unique identifier) * * It is also possible to get rooms by name, but this only works for in-progress rooms. * You can use this endpoint to get rooms of any status! */ roomsRouter.get('/:sid', async (request, response, next) => { const sid: string = request.params.sid; try { // Call the Twilio video API to retrieve the room you created const room = await twilioClient.video.rooms(sid).fetch(); return response.status(200).send({room}); } catch (error) { return response.status(400).send({ message: `Unable to get room with sid=${sid}`, error }); } });
Create a new video room called "Room 4" using HTTPie or cURL. Take note of the room's sid
from the response in your terminal. Then try calling the new endpoint to get this room by its sid
.
Here's the command if you are using HTTPie:
http GET localhost:3000/rooms/<YOUR_ROOM_SID_HERE>
curl -X GET localhost:3000/rooms/<YOUR_ROOM_SID_HERE>
The response in your terminal will contain the details of the selected room:
HTTP/1.1 200 OK Connection: keep-alive Content-Length: 873 Content-Type: application/json; charset=utf-8 Date: Thu, 18 Mar 2021 19:42:53 GMT ETag: W/"<ETAG>" Keep-Alive: timeout=5 X-Powered-By: Express { "room": { "accountSid": "<ACCOUNT_SID_HERE>", "dateCreated": "2021-03-18T19:42:33.000Z", "dateUpdated": "2021-03-18T19:42:33.000Z", "duration": null, "enableTurn": true, "endTime": null, "links": { "participants": "https://video.twilio.com/v1/Rooms/<ROOM_SID_HERE>/Participants", "recording_rules": "https://video.twilio.com/v1/Rooms/<ROOM_SID_HERE>/RecordingRules", "recordings": "https://video.twilio.com/v1/Rooms/<ROOM_SID_HERE>/Recordings" }, "maxConcurrentPublishedTracks": null, "maxParticipants": 50, "mediaRegion": "us1", "recordParticipantsOnConnect": false, "sid": "<ROOM_SID_HERE>", "status": "in-progress", "statusCallback": null, "statusCallbackMethod": "POST", "type": "group", "uniqueName": "Room 4", "url": "https://video.twilio.com/v1/Rooms/<ROOM_SID_HERE>", "videoCodecs": [ "VP8", "H264" ] } }
Close a video room
To complete your API, add a route for closing a video room. This will end the video chat and also disconnect any participants who are still connected to the room. With the Twilio Video Rooms API, all you need to do to close a video room is change its status from in-progress
to completed
.
For this route, you'll pass in the sid
of the room you want to change. Add the following code just above the export
line in room.ts:
/** * Complete a room * * This will update the room's status to `completed` and will end the video chat, disconnecting any participants. * The room will not be deleted, but it will no longer appear in the list of in-progress rooms. */ roomsRouter.post('/:sid/complete', async (request, response, next) => { // Get the SID from the request parameters. const sid: string = request.params.sid; try { // Update the status from 'in-progress' to 'completed'. const room = await twilioClient.video.rooms(sid).update({status: 'completed'}) // Create a `Room` object with the details about the closed room. const closedRoom: Room = { sid: room.sid, name: room.uniqueName, } return response.status(200).send({closedRoom}); } catch (error) { return response.status(400).send({ message: `Unable to complete room with sid=${sid}`, error }); } });
Create another video room called “Room 5”. Once you’ve created this room, take note of the room’s sid
in your terminal. You’ll also notice that the room has a status of in-progress
.
When you’re ready to try closing the room, run the following command with HTTPie:
http POST localhost:3000/rooms/<ROOM_SID_HERE>/complete
If using cURL, run this command instead:
curl -X POST localhost:3000/rooms/<ROOM_SID_HERE>/complete
The response in your terminal will let you know that the room is now closed:
HTTP/1.1 200 OK Connection: keep-alive Content-Length: 75 Content-Type: application/json; charset=utf-8 Date: Thu, 18 Mar 2021 22:11:10 GMT ETag: W/"<ETAG>" Keep-Alive: timeout=5 X-Powered-By: Express { "closedRoom": { "name": "Room 5", "sid": "<ROOM_SID_HERE>" } }
What to build next with your video API and Twilio Programmable Video
Being able to create and manage video rooms programmatically is a great step towards building the video-enabled app of your dreams. With this as a starting point, you could build all sorts of ideas, from EdTech to social media to telemedicine.
If you want to take a look at the code from this tutorial in its entirety, you can find it here on GitHub.
In future tutorials, you’ll have a chance to build on this API to create other interesting projects. Or perhaps you already have an idea in mind and are ready to give it a go. I can’t wait to see what you build!
Mia Adjei is a Software Developer on the Developer Voices team. They love to help developers build out new project ideas and discover aha moments. Mia can be reached at madjei [at] twilio.com.