Twilio is all about HTTP endpoints & webhooks. From responding to incoming SMS, to controlling the flow of a voice call to blocking unwanted chat messages with an onMessageSend
webhook, chances are that you'll end up writing an HTTP endpoint for the Twilio product you're interacting with. Twilio Functions allow you to write and host those endpoints directly in the Twilio cloud while relying on the power of Node.js.
What if you want to develop these functions with your IDE or editor of choice and run them locally? What if something goes wrong and you want to use your debugger to dive deeper into it? For this reason I built twilio-run
, a command-line tool that allows you to run your Twilio Functions in your local environment.
Let's dive into how it works, and how it can help your development flow with Twilio.
Note
This tool is still in active development and might not mirror 100% of the behavior of Twilio Functions when emulating the environment. If you find bugs please open an issue at https://github.com/dkundel/twilio-run or send me an email to dkundel@twilio.com
Installation
twilio-run
is built with Node.js and therefore requires you have Node.js installed as well as a package manager like npm
or yarn
. Once you have those prerequisites, there are multiple ways you can install and use twilio-run
.
The fastest way, if you just want to occasionally execute it, is to use npm@5.2
or newer since it has a tool called npx
. If you have npx
installed you can run:
npx twilio-run
And npm will automatically download the tool if it's the first time, and run it in the local directory. You can also pass in any of the options you'll find below.
If you want to use twilio-run
more often, I recommend to install it as a devDependency in your project. If you don't have a Node.js project yet, create a new folder and run npm init -y
before running:
npm install -D twilio-run
# or alternatively with yarn:
yarn add -D twilio-run
This will add twilio-run
into your node_modules
folder and there are multiple ways you can execute it:
# specify the path to the executable
node_modules/.bin/twilio-run
# run it using npx (this won't reinstall it)
npx twilio-run
# run it using yarn
yarn twilio-run
# Add "start": "twilio-run" into your package.json's scripts section. Then:
npm start
Now that we have twilio-run
installed, let's look at how we can use it. In the rest of the post I'll omit the respective ways to run the tool and instead only use twilio-run
. Please adapt it to the way you are running the tool.
The basics
Similar to the real Twilio Functions, we are able to host both JavaScript functions and static assets. For these twilio-run
will look for a functions/
and an assets/
directory in the path that you specified as an argument to the tool. If you don't specify a path, it will use your current working directory as the base directory.
Let's set up a basic function and create an asset to test. Inside your project directory create a functions/
folder and add a file called hello-world.js
to it. Place the following code into this file:
exports.handler = function(context, event, callback) {
let twiml = new Twilio.twiml.MessagingResponse();
twiml.message('Hello World');
callback(null, twiml);
};
Next create an assets/
directory and place a text file called hello.txt
into it. Feel free to put whatever content you want into it. I'll just place "Hello Blog!" into it.
Now that we have our basic project setup we can start twilio-run
by running:
twilio-run
Once it's started you should be greeted with an output that shows all available URLs for your Twilio function and assets.
To verify that it's working open your browser and navigate to http://localhost:3000/hello-world. You should see some TwiML returned to you:
And if you go to http://localhost:3000/assets/hello.txt you'll be able to see "Hello Blog!" or whatever message you placed into it. This will also work with any other static files you might want to serve.
Additionally you should see all successful and failed requests being logged to the console:
This is all it takes to get started with running Twilio Functions locally. Let's talk about a few additional features you have available with twilio-run
.
Exposing local Functions to the outside world
If you want to check how well your locally developed Twilio Function plays with Twilio you'll have to make it available for Twilio to contact it. The tool we tend to recommend for this is called ngrok. It creates an HTTP tunnel to your localhost. twilio-run
comes with this functionality directly built-in. All you have to do is pass the --ngrok
flag:
twilio-run --ngrok
You'll see that the output slightly differs since the tool will now return you the externally available URLs as well as the request inspector of ngrok, a great tool to replay past requests.
If you have a paid account for ngrok you can also pass a custom subdomain to the flag: --ngrok my-awesome-functions
and it will spawn them as my-awesome-functions.ngrok.io
.
Debugging your Functions
While console.log
is probably the most popular debugging tool (and yes it works with twilio-run
), you sometimes have to take out the big guns and use an actual debugger. twilio-run
allows you to attach your favorite Node.js debugger by using the same command-line flags you are already familiar with from Node.js.
twilio-run --inspect
This will open the default debugging port which you can see displayed in the output of the tool:
If you are using Visual Studio Code like me, all you have to do now is create a launch.json
inside a .vscode
folder in your project and place in the following config:
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "Attach",
"port": 9229
}
]
}
Now, with twilio-run
running with the --inspect
flag, open the debugger pane, choose "Attach", and run it.
Once it's attached, you can set a break pointer (Don't worry if it appears grey at first) and execute your request. The debugger should catch and set the breakpoint.
You can learn more about debugging Node.js applications with VS Code in their documentation.
If you don't use VS Code, or prefer the Chrome developer tools, open the Chrome dev tools on any page and you should see a Node.js icon. Click on that icon to open the Debugger for your project:
Once it's open we have to load in our project. Go into the Sources tab, click on Filesystem on the side (it might be hidden behind ">>" next to "Page"), and open your project folder. If you haven't previously, you'll have to grant your browser access to the file system before opening the project. You can now set your breakpoint, and once they are hit you are able to debug your app further.
If none of these options are your jam, you can use whatever tool you prefer that supports attaching to the Node.js debugging protocol.
Loading in environment variables
Twilio Functions lets you access environment variables via the context
object. For security, twilio-run
won't let you access all of the local environment variables of your development machine. If you want to do so, you can add the --load-local-env
flag and it will load them in.
twilio-run --load-local-env
If you want to configure project specific variables the best way is to use a .env
file in your project. Make sure you add your .env
file to your .gitignore
if you have sensitive data like credentials in it. You can load in .env
files by using the --env
flag. If you want to load a specific file, you can also specify the path to it relative to your base directory.
Let's try this. Create a .env
file in your project root with the following content:
PLANET=Mars
And modify your Twilio Function to:
exports.handler = function(context, event, callback) {
let twiml = new Twilio.twiml.MessagingResponse();
twiml.message(`Hello ${context.PLANET || 'World'}`);
callback(null, twiml);
};
We are going to greet with whatever value is specified in the environment and fallback to "Hello World" if there isn't one. If you restart twilio-run
without the --env
variable you should see:
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Message>Hello World</Message>
</Response>
If you now restart twilio-run
with the --env
flag like so:
twilio-run --env
You should see that the message changed to:
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Message>Hello Mars</Message>
</Response>
Note that if you combine both --load-local-env
and --env
, all variables set in your local environment will be temporarily replaced by the ones set in the .env
file.
"Live reloading"
By default you'll have to restart twilio-run
if you want to check out changes in one of your Functions since they are cached by Node.js' cache. You can disable this caching by running twilio-run
with the --live
flag like so:
twilio-run --live
Since this isn't really performant, it is disabled by default.
What about deploying my functions and assets to run on Twilio?
Right now, you'll have to copy and paste Functions code and/or drag and drop asset filesin the Twilio console to deploy them live. We're working hard on an API for deployment. Look out for that soon, and get in touch with me if you'd like to be one of the first to try it.
What's next?
This project was spawned out of my own needs, but I would love to hear what features you would like to see. I'm also totally open to contributions to the project. If you want to check out the source code, file issues, or just say thank you, feel free to go to https://github.com/dkundel/twilio-run
The project also exposes an API if you want to load a Twilio Function in an existing Express server for testing. You can find it documented in the project's README.md.
Now that you are successfully developing with Twilio Functions locally, why don't you check out some of these Twilio Functions powered blog posts:
- Forward fax to email with SendGrid and Node.js
- Identify Unknown Phone Numbers with JavaScript, Twilio Lookup and SMS
- Building an npm search bot with WhatsApp and Twilio
- 👉 Emoji translations with the 📞 Twilio API for 💬 WhatsApp and Node.js
And if you have any questions or would love to show me what cool thing you built with Twilio Functions, simply reach out to me:
- Mail: dkundel@twilio.com
- Twitter: @dkundel
- GitHub: dkundel