Using Twilio Functions with TypeScript

September 03, 2019
Written by

decorative header image with

With the Serverless Toolkit we can include the development, debugging and deploying of Twilio Functions, Twilio's Serverless Runtime offering, more tightly into our existing development flows. For example we can add build tools such as TypeScript into our project to perform type checks on our Twilio Functions to catch more bugs during compilation time. In this post we'll look into how we can set up a Twilio Functions project using the Serverless Toolkit and TypeScript.

Requirements

Before we can get started, you'll need to make sure to have the following things:

We'll be using the Serverless Toolkit via the Twilio CLI but you can also use it as a standalone. Check out the docs for more on how to use the Toolkit alone.

If you don't have the Twilio CLI and Serverless Toolkit installed run the following commands in your terminal:

npm install -g twilio-cli
twilio plugins:install @twilio-labs/plugin-serverless
twilio login

Now that we have everything set up let's get started.

Creating a Project

We'll be working on a new project but you can also apply these steps to an existing project. To create a new project run the following:

twilio serverless:init typescript-demo
cd typescript-demo

This will create a new typescript-demo directory with a few default functions and assets in it.

If this is the first time working with a Twilio Functions project and the Serverless Toolkit, run the following command to start up your local development server:

npm start

Open your browser of choice and go to http://localhost:3000/index.html. You'll find a page that will walk you through the different files created in this project.

screenshot of a browser window with the content of the localhost page

Now that we have the project up and running we have to add TypeScript to the party.

Setting Up TypeScript

The TypeScript compiler is available via npm registry. Since it's only needed as a build tool but not as a runtime dependency we'll install it as a devDependency:

npm install typescript --save-dev

Now that we have our compiler installed, we have to set up our project configuration in a tsconfig.json. This tells the compiler which options to use and where to place the output. In our case we'll use the existing functions/ directory as our output directory and a new src/ directory for input.

Create a new file called tsconfig.json at the root of your project and place the following content into it:

{
  "compilerOptions": {
    "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
    "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
    "outDir": "./functions" /* Redirect output structure to the directory. */,
    "rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
    "strict": false /* Enable all strict type-checking options. */,
    "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
  }
}

This will set up everything necessary for the compiler to take any TypeScript files inside the src/ directory, compile them and place the output into the functions/ directory.

Since the functions/ directory will be our new output directory, go ahead and delete the current functions/ directory. The TypeScript compiler will create a new one once we run it.

We can run the TypeScript compiler manually for our changes but let's configure our npm scripts in a way that whenever we run npm start or npm run deploy we'll run the compiler. Update your package.json accordingly:

{
  "name": "typescript-demo",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "prestart": "tsc",
    "start": "twilio-run start",
    "predeploy": "tsc",
    "deploy": "twilio-run deploy"
  },
  "devDependencies": {
    "twilio-run": "^2.0.0",
    "typescript": "^3.6.2"
  },
  "engines": {
    "node": "8.10.0"
  }
}

Creating a Twilio Function in TypeScript

Now that we have the TypeScript aspect of our project all set-up, it's time to create our first Twilio Function using TypeScript. Create a new src/ directory inside your project and add a file called hello-world.ts with the following content:

export const handler = function(context, event, callback) {
  callback(null, 'Hello World from TypeScript!');
};

So far there's nothing TypeScript specific in this file except that we switched to an ES Module syntax for exporting the handler function. This is okay because we are currently not running in strict mode. We'll get to that in a minute.

To see if our changes worked, go into your terminal and run:

npm start

You should see the TypeScript compiler (tsc) being executed and then it should print the same screen we've seen earlier when we ran npm start.

screenshot of terminal showing that tsc was executed and the output of "twilio-run start"

Open http://localhost:3000/hello-world and you should see Hello World from TypeScript! printed on the screen.

browser window with "Hello World from TypeScript" on it

Getting Strict and Adding Types

Right now we have the TypeScript compiler set up but since we are not running in strict mode we won't get all the benefits the TypeScript compiler can give us.

Start by enabling strict mode by changing the value of strict in your tsconfig.json to true:

{
  "compilerOptions": {
    "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
    "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
    "outDir": "./functions" /* Redirect output structure to the directory. */,
    "rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
    "strict": true /* Enable all strict type-checking options. */,
    "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
  }
}

Try re-running npm start and you'll see the TypeScript compiler complaining that we have three parameters that are implicitly of type any. That's because so far we have omitted any time definitions.

We'll have to tell the TypeScript compiler what the types of the arguments are. We could create these types ourselves but to make this easier, we created a package containing all of these types under @twilio-labs/serverless-runtime-types. Install the package by running:

npm install @twilio-labs/serverless-runtime-types

Go back into your hello-world.ts file and add the types for these arguments by adding the following lines:

import {
  Context,
  ServerlessCallback,
  ServerlessFunctionSignature,
} from '@twilio-labs/serverless-runtime-types/types';

// You have to change that based on which parameters you expect to be passed to
// your Twilio Function via the POST body or request parameters.
type RequestParameters = {};

export const handler: ServerlessFunctionSignature = function(
  context: Context,
  event: RequestParameters,
  callback: ServerlessCallback
) {
  callback(null, 'Hello World from TypeScript!');
};

Try re-running the TypeScript compiler by running npm start again and you should see it's passing again but now with strict mode enabled and http://localhost:3000/hello-world should work again.

Twilio Functions expose a few global objects like a global Twilio instance of the Node.js helper library. Right now TypeScript is unaware of those. We can change that by importing the @twilio-labs/serverless-runtime-types module directly. Let's try this by responding with TwiML instead of just a string in our hello-world.ts:

import '@twilio-labs/serverless-runtime-types';
import {
  Context,
  ServerlessCallback,
  ServerlessFunctionSignature,
} from '@twilio-labs/serverless-runtime-types/types';

// You have to change that based on which parameters you expect to be passed tp
// your Twilio Function via the POST body or request parameters.
type RequestParameters = {};

export const handler: ServerlessFunctionSignature = function(
  context: Context,
  event: RequestParameters,
  callback: ServerlessCallback
) {
  const twiml = new Twilio.twiml.MessagingResponse();
  twiml.message('Hello World from TypeScript!');
  callback(null, twiml);
};

Re-run npm start and open http://localhost:3000/hello-world and you should now see TwiML with the same message being returned.

browser window screenshot with TwiML and a message "Hello World from TypeScript!" on it

Since Twilio Functions are running Node.js you might want to use some Node.js specific functionality at one point. If you want to do that, you can install the Node.js types by running:

npm install @types/node --save-dev

This will make any built-in Node.js module and globals available for you.

Deploying your Functions

Now that we have locally developed our Twilio Functions, all we have to do is, deploy our Functions to Twilio. Deploy your Functions by running:

npm run deploy

Once your project has successfully been deployed you should see a list of the Functions and Assets that have been deployed. Your URL should look something like this: https://typescript-demo-1234-dev.twil.io/hello-world where 1234 will be some other number.

screenshot of terminal showing the deployment output and highlighting the functions URL

Wrapping It Up

Congratulations! You built your first Twilio Functions project with TypeScript and successfully deployed it to Twilio. Why not check out how you can link your new Twilio Function to a phone number using the Twilio CLI?

Before you leave, here are a few more tips of things you might want to do:

If you have any questions, feedback or just want to show me what you build, feel free to reach out to me: