We talk a lot about sending SMS messages from web applications, but what about sending SMS messages from a React application? There's a bit more to it than just the server-side version, but it won't take us long.
Why shouldn't I use the REST API from the client-side?
Technically you could send an SMS using the Twilio REST API directly from a JavaScript client-side application. But (and it's a very big "but") if you were to do that, you would expose your Twilio credentials to anyone using your site. A malicious user could then take those credentials and abuse them, running up a huge bill with your account.
Live view of a hacker with your account credentials
To avoid this we will create a back end application that implements the Twilio REST API, wraps up your credentials and sends SMS messages for you. Then you can call your back end from your React application and send SMS messages without distributing your credentials to the internet.
Our tools
For our application to send text messages using the Twilio REST API we will need the following:
- A Twilio account and phone number that can send SMS messages (you can sign up for a Twilio account for free here)
- Node.js to build our React app and to run our server (you can build the server-side component of this in any language, but in this post we're going to do so in Node so we can keep it all JavaScript)
- React Dev Tools for your browser (optional, but really useful for seeing what goes on in the application
To get started, download or clone the react-express-starter application that I built in my last blog post.
git clone https://github.com/philnash/react-express-starter.git
Change into the directory and install the dependencies.
cd react-express-starternpm install
In the project directory, create a file called .env
:
touch .env
You can now test the project is working by running npm run dev
. The application will load in your browser at localhost:3000.
This starter application is set up to have both a React application and an Express application in the same project that you can run concurrently. If you want to find out how this works, check out this blog post.
Building the server-side
As discussed, we need to make the Twilio API calls from the server. We'll add an endpoint to the Express server that can be called from our React application. Start by installing the Twilio Node.js module. Note: for the purposes of this application I'm saving the server dependencies as development dependencies to separate them from the client-side dependencies.
npm install twilio --save-dev
Next, we need to configure the application with our Twilio credentials. Gather your Twilio Account Sid and Auth Token from the Twilio console along with a Twilio phone number that can send SMS messages. Enter all three into the .env
file you created earlier like so:
TWILIO_ACCOUNT_SID=YOUR_ACCOUNT_SID
TWILIO_AUTH_TOKEN=YOUR_AUTH_TOKEN
TWILIO_PHONE_NUMBER=YOUR_TWILIO_PHONE_NUMBER
This will set your credentials in the environment. Now, open server/index.js
so that we can get started with the code necessary for sending the message. Under the other module requires at the top of the file, require and initialise the Twilio library with the credentials from the environment.
const express = require('express');
const bodyParser = require('body-parser');
const pino = require('express-pino-logger')();
const client = require('twilio')(
process.env.TWILIO_ACCOUNT_SID,
process.env.TWILIO_AUTH_TOKEN
);
We'll be sending the data to the endpoint we're building as JSON, so we'll need to be able to parse the JSON body. Configure the Express app with body parser's JSON parser:
const app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());app.use(pino);
Make a route for a POST
request. Add the following below the route for /api/greeting
:
app.post('/api/messages', (req, res) => {
});
We're going to respond with JSON too, so set the Content-Type
header to application/json
.
app.post('/api/messages', (req, res) => {
res.header('Content-Type', 'application/json');
});
We'll then use the Twilio client we initialised earlier to create a message. We'll use our Twilio number as the from
number and get the to
number and body
of the message from the incoming request body. This returns a Promise that will fulfill when the API request succeeds or reject if it fails. In either event we will return a JSON response to tell the client-side whether the request was a success or not.
app.post('/api/messages', (req, res) => {
res.header('Content-Type', 'application/json');
client.messages
.create({
from: process.env.TWILIO_PHONE_NUMBER,
to: req.body.to,
body: req.body.body
})
.then(() => {
res.send(JSON.stringify({ success: true }));
})
.catch(err => {
console.log(err);
res.send(JSON.stringify({ success: false }));
});
});
That's all we need on the server, let's get started on the React portion.
Building the client-side
On the client-side, we can encapsulate the form to send our SMS via the server entirely in just one component. So, in the src
directory create a new component called SMSForm.js
and start with the boilerplate for a component:
import React, { Component } from 'react';
class SMSForm extends Component {
}
export default SMSForm;
We're going to create a form that a user can fill in with a phone number and message. When the form is submitted it will send the details to our server endpoint and send the message as an SMS to the number.
Let's build the render
method for this component first: it will include a form, an input for the phone number, a textarea for the message and a button to submit:
render() {
return (
<form>
<div>
<label htmlFor="to">To:</label>
<input
type="tel"
name="to"
id="to"
/>
</div>
<div>
<label htmlFor="body">Body:</label>
<textarea name="body" id="body"/>
</div>
<button type="submit">
Send message
</button>
</form>
);
}
We can add some CSS to style this form a bit. Create the file src/SMSForm.css
and add the following:
.sms-form {
text-align: left;
padding: 1em;
}
.sms-form label {
display: block;
}
.sms-form input,
.sms-form textarea {
font-size: 1em;
width: 100%;
box-sizing: border-box;
}
.sms-form div {
margin-bottom: 0.5em;
}
.sms-form button {
font-size: 1em;
width: 100%;
}
.sms-form.error {
outline: 2px solid #f00;
}
Import the CSS at the top of the SMSForm component:
import React, { Component } from 'react';
import './SMSForm.css';
Now, import the component into src/App.js
and replace the render method with the following:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import SMSForm from './SMSForm';
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<SMSForm />
</header>
</div>
);
}
}
export default App;
Start your application with npm run dev
and you'll see the form on the page.
The form doesn't do anything yet, so let's fix that.
Making an interactive form in React
To hook the HTML form up with the component we need to do a few things:
- Keep the state of the input and textarea up to date in the state of the component
- Handle submitting the form and sending the data to the server
- Handle the response from the server and clear the form if the message was sent successfully, or show an error if it wasn't
We'll start by setting up some initial state in the constructor. We'll need to store the form inputs, whether the form is currently being submitted (so that we can disable the submit button) and whether there was an error. Create the constructor for the component as follows:
class SMSForm extends Component {
constructor(props) {
super(props);
this.state = {
message: {
to: '',
body: ''
},
submitting: false,
error: false
};
}
// rest of the component
}
We'll need a method that can handle changes in the form fields and update the state. We could create two methods, one for the input and one for the textarea, but since the names of the form elements and items in the state match up we can build one method to cover both.
onHandleChange(event) {
const name = event.target.getAttribute('name');
this.setState({
message: { ...this.state.message, [name]: event.target.value }
});
}
Note here that we use ES2015's computed property names to set the right property in the state and the spread operator to fill in the rest of the state.
We'll need to bind this method to the object to ensure that this
is correct when we use it to receive an event. Add the following to the bottom of the constructor:
constructor(props) {
super(props);
this.state = {
message: {
to: '',
body: ''
},
submitting: false,
error: false
};
this.onHandleChange = this.onHandleChange.bind(this);
}
We can now update our rendered JSX to set the value of the form fields using the current state and handle updates with our onHandleChange
method:
render() {
return (
<form>
<div>
<label htmlFor="to">To:</label>
<input
type="tel"
name="to"
id="to"
value={this.state.message.to}
onChange={this.onHandleChange}
/>
</div>
<div>
<label htmlFor="body">Body:</label>
<textarea
name="body"
id="body"
value={this.state.message.body}
onChange={this.onHandleChange}
/>
</div>
<button type="submit">Send message</button>
</form>
);
}
Reload the app and you'll be able to update the form fields. If you have the React dev tools for your browser, you'll be able to see the state updating too.
Now we need to handle the form submission. Build another function, onSubmit
, that starts by updating the submitting
state property to true. Then use the fetch
API to make the request to the server. If the response is successful then clear the form and set submitting
to false. If the response is not a success, set submitting
to false but set error
to true.
onSubmit(event) {
event.preventDefault();
this.setState({ submitting: true });
fetch('/api/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(this.state.message)
})
.then(res => res.json())
.then(data => {
if (data.success) {
this.setState({
error: false,
submitting: false,
message: {
to: '',
body: ''
}
});
} else {
this.setState({
error: true,
submitting: false
});
}
});
}
Like with the onHandleChange
method, we also bind this method in the constructor:
constructor(props) {
super(props);
this.state = {
message: {
to: '',
body: ''
},
submitting: false,
error: false
};
this.onHandleChange = this.onHandleChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
}
Now, in the JSX we add the onSubmit
method as the submit handler on the form. We also set the form's class to "error" if we receive an error from the request. And while the form is submitting we set the button's disabled
property.
render() {
return (
<form
onSubmit={this.onSubmit}
className={this.state.error ? 'error sms-form' : 'sms-form'}
>
<div>
<label htmlFor="to">To:</label>
<input
type="tel"
name="to"
id="to"
value={this.state.message.to}
onChange={this.onHandleChange}
/>
</div>
<div>
<label htmlFor="body">Body:</label>
<textarea
name="body"
id="body"
value={this.state.message.body}
onChange={this.onHandleChange}
/>
</div>
<button type="submit" disabled={this.state.submitting}>
Send message
</button>
</form>
);
}
This is all we need, so refresh the app again and enter your mobile number and a message to send. Submit the form and if the details are correct then your message will be sent, if not, the form will show that the state is in error.
Sending messages and keeping your credentials safe
Sending SMS messages from a web app is cool. Sending SMS messages from your React app without exposing your credentials is even cooler ๐.
You can check out all the code from this example application in the GitHub repo.
Now that you have the basis of a React app that can send SMS messages you could make some improvements. First would likely be better validation and error messages. With a similar design you could add phone number lookups, generate phone calls or implement two factor authentication right from your React app too.
Iโd love to hear about the app youโre building with React. Drop me a comment below, hit me up on Twitter at @philnash or send an email my way at philnash@twilio.com.