Custom HTTP Clients for the Twilio Node Helper Library

If you are working with the Twilio Node Helper Library, and you need to be able to modify the HTTP requests that the library makes to the Twilio servers, you’re in the right place.

The most common need to alter the HTTP request is to connect and authenticate with an enterprise’s proxy server. We’ll provide sample code that you can drop right into your app to handle this use case.

Connect and authenticate with a proxy server

To connect and provide credentials to a proxy server that may be between your app and Twilio, you need a way to modify the HTTP requests that the Twilio helper library makes on your behalf to invoke the Twilio REST API.

In Node.js, the Twilio helper library uses the request package under the hood to make the HTTP requests. The Twilio Helper Library allows you to provide your own httpClient for making API requests. 

Node Custom HTTP Client

So the question becomes: how do we apply this to a typical Twilio REST API example?

const client = twilio(accountSid, authToken);

client.messages
    .create({
        to: '+15558675310',
        from: '+15017122661 ',
        body: 'Hey there!',
    })
    .then(message => console.log(`Message SID ${message.sid}`));

Where does an httpCient get created and used? Out of the box, the helper library creates a default RequestClient for you, using the Twilio credentials you pass to the init method. However, nothing is stopping you from creating your own and using that.

Once you have your own RequestClient, you can pass it to any Twilio REST API resource action you want. Here’s an example of sending an SMS message with a custom client:

Loading Code Samples...
Language
// require the Twilio module and RequestClient
const twilio = require('twilio');
const MyRequestClient = require('./MyRequestClient');

// Load environment variables
require('dotenv').config()

// Twilio Credentials
const accountSid = process.env.ACCOUNT_SID;
const authToken = process.env.AUTH_TOKEN;

const client = twilio(accountSid, authToken, {
    // Custom HTTP Client
    httpClient: new MyRequestClient(process.env.PROXY)
});

client.messages
    .create({
        to: '+15558675310',
        from: '+15017122661',
        body: 'Hey there!',
    })
    .then(message => console.log(`Message SID ${message.sid}`));
How to pass a TwilioRestClient

Create your custom TwilioRestClient

When you take a closer look at the constructor for twilio.restClient, you see that the httpClient parameter is a RequestClient. This class provides the client to the Twilio helper library to make the necessary HTTP requests.

Call Twilio through the proxy server

Now that we understand how all the components fit together, we can create our own RequestClient that can connect through a proxy server. To make this reusable, here’s a class that you can use to create this RequestClient whenever you need one:

Loading Code Samples...
Language
'use strict';

var _ = require('lodash');
var http = require('request');
var Q = require('q');
var Response = require('twilio/lib/http/response');
var Request = require('twilio/lib/http/request');

/**
 * Custom HTTP Client
 * Based on: /twilio/lib/base/RequestClient.js
 */
class RequestClient {
  constructor(proxy = null) {
    if (proxy) {
      this.proxy = proxy;
    }
  }

  request(opts) {
    opts = opts || {};
    if (!opts.method) {
      throw new Error('http method is required');
    }

    if (!opts.uri) {
      throw new Error('uri is required');
    }

    var deferred = Q.defer();
    var headers = opts.headers || {};
    if (opts.username && opts.password) {
      var b64Auth = new Buffer(opts.username + ':' + opts.password).toString('base64');
      headers.Authorization = 'Basic ' + b64Auth;
    }

    var options = {
      timeout: opts.timeout || 30000,
      followRedirect: opts.allowRedirects || false,
      url: opts.uri,
      method: opts.method,
      headers: opts.headers,
      forever: opts.forever === false ? false : true,
      proxy: this.proxy
    };

    if (!_.isNull(opts.data)) {
      options.formData = opts.data;
    }

    if (!_.isNull(opts.params)) {
      options.qs = opts.params;
      options.useQuerystring = true;
    }

    var optionsRequest = {
      method: options.method,
      url: options.url,
      auth: b64Auth || null,
      params: options.qs,
      data: options.formData,
      headers: options.headers,
    };

    var that = this;
    this.lastResponse = undefined;
    this.lastRequest = new Request(optionsRequest);

    http(options, function (error, response) {
      if (error) {
        that.lastResponse = undefined;
        deferred.reject(error);
      } else {
        that.lastResponse = new Response(response.statusCode, response.body);
        deferred.resolve({
          statusCode: response.statusCode,
          body: response.body,
        });
      }
    });

    return deferred.promise;
  }
}

module.exports = RequestClient;
Custom HTTP Client for Node.js

In this example, we are using some environmental variables loaded at the program's startup to retrieve various configuration settings:

  • Your Twilio Account Sid and Auth Token (found here, in the Twilio console)

  • HTTP_PROXY and HTTPS_PROXY in the form of http://127.0.0.1:8888

These settings are located in a file like .env like so:

ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
AUTH_TOKEN= your_auth_token

HTTPS_PROXY=https://127.0.0.1:8888
HTTP_PROXY=http://127.0.0.1:8888

Here’s the full console program that sends a text message and shows how it all can work together. It loads the .env file for us.

Loading Code Samples...
Language
// require the Twilio module and RequestClient
const twilio = require('twilio');
const MyRequestClient = require('./MyRequestClient');

// Load environment variables
require('dotenv').config()

// Twilio Credentials
const accountSid = process.env.ACCOUNT_SID;
const authToken = process.env.AUTH_TOKEN;

const client = twilio(accountSid, authToken, {
    // Custom HTTP Client
    httpClient: new MyRequestClient(process.env.PROXY)
});

client.messages
    .create({
        to: '+15558675310',
        from: '+15017122661',
        body: 'Hey there!',
    })
    .then(message => console.log(`Message SID ${message.sid}`));
Call the Twilio REST API with a custom TwilioRestClient in Node

What else can this technique be used for?

Now that you know how to inject your own httpClient into the Twilio API request pipeline, you can use this technique to add custom HTTP headers and authorization to the requests (perhaps as required by an upstream proxy server).

You could also implement your own httpClient to mock the Twilio API responses. With a custom httpClient,  you can run your unit and integration tests quickly without the need to make a connection to Twilio. In fact, there’s already an example online showing how to do exactly that with C#.

We can’t wait to see what you build!

Need some help?

We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd browsing the Twilio tag on Stack Overflow.

Loading Code Samples...
// require the Twilio module and RequestClient
const twilio = require('twilio');
const MyRequestClient = require('./MyRequestClient');

// Load environment variables
require('dotenv').config()

// Twilio Credentials
const accountSid = process.env.ACCOUNT_SID;
const authToken = process.env.AUTH_TOKEN;

const client = twilio(accountSid, authToken, {
    // Custom HTTP Client
    httpClient: new MyRequestClient(process.env.PROXY)
});

client.messages
    .create({
        to: '+15558675310',
        from: '+15017122661',
        body: 'Hey there!',
    })
    .then(message => console.log(`Message SID ${message.sid}`));
'use strict';

var _ = require('lodash');
var http = require('request');
var Q = require('q');
var Response = require('twilio/lib/http/response');
var Request = require('twilio/lib/http/request');

/**
 * Custom HTTP Client
 * Based on: /twilio/lib/base/RequestClient.js
 */
class RequestClient {
  constructor(proxy = null) {
    if (proxy) {
      this.proxy = proxy;
    }
  }

  request(opts) {
    opts = opts || {};
    if (!opts.method) {
      throw new Error('http method is required');
    }

    if (!opts.uri) {
      throw new Error('uri is required');
    }

    var deferred = Q.defer();
    var headers = opts.headers || {};
    if (opts.username && opts.password) {
      var b64Auth = new Buffer(opts.username + ':' + opts.password).toString('base64');
      headers.Authorization = 'Basic ' + b64Auth;
    }

    var options = {
      timeout: opts.timeout || 30000,
      followRedirect: opts.allowRedirects || false,
      url: opts.uri,
      method: opts.method,
      headers: opts.headers,
      forever: opts.forever === false ? false : true,
      proxy: this.proxy
    };

    if (!_.isNull(opts.data)) {
      options.formData = opts.data;
    }

    if (!_.isNull(opts.params)) {
      options.qs = opts.params;
      options.useQuerystring = true;
    }

    var optionsRequest = {
      method: options.method,
      url: options.url,
      auth: b64Auth || null,
      params: options.qs,
      data: options.formData,
      headers: options.headers,
    };

    var that = this;
    this.lastResponse = undefined;
    this.lastRequest = new Request(optionsRequest);

    http(options, function (error, response) {
      if (error) {
        that.lastResponse = undefined;
        deferred.reject(error);
      } else {
        that.lastResponse = new Response(response.statusCode, response.body);
        deferred.resolve({
          statusCode: response.statusCode,
          body: response.body,
        });
      }
    });

    return deferred.promise;
  }
}

module.exports = RequestClient;
// require the Twilio module and RequestClient
const twilio = require('twilio');
const MyRequestClient = require('./MyRequestClient');

// Load environment variables
require('dotenv').config()

// Twilio Credentials
const accountSid = process.env.ACCOUNT_SID;
const authToken = process.env.AUTH_TOKEN;

const client = twilio(accountSid, authToken, {
    // Custom HTTP Client
    httpClient: new MyRequestClient(process.env.PROXY)
});

client.messages
    .create({
        to: '+15558675310',
        from: '+15017122661',
        body: 'Hey there!',
    })
    .then(message => console.log(`Message SID ${message.sid}`));