Building an npm search bot with WhatsApp and Twilio

August 02, 2018
Written by

kk5Dl1j4QkccAXbXCEnRAYuDNbn7ssPj4I1_3DthB-Zq74ljlPjtMhAPrVf0X4v_jyjts5QuqALUX2sQkvPR8waZASn0h-qK9vc8LqFOmOVFam9BHN02s3uG1mOYcKcXhJo3EALd

The npm ecosystem is vast by now with almost 750k packages published. Naturally, you’ll find yourself often trying to look up a package you don’t remember. The npm search is great for that but what if you just want to quickly look up a package on the go? Let’s build a bot that can look up the packages for us using Twilio’s API for WhatsApp integration.

Requirements

Just like with Twilio Programmable SMS we’ll need a webhook that will respond to incoming HTTP requests with every message sent to our WhatsApp bot. The webhook then has to reply with TwiML to reply to these messages.

We’ll build our webhook using Twilio Functions. This allows us to write Node.js code and host it in a serverless function directly on Twilio.

To get started make sure you have:

Creating a Twilio Function

Our app will use two external dependencies from npm that we have to install before we can use them. Go to the Twilio Functions Configuration section and add axios and common-tags as dependencies. You can set the version as * to grab the latest version.

Create a new Twilio Function by going into the Manage section, press the + button to create a new Function and choose the Hello SMS as the template. Our WhatsApp integration works just like the SMS channel and therefore we’ll be able to use the same template here.

You should be greeted with a screen showing a code editor as well as a few input fields. Update the function name field to any name of your liking. This helps you to find your function more easily at a later point. Afterwards, update the path. We’ll use this URL to configure our webhook later. Pick something like /npm-search.

Save your changes and copy the path to your function.

Connecting your Twilio Function to WhatsApp

Go back to your WhatsApp Sandbox in the Twilio Console and paste the URL of your Twilio Function into the A Message Comes In field. Save the changes afterwards and open WhatsApp to text your Sandbox. You should be greeted with a friendly Hello World.

Searching npm

Your Twilio Function should look something like this at the moment:

exports.handler = function(context, event, callback) {
    let twiml = new Twilio.twiml.MessagingResponse();
    twiml.message("Hello World");
    callback(null, twiml);
};

To query the npm database, we’ll be making an HTTP GET request to their registry. They expose a search endpoint that we can use. If you are interested about its capability, check out the documentation on GitHub. We’ll be using the axios package to do the request. Since we installed it earlier, we can directly require it at the top of our function. While we are at it, we’ll be also requiring the stripIndent function from common-tags and the escape function from the built-in querystring library. Update your Twilio Function accordingly:

const axios = require('axios');
const { stripIndent } = require('common-tags');
const { escape } = require('querystring');

exports.handler = function(context, event, callback) {
    let twiml = new Twilio.twiml.MessagingResponse();
    twiml.message("Hello World");
    callback(null, twiml);
};

Next, we need to take the message body for every incoming message to perform an HTTP request against the npm registry with it. You can access the message body in a Twilio Function via event.Body. If you are hosting your endpoint somewhere else, you’ll have to parse the HTTP POST request body and access Body afterwards.

Since we’ll need to place the message into the querystring of the URL, we have escape it. For this, we’ll use the escape(...) function that we imported earlier.

Lastly for now let’s reply with the name for the first package found. Update your inside the Twilio Function to make these changes:

const axios = require('axios');
const { stripIndent } = require('common-tags');
const { escape } = require('querystring');

exports.handler = function(context, event, callback) {
    const twiml = new Twilio.twiml.MessagingResponse();
    const query = escape(event.Body);
    const npmSearchApiUrl = `https://registry.npmjs.com/-/v1/search?text=${query}&fields=name,author,description,rating&sort=rating:desc`;
         
    axios
        .get(npmSearchApiUrl)
        .then(({ data }) => {
            const { objects } = data;
            let responseText = objects[0].package.name
            return responseText;
        })
        .catch(err => {
            console.error(err);
            return 'Failed to search npm';
        })
        .then(response => {
            twiml.message(response);
            callback(null, twiml);   
        });
};

Save your function, wait until it is successfully deployed and send a message like twilio or author:dkundel to search the npm registry. You should get the name of the first package found returned.

Now that we have access to the results lets mark this up more nicely. WhatsApp allows some basic Markdown-like formatting options. And we’ve always got emojis 🌈💖✨🥑.

Let’s write a separate function to do our formatting. We’ll be using the stripIndent tagged template function from the common-tags package to enable us to write our response more neatly. It will basically strip away any whitespace before or after our text and remove the basic indentation. Afterwards, we’ll use the function to map it over the first three results returned and join them to a new string.

Place the following code into your Twilio Function to achieve this:

const axios = require('axios');
const { stripIndent } = require('common-tags');
const { escape } = require('querystring');
 
function formatEntry({ package, score }) {
  const { quality } = score.detail;
  const { name, version, description, publisher } = package;
  const stars = '⭐️'.repeat(Math.round(quality * 5));
  return stripIndent`
      *${name}* (${stars})
      💖 by ${publisher.username}
      🔖 ${version}
      \`\`\`${description}\`\`\`
      📦 https://npm.im/${name}
  `;
}
 
exports.handler = function(context, event, callback) {
    const twiml = new Twilio.twiml.MessagingResponse();
    const query = escape(event.Body);
    const npmSearchApiUrl = `https://registry.npmjs.com/-/v1/search?text=${query}&fields=name,author,description,rating&sort=rating:desc`;
     
    axios
        .get(npmSearchApiUrl)
        .then(({ data }) => {
            const { objects } = data;
            let responseText = objects
                .slice(0, 3)
                .map(formatEntry)
                .join('\n---\n');
            responseText += `\n\n\n🌈  More at: https://www.npmjs.com/search?q=${query}`;
            return responseText;
        })
        .catch(err => {
            console.error(err);
            return 'Failed to search npm';
        })
        .then(response => {
            twiml.message(response);
            callback(null, twiml);   
        });
};

Deploy your Twilio Function once more and text in the name of your favorite package or author. You can find additional search queries in the documentation of the registry. The result should be something that looks like this:

Whats(App) next?

The neat thing with using WhatsApp with Twilio is that we can use the bot that we just built to now deploy it to other channels, like SMS, without doing any code changes. Take the URL of your Twilio Function, get a Twilio phone number and configure the Incoming Messaging webhook to the same URL. Save your changes and try to text your phone number. The result should be the same (minus the special formatting).

If you want to learn more about Twilio & WhatsApp check out the following resources:

There is so much more you can do with Twilio & WhatsApp. If you come up with a cool idea or if you have any questions feel free to reach out to me: