Speed up Actions on Google development workflow with local fulfillment
The biggest issue that plagues Actions on Google developers is by far the slow development workflow when using Cloud Functions for Firebase for webhook development. It not only affects developer productivity but also adds up to the development time when building Actions.
Let’s say your Action needs a small change. You make the changes locally, do a
firebase deploy and wait for the changes to propagate. You test out your changes in the Actions on Google simulator and —
Darn! That didn’t work 😑
You work some more changes, deploy and wait yet again for the changes to propagate.
Still nothing! 😤
A few more changes and —
My Test App isn’t responding right now. Try again soon. 😠
The vicious cycle continues until you either get the Action working or call it a day. This sort of development workflow seems so old when compared to the hot reloads we are used to today. The problem lies in the fact that even the tiniest of the changes that you make locally requires redeploying to Firebase Functions before you can test whether the changes work or not using the simulator.
This increases the development time significantly while also hampering developer productivity. It is common to stray away from the task in hand while your function is deploying and you wait for the changes to propagate. As the complexity of the Action increases, you need to iterate even more and the process of redeploying every time to test out the smallest of the changes increases the development time significantly.
The solution to the problem is pretty straightforward — we’ll run a simple web server on our local machine and create a secure tunnel to expose the HTTP endpoint using ngrok. We’ll then use nodemon to monitor the endpoint for any changes and restart the server automatically. This will allow us to instantly test out the changes in the simulator.
1. Install ngrok
When Dialogflow sends a POST request, Firebase Functions handles the request and sends back a response. Similarly, our local webhook will also need to receive the request and send back a response. But there’s a small problem. Our HTTP endpoint would be running on a local web server and there would be no way for Dialogflow to access it.
This is where ngrok comes into play. It is a reverse proxy software that helps expose our local web server to the outside world by creating a secure tunnel. It creates a public URL for your endpoint which is accessible over the internet. This means that your local endpoint can now send and receive requests from Dialogflow.
Install ngrok using
npm install ngrok --save-dev . It is preferable to install it as a dev-dependency so that the developer next to you does not need to install anything extra. You can add the following to your scripts in
Now anytime you need a public URL for your local HTTP endpoint, just run
npm run tunnel
2. Modify the fulfillment code
Up until now, you probably might be having a single
index.js file for handling and sending responses. This might look something like —
Now since we would be deploying separately to local and production, we will need to modularize our code. I have created 3 separate files for doing this —
app.js file stores the intent handlers for our Action. The intent handlers handle the requests for specific intents and send back the response. The Dialogflow object is then exported using
module.exports = app
In order to run the code locally, we need to create an Express server. The reason for this is that when we use Firebase functions, the Actions on Google Nodejs Client Library does the heavy lifting for us when handling the POST requests from the Dialogflow agent. But when we run the HTTP endpoint from our local machine outside the Firebase environment, we’ll need to run our own Express server and parse the incoming HTTP POST requests using middleware such as Body-parser.
I have created a separate file
local.js for running our local server and parsing the requests. Run
npm install express body-parser to install Express and Body-parser.
We then fetch the
app.js module and pass it as a callback to the POST route. The GET request is not needed but is a good way to determine if your local endpoint is running. We then tell the web server to listen on any port of our choice.
3. Install nodemon
Now whenever you will make any changes, you’ll want the changes to reflect immediately so that you may test it instantly in the simulator. This is where nodemon comes into play. It automatically restarts the server whenver it detects any changes. Again, install it as a dev-dependency using
npm install nodemon --save-dev and add the following to your scripts —
4. Run the local web server
Now you just need to run the Express server and create a public facing URL for the HTTP endpoint. Do this by running
npm run dev and
npm run tunnel in two separate terminal windows.
Now you just need to copy the URL under Forwarding (the https one), into the Dialogflow webhook fulfillment.
Now you can test your Action in the Actions on Google simulator as usual. Any changes that you make on your local machine will be reflected instantly in the simulator while testing.
5. Deploying to Firebase Functions
In order to deploy to Firebase, we’ll need to create a separate file. Separating the logic for local and production allows us to test our Actions freely without the fear of breaking anything. A minimal configuration
index.js will help us achieve that goal. Create an
index.js file with the following code —
You can deploy to Firebase using
firebase deploy --only functions . Once the deployment is successful, don’t forget to replace the webhook URL in Dialogflow with the Function URL.
You should now be able to run a local web server for your HTTP endpoint. Now you can test out the changes instantly. It will help you speed up your development workflow and launch more and more amazing Actions! 😃
Voiceano is a voice interaction design and development studio that builds Actions for Google Assistant. If you feel that your brand needs a voice, then you should definitely grab a drink with us! 🍻😄