While you can get started with the Vonage Video API with very little code, you will still require a server to handle session and token generation. If you have a server to host a basic Express.js application, we have a delightful blog post on building a basic video chat, but not everyone does.
Enter serverless—functions that are hosted on the web and only run when they're needed. Gone are the days of needing to manage an entire server for one little application, and Netlify has a wonderful low-barrier to build and host serverless functions.
To keep track of sessions that exist in your application, you'll also need a hosted database. FaunaDB has a similarly low barrier to get started and is what will be used in this tutorial. Let's get started...
The full code is available on GitHub at https://github.com/nexmo-community/netlify-functions-video-conf
Prerequisites
- Node.js installed on your machine
- A Vonage Video API account
- A Fauna account
- A Netlify account
- A GitHub account
Create a new directory and navigate to it using your terminal, then create a package.json
file by typing npm init -y
. Once this has completed, install the project dependencies by running npm install encoding faunadb netlify-lambda opentok
.
Create a Vonage Video API Project
Open your Vonage Video API dashboard and create a new API project. You can call it anything, and leave the codec as VP8.
Create a file in your project directory called .env
and populate it with your API key and secret like so:
VONAGE_KEY=YOUR_KEY
VONAGE_SECRET=YOUR_SECRET
Set Up A Netlify Function Locally
The netlify-lambda package allows you to run Netlify Functions on your own machine. To get started, add two scripts to package.json
"netlify:serve": "netlify-lambda serve functions/src",
"netlify:build": "netlify-lambda build functions/src"
Create a functions
directory, and a src
folder inside of that. In the src
folder, create a hello.js
file. You'll use this basic function to test everything is set up correctly.
Create a netlify.toml
file and put the following configuration in it:
[build]
functions = "./functions/build"
This configuration tells Netlify that your built functions will be accessible in the ./functions/build
directory, which doesn't yet exist. Run npm run netlify:serve
in your terminal, and a build
directory will appear. Files in this directory are automatically generated and updated, so keep your changes to src
, or they may be overridden.
Put the following in your hello.js
file:
const headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type'
}
exports.handler = async (event, context) => {
try {
return { headers, statusCode: 200, body: 'ok' }
} catch(e) {
console.error('Error', e)
return { headers, statusCode: 500, body: 'Error: ' + e }
}
}
Open a new terminal and test your endpoint by running curl http://localhost:9000/hello
. You should get a response of ok
.
Set Up a Fauna Database
Every Vonage Video Session has a Session ID, which you must be able to store and later retrieve. In this application, they will also have a name that users can use to refer to the sessions.
Log in to your Fauna DB Console and create a new database. Once in this database, create a new collection called sessions
, which will store your session ID and names.
Create a Database Index
Indexes are used to define common searches that will take place in our database. Click indexes in the sidebar and create a new index with the following settings:
- Source Collection: sessions
- Index Name: sessions_by_name
- Terms: data.name
- Unique: checked
This index now exists to search sessions by their friendly name, which you will store in a property called name
.
Get a FaunaDB Access Key
You will require an access key to write and read data in this database. Click security in the sidebar and create a new key with an admin role. Copy the key's secret and add the following line to the bottom of your .env
file:
FAUNA_SECRET=YOUR_SECRET
Create and Store a Video Session
Copy and paste the entire contents of your hello.js
file into a new session.js
file in the same directory. At the very top of the file, add the following:
import dotenv from 'dotenv'
dotenv.config()
import faunadb, { query as q } from 'faunadb'
const client = new faunadb.Client({ secret: process.env.FAUNA_SECRET })
const OpenTok = require("opentok");
const OT = new OpenTok(process.env.VONAGE_KEY, process.env.VONAGE_SECRET);
This loads your .env
file into the application, imports and initializes the FaunaDB and OpenTok (now Vonage Video API) clients.
At the very bottom of your file, add a createSession
function:
const createSession = (name) => {
return new Promise((resolve, reject) => {
OT.createSession(async (error, session) => {
try {
if(error) { throw error }
// Send document to FaunaDB
const document = await client.query(
q.Create(
q.Collection('sessions'),
{ data: { name, id: session.sessionId }}
)
)
resolve(document)
} catch(e) {
reject(e)
}
})
})
}
This function takes a friendly session name, creates a new Session ID through the Vonage Video API client, and stores it in our Fauna database.
Finally, update the content in the exports.handler
try block to the following:
if (event.httpMethod == 'OPTIONS') {
return {
headers: { ...headers, 'Allow': 'POST' },
statusCode: 204
}
}
const { name } = JSON.parse(event.body)
const document = await createSession(name)
return {
headers,
statusCode: 200,
body: JSON.stringify(document)
}
When you trigger a request from an application, it may send an OPTIONS HTTP request to ask which methods are allowed. If this is the case, return an additional Allow
header. By returning, the rest of the code is skipped.
Once our main request is sent, you get the name value sent by the client, and create a new session. As the body in the response needs to be a string, the returned document is stringified and returned to the user.
Try it out! Restart your netlify-lambda application, open your terminal, and run this command:
curl --header "Content-Type: application/json" --request POST --data '{"name": "kevins-call"}' http://localhost:9000/session
The response should be a full document from FaunaDB. In your FaunaDB Console you should now see your stored document.
Try running the curl command again, and notice an error as there can't be two items with the same name (based on our uniqueness constraint placed on the collection through the index).
Check for an Existing Session
When a user enters the name of an existing session, the application should instead return the document instead of trying to create a new one. If new sessions are created with every connection, each user will end up in a one-person session, which isn't very social.
Replace this line:
const document = await createSession(name)
With this snippet:
const doesSessionExist = await client.query(
q.Exists(q.Match(q.Index('sessions_by_name'), name))
)
let document
if(doesSessionExist) {
document = await client.query(
q.Get(q.Match(q.Index('sessions_by_name'), name))
)
} else {
document = await createSession(name)
}
The first step is to check if a document exists with the current name. Using FaunaDB's q.Exists()
method with the index created earlier results in either a true
or false
value being stored in doesSessionExist
.
If the session does exist, get the document. If it doesn't, create one. At the end of this snippet, the document
variable will contain a document.
Generate a Token
For users to authenticate when connecting to a Vonage Video Session, they must be provided a token from our endpoint.
Below the code you just wrote, and above the return statement, generate a token using the Vonage Video API client:
const token = OT.generateToken(document.data.id, {
role: 'publisher',
data: `roomname=${document.data.name}`
})
The first parameter is a Session ID, and the second contains options for this token. There are three supported roles: subscriber (can only subscribe to other people's streams), publisher (can publish and subscribe), and moderator(publisher, and can force other clients to disconnect).
Everyone will be a publisher for this application. However, you can later add a way to distinguish user roles. The second option holds the room name, which is stored in the database with the associated Session ID.
Return Data To Client
The endpoint must return three pieces of information to the frontend—a Session ID, a token, and our Vonage Video API Key. Replace your return statement to include all of these elements:
return {
headers,
statusCode: 200,
body: JSON.stringify({
token: token,
sessionId: document.data.id,
apiKey: process.env.VONAGE_KEY
})
}
Try your endpoint again with both a new and existing session name. The response should include all three properties! This means the endpoint is performing correctly, and you can move on to building a basic frontend to utilise it.
Build Frontend
Create a index.html
file in the main directory of your project. As the frontend client will be minimal, all of your code will live in this one file. Write the required HTML:
<html>
<head></head>
<body>
<form id="registration">
<input type="text" name="name" placeholder="Enter room name" required />
<button>Enter</button>
</form>
<div id="call">
<div id="subscriber" class="subscriber"></div>
<div id="publisher" class="publisher"></div>
</div>
<script src="https://static.opentok.com/v2/js/opentok.min.js"></script>
<script>
// Other logic will go here
</script>
</body>
</html>
When the button is pressed, submit a request to the endpoint and check the data is being returned correctly. Inside of your empty <script>
tag:
const form = document.getElementById("registration")
form.addEventListener("submit", event => {
event.preventDefault()
form.style.display = "none"
fetch("http://localhost:9000/session", {
method: "POST",
body: JSON.stringify({ name: form.elements.name.value })
}).then(res => {
return res.json();
}).then(res => {
console.log(res);
}).catch(handleCallback);
})
function handleCallback(error) {
if (error) {
console.log("error: " + error.message);
} else {
console.log("callback success");
}
}
Your browser console should look like this:
Now that the frontend has all of the data it requires, you can initialize the session in your browser, create a new publisher and publish it to the session. Replace console.log(res);
with the following:
const { apiKey, sessionId, token } = res;
const session = OT.initSession(apiKey, sessionId);
const publisher = OT.initPublisher(
"publisher",
{ insertMode: "append", width: "100%", height: "100%" },
handleCallback
);
session.connect(token, error => {
if (error) {
handleCallback(error);
} else {
session.publish(publisher, handleCallback);
}
});
session.on("streamCreated", event => {
session.subscribe(
event.stream,
"subscriber",
{ insertMode: "append", width: "100%", height: "100%" },
handleCallback
);
});
The "publisher"
and "subscriber"
parameters refer to the HTML id
values on the elements which will contain the video streams.
Open index.html in your browser, type a room name and hit the button. After giving permission to the page to access your microphone and camera, you should see a video (publisher) stream appear.
Host on Netlify
Create a .gitignore
file in your project directory and include the following code in it to avoid pushing these files and directories to a public space.
node_modules
functions/build
.env
Create a new GitHub repository and follow the steps to 'create a new repository on the command line'.
Once completed, go to your Netlify account and click New site from Git. Choose your GitHub repository and make the Build command npm run netlify:build
.
Go to the Environment variables section of the build & deploy settings for this project. Enter your three environment variables from your local .env
file.
The final step is to reference your hosted Netlify function in your frontend client when hosting online, and your localhost URL when offline.
Locate this line in index.html
:
fetch("http://localhost:9000/session", {
Replace it with the following:
let url;
if(location.hostname == 'localhost' || location.hostname == "127.0.0.1") {
url = "http://localhost:9000/session"
} else {
url = "YOUR_NETLIFY_URL/.netlify/functions/session"
}
fetch(url, {
Create and push a new commit to GitHub, and your Netlify function should re-deploy. Once it's done, visit your Netlify URL and give it a whirl!
What Next?
This application works really well, but it isn't very nice looking. You may consider taking some styling cues from our Express.js tutorial. Other entries in the series cover adding text chat, sharing your screen, or using the other provided roles as part of the Vonage Video API. Remember—for every endpoint that exists in those tutorials, you should create a new serverless function in the functions/src
directory.
The completed code for this project is also on GitHub.
You can read more about the Vonage Video API through our documentation, and if you need any additional support, feel free to reach out to our team through our Vonage Developer Twitter account or the Vonage Community Slack.