With the JavaScript Nexmo Client SDK, you can provide a front-end application that allows users to control the conversations they're part of. A Nexmo Conversation can include two users or many, and use multiple different media. Letting participants control elements of the conversation only opens up more options for what your app can provide.
React is a very popular choice for building apps for the front-end, and concepts like Conversations and Users within the Nexmo Client SDK map nicely to React components with their own stateful controls. However, there are some things the Nexmo Client SDK can't do, and so the full picture of your application doesn't emerge until we also consider the back-end. Using Express, you can add a few simple routes to support your front-end in managing users and do any other work you feel is best kept on the server.
Unlike a traditional server-provided application, your React front-end is an application in its own right, which means your "full stack" app is really two applications. Each will listen on its own port and respond to requests without checking in with the other side. What this looks like from a file structure perspective is an app within an app. You'll set up your Express server in the root of your directory, then add the React app—complete with its own separate package.json
—in a subdirectory.
Application Setup
At the root of the project directory, you'll start by creating a package.json
for your Express app and a server.js
that will contain it. You'll also want to create a .env
file that will store your sensitive application and account credentials. You'll need to install a few packages from npm: Express, body-parser, dotenv, and of course the Nexmo Node SDK:
For starting both your applications at once, you'll also want concurrently
installed as a dev dependency:
Next you'll provide the keys, IDs, and secrets needed to identify your Nexmo application, which you can store in your .env
file:
You can find your API key and secret on the Getting Started page of your Nexmo dashboard. You can get an Application ID and a generated private key to download from the Create an application page (if your application will is for messaging, you can use the Create a Messages application page instead). In the example, the private key is saved in the root of your directory, so be sure to update the path in .env
if you move it elsewhere.
After completing that setup, you can move on to creating the client app.
Create a React App
You can quickly create some scaffolding for your client using the very handy create-react-app. Since this React app will live in a subdirectory of your project, you can specify the subdirectory name you prefer when you run the command (though you may want to change the application name to something more descriptive once it generates its package.json
). In our example, we've called the subdirectory "client":
This will give you most of what you need on the client, including React's dependencies and a set of scripts to do things like start and build your app. The only additional package you'll need from npm is the Nexmo Client SDK:
In your client package.json
you'll also want to add a proxy, which will refer to your Express server and the port it's running on:
"proxy": "http://localhost:3001",
Express Server
Initializing your Express server should seem familiar if you've worked with Express before. You'll also want to require dotenv
and body-parser
, the latter of which you'll attach to your app as middleware:
require('dotenv').config();
// init server
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
app.use(bodyParser.json());
Next you'll want to create a new Nexmo client, supplying it the variables you saved in your .env
file, which dotenv
will make accessible as members of process.env
:
// create a Nexmo client
const Nexmo = require('nexmo');
const nexmo = new Nexmo({
apiKey: process.env.API_KEY,
apiSecret: process.env.API_SECRET,
applicationId: process.env.APP_ID,
privateKey: __dirname + process.env.PRIVATE_KEY
}, {debug: true});
For this simple example, we'll only create endpoints for getting a JWT and creating a new user. You can define the signatures of your endpoints now, and we'll supply the logic for both apps together in a later step. Finally, of course, you'll want your Express server to listen on the port you specified in React's package.json
:
app.post('/getJWT', function(req, res) {});
app.post('/createUser', function(req, res) {});
app.listen(3001);
React App Component
When you ran create-react-app
, it should have created an entrypoint to your application at src/index.js
in your client subdirectory. This loads the component defined in src/App.js
and renders it as the body of your landing page. This component is an ideal place to do administrative tasks like obtaining a JWT and logging in to your Nexmo application. It's also a good container for two child components: User
and Conversation
. To begin with, import two components you'll create in a moment:
import React from 'react';
import User from './User';
import Conversation from './Conversation';
import nexmoClient from 'nexmo-client';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.login = this.login.bind(this);
this.getJWT = this.getJWT.bind(this);
this.userUpdated = this.userUpdated.bind(this);
}
login() {}
getJWT() {}
userUpdated() {}
render() {}
};
export default App;
While we're at it, you can see we've also imported the Nexmo Client SDK and cleaned up the created class so it's ready to populate with our logic. We've also added placeholders for the functions we'll need.
Now that you have references to the two child components (even though we've yet to create them), you can update your render function to load them on your page:
render() {
return (
<div classname="nexmo">
<user onupdate="{this.userUpdated}">
<conversation app="{this.state.app}" loggedin="{!!this.state.token}">
</conversation></user></div>
);
}
Logging in the Nexmo Application
The User
component is going to call userUpdated
when it wants to report changes to its state, so that function becomes the first link in your chain of execution. You'll look for a username
property on the state object you receive and, if it exists, continue on to get a JWT for that user:
userUpdated(user) {
if (user.username) {
this.getJWT(user.username);
}
}
Your getJWT
function will mostly consist of a fetch
and the handling of its response. You'll need to POST
the username the function receives to the Express server as JSON, then parse the data and save your new JWT as the state property token
. With that done, you can call the login
function to finish initializing your application:
getJWT(username) {
fetch('/getJWT', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({name: username})
})
.then(results => results.json())
.then(data => {
this.setState({
token: data.jwt
});
this.login();
});
}
Getting a JWT from the Server
Let's very quickly jump back over to your Express app in server.js
and provide the endpoint the client-side getJWT
function will call. Using the Nexmo Node SDK, we can generate a JWT by providing our application ID once again; the sub
, or username, we're sending from the client; an expiration in seconds; and the permissions we want this token to have. In the code below, the user can do things involving user, conversations, sessions, and applications, which is enough for our very simple app:
app.post('/getJWT', function(req, res) {
const jwt = nexmo.generateJwt({
application_id: process.env.APP_ID,
sub: req.body.name,
exp: Math.round(new Date().getTime()/1000)+3600,
acl: {
"paths": {
"/v1/users/**":{},
"/v1/conversations/**":{},
"/v1/sessions/**":{},
"/v1/applications/**":{}
}
}
});
res.send({jwt: jwt});
});
Now that your server is sending a token to the React app, you can return to App.js
and provide the logic for your final function, login
. There really isn't much to do. Using the new token saved in the App component's state, you log in the Nexmo client and receive a reference to your logged-in Nexmo app. You can save that to the component's state, and that's this component done!
login() {
let nexmo = new nexmoClient();
nexmo.createSession(this.state.token).then(app => {
this.setState({
app: app
});
});
}
React User Component
Since your App component is waiting on the User component to trigger the login flow, let's create that component now. In a new User.js
file in the same directory as App.js
, you can provide an outline of the component:
import React from 'react';
class User extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.createUser = this.createUser.bind(this);
this.setUsername = this.setUsername.bind(this);
};
createUser() {}
setUsername() {}
render() {}
};
export default User;
In a real-world application, you'd want to use a system for managing and storing your users, as well as authenticating them. For this minimal example, however, you're just going to create a new user each time you access this page. In your render
function you can check whether the component's state contains a userId
property. If so, you can post a message confirming the user is logged in. If not, you can show a text field and button to create the new user:
render() {
if (this.state.userId) {
return (
<div classname="userinfo userconnected">
Connected as <span classname="username">{this.state.username}</span>
</div>
);
} else {
return (
<div classname="userinfo">
<input type="text" onchange="{evt" ==""> this.setUsername(evt)} />
<button onclick="{this.createUser}">Create user</button>
</div>
);
}
}
Creating New Users
Creating the user is actually a two-part process that begins with listening for changes to the text in your text field and storing the updated value. If you wanted to begin making this app more robust, you could choose to start by checking that value against your username rules and list of existing users and messaging validity or duplication problems to the user via a styling change. But for this example, we'll just naively store whatever text the user has typed:
setUsername(evt) {
this.setState({
username: evt.target.value
});
}
Once the user clicks the "Create user" button, you can fire off another request to your Express server. You'll send the username as stored in the state by setUsername
, and when the server responds, trigger the onUpdate
function provided by the App
component when this component was instantiated:
createUser() {
fetch('/createUser', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({name: this.state.username})
})
.then(results => results.json())
.then(data => {
this.setState({
userId: data.id
}, () => this.props.onUpdate(this.state));
});
}
The createUser
endpoint is the last bit of your Express server we've yet to provide, so let's go back to server.js
and fill in that logic. You can call users.create
on your Nexmo object, passing in the username from the client and an optional display name (which we haven't included in the client code for this app, but you could choose to provide later). If that succeeds, you'll pass the new user's ID back to the client:
app.post('/createUser', function(req, res) {
nexmo.users.create({
name: req.body.name,
display_name: req.body.display_name || req.body.name
},(err, response) => {
if (err) {
res.sendStatus(500);
} else {
res.send({id: response.id});
}
});
});
Now all the logic you need for creating a user in both your React and Express apps is available, and so your React app will be able to log in and do things like creating a conversation.
React Conversation Component
The last file you need to create is Conversation.js
, in the same directory as App.js
and User.js
. The outline of the component is even smaller than the two you've already created, but in a real-world application it would probably be the component containing the most logic and probably even several child components:
import React from 'react';
class Conversation extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.createConversation = this.createConversation.bind(this);
}
createConversation() {}
render() {}
};
export default Conversation;
This component's render
function really only needs to provide a button, but you can make it a little nicer by disabling the button until the App
component notifies this Conversation
it's done its initialization work. You can then hide the button once the conversation is joined and a reference to it stored in the component state:
render() {
if (this.state.conversation) {
return (
<div classname="conversation">Joined conversation!</div>
);
} else {
return (
<div classname="conversation">
<button onclick="{this.createConversation}" disabled="{!this.props.loggedIn}">Start conversation</button>
</div>
);
}
}
Most of the logic the createConversation
function relies upon comes from the Nexmo Application and Conversation objects. When the user clicks the button, you can create a new conversation with a call to the app
property passed to this component. That will return a conversation, which you can join, and then save as a state property:
createConversation() {
this.props.app.newConversation().then(conv => {
conv.join().then(member => {
this.setState({
conversation: conv
});
});
});
}
From here, you could invite other users to the conversation, begin providing handlers for conversation events, or open an audio stream to let participants speak to each other.
Starting the Apps
You want to be able to start your Express and React apps as though they were one, and so the last thing you need to do is provide the mechanism for them to start up together. You've already edited React's package.json
to be aware of the Express app; now it's time to add some scripts to Express's package.json
so that npm start
actually starts everything.
In the package.json
at the root of your project, add or modify three scripts: start
, client
, and server
:
"scripts": {
"client": "cd client && npm start",
"server": "node server.js",
"start": "concurrently --kill-others-on-fail \"npm run server\" \"npm run client\""
},
The concurrently
package you installed at the beginning of this tutorial will start the Express server at the same time as it navigates to the client directory (note that you must change "client" to the name of your React subdirectory if you called it something else) and runs the start
script provided by create-react-app
. If you run npm start
now, you should be able to open a browser to the React app at http://localhost:3000
and see your application running.
Want to see a slightly more complex version of this app in action? You can view the extended code on Glitch and remix it to experiment further with Nexmo Conversations. And now that you have the fundamentals, you can continue on to build a chat app with React and Nexmo.
I'm a JavaScript developer and a Developer Educator at Vonage. Over the years I’ve been really excited about templates, Node.js, progressive web apps, and offline-first strategies, but what I’ve always really loved is a useful, well-documented API. My goal is to make your experience using our APIs the very best I can help it be.