<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Google Assistant - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Google Assistant - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Mon, 27 Jul 2026 04:22:53 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/google-assistant/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to implement local fulfillment for Google Assistant actions using Dialogflow ]]>
                </title>
                <description>
                    <![CDATA[ NB: this blog covers Actions on Google node.js deployments only, and presumes some basic prior knowledge of Actions on Google/ Dialogflow Google Home Devices that run the Google Assistant Hello, world! Problem statement I’ve been getting into Action... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-implement-local-fulfillment-for-google-assistant-actions-using-dialogflow-1b3b3a13075f/</link>
                <guid isPermaLink="false">66d461cd55db48792eed3fd1</guid>
                
                    <category>
                        <![CDATA[ coding ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Google Assistant ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Zubin Pratap ]]>
                </dc:creator>
                <pubDate>Tue, 05 Mar 2019 15:04:18 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/0*7jmZNu-Wbc7Z4Ulo.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>NB: this blog covers Actions on Google node.js deployments only, and presumes some basic prior knowledge of Actions on Google/ Dialogflow</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/VFFY4IHPLQMJDw0QkHQf5XQL0au4uIdLTjOI" alt="Image" width="800" height="450" loading="lazy"></p>
<p><em>Google Home Devices that run the Google Assistant</em></p>
<p>Hello, world!</p>
<h4 id="heading-problem-statement"><strong>Problem statement</strong></h4>
<p>I’ve been getting into <a target="_blank" href="https://developers.google.com/actions/">Actions on Google</a> quite a bit of late — enjoying myself thoroughly — My learning has been primarily designing small “party tricks” that I can amuse visitors with. I’ve been doing the same with Alexa too, but since I’m a lot more familiar with the <a target="_blank" href="https://cloud.google.com/">Google Cloud Platform</a> and <a target="_blank" href="https://firebase.google.com/">Firebase</a> especially, I have prototyped more on Google.</p>
<p>Actions and working with the Google Assistant often requires server side logic to handle the interaction with the desired level of personalisation or customization. This is called “fulfillment”.</p>
<p>The development cycle for fulfillment can be a bit tedious because you need to push your server code to the cloud each time, to see if it’s working. Every time we make changes that we want to test, we need to push code to Firebase and give it 30–60 seconds to propagate. It’s like web dev before hot reloads — ugh! So 2011.</p>
<h4 id="heading-summary-of-the-architecture"><strong>Summary of the Architecture</strong></h4>
<p>I won’t go into the details of building Actions for the Assistant, as there is plenty out there to help you with that. But a high level overview of the system now will help you understand the problem statement that inspired this blog.</p>
<p>Here is a diagram from Google that explains how Actions/ Assistant interactions are processed.</p>
<p>A user’s interaction gets picked up by the device, converted to text that is parsed by the device, and passed to a <a target="_blank" href="http://dialogflow.com/">DialogFlow</a> agent which is a <a target="_blank" href="https://en.wikipedia.org/wiki/Natural-language_understanding">NLU</a>engine. That then works out the “intent” of the user’s statement (“phrase” or “utterance”). That intent then gets matched to code that “fulfills” the intent of the user, then returns a response that is (hopefully) appropriate and acceptable.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/0*7jmZNu-Wbc7Z4Ulo.png" alt="Image" width="1280" height="720" loading="lazy"></p>
<p>If you have studied the picture, you will see that a request is made in the last column, which denotes <a target="_blank" href="https://firebase.google.com/">Firebase</a>. What it really denotes is a back-end server that has a HTTP webhook that receives ‘requests’ for fulfillment and then works out how to respond appropriately. Architecturally, the Dialogflow NLU agent sends a <strong>POST</strong> request to the server webhook, which then gets processed by the server using SDKs.</p>
<p>The back end server that processes requests and responses is easily hosted on <a target="_blank" href="https://firebase.google.com/">Firebase Cloud Functions</a> (of course, Google makes it easy to use their stack — that’s just good business!).</p>
<p>Also, a locally run server will generate a webhook endpoint like <a target="_blank" href="http://localhost:3000,/">http://localhost:3000,</a> which is not accessible to Google Assistant’s POST requests.</p>
<h4 id="heading-solution-statement-local-fulfillment-dev-server"><strong>Solution Statement — local fulfillment dev server!</strong></h4>
<p>There are three steps to this solution:</p>
<ol>
<li><p>Create the back-end fulfillment server as an <a target="_blank" href="https://expressjs.com/">Express server</a>.</p>
</li>
<li><p>Handle Dialogflow requests and responses as an Express endpoint. The default Firebase Cloud Functions server users the <a target="_blank" href="https://github.com/actions-on-google/actions-on-google-nodejs">Actions on Google Nodejs Client Library</a> which has built-in functionality to receive HTTP POST requests from the Dialogflow agent. But that won’t work outside the Firebase environment (i.e. on our local development server). So we need to deploy our fulfillment server an an Express app.</p>
</li>
<li><p>use the <a target="_blank" href="https://www.npmjs.com/package/ngrok">Ngrok Node package</a> to create a temporary HTTP endpoint that tunnels HTTP requests through to our local:3000 (or whatever port you use) server.</p>
</li>
</ol>
<p>I will not go into the steps of setting up your basic code for a simple nodejs fulfillment server — the Dialogflow/ Actions on Google docs help you with all that. But I do provide here the snippets that show you how to convert that Dialogflow App into an Express App, and what your <code>package.json</code> needs to look like.</p>
<p>Let’s start with the basic setup for the Dialogflow node.js client. We will call it the Dialogflow App.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> {
  dialogflow,
  BasicCard
} = <span class="hljs-built_in">require</span>(<span class="hljs-string">"actions-on-google"</span>);


<span class="hljs-comment">// Instantiate the Dialogflow client.</span>
<span class="hljs-keyword">const</span> app = dialogflow({ <span class="hljs-attr">debug</span>: <span class="hljs-literal">true</span> });


<span class="hljs-comment">// Handlers go here..</span>
app.intent(<span class="hljs-string">"Default Welcome Intent"</span>, <span class="hljs-function"><span class="hljs-params">conv</span> =&gt;</span> {
   <span class="hljs-comment">// handler for this intent</span>
});

app.intent(<span class="hljs-string">"Say_Something_Silly"</span>, <span class="hljs-function"><span class="hljs-params">conv</span> =&gt;</span> {
   <span class="hljs-comment">// handler for this intent</span>
});


<span class="hljs-built_in">module</span>.exports = app;
</code></pre>
<p>This is the app that ‘handles’ the intents. The above code is just scaffolding. Export the app, and import it in <code>functions/index.js</code>.</p>
<p><code>index.js</code> is the entry point in our <code>functions</code> folder, which contains the cloud functions we push up to Firebase Cloud Functions. In this file we create the Express App, and import the DialogflowApp object, and then pass it in to the Express route that will receive the HTTP POST requests from Dialogflow. <strong>Note</strong> that we need the body-parser npm package as the HTTP requests are JSON.</p>
<pre><code class="lang-js"><span class="hljs-meta">"use strict"</span>;

<span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
<span class="hljs-keyword">const</span> bodyParser = <span class="hljs-built_in">require</span>(<span class="hljs-string">"body-parser"</span>);
<span class="hljs-keyword">const</span> functions = <span class="hljs-built_in">require</span>(<span class="hljs-string">"firebase-functions"</span>);


<span class="hljs-comment">// clients</span>
<span class="hljs-keyword">const</span> dialogFlowApp = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./DialogflowApp"</span>);
<span class="hljs-keyword">const</span> expressApp = express().use(bodyParser.json());

<span class="hljs-comment">// EXPRESS APP fulfillment route (POST). The entire dialogFlowApp object (incl its handlers) is the callback handler for this route.</span>
expressApp.post(<span class="hljs-string">"/"</span>, dialogFlowApp);


<span class="hljs-comment">//  EXPRESS APP test route (GET)</span>
expressApp.get(<span class="hljs-string">"/"</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  res.send(<span class="hljs-string">"CONFIRMED RECEIPT OF GET."</span>);
});



<span class="hljs-comment">/*
 *   LOCAL NGROK SERVER LOGIC. ENSURE that you "export IS_LOCAL_DEV=true" in terminal prior to start
 */</span>
<span class="hljs-keyword">if</span> (process.env.IS_LOCAL_DEV) {
  <span class="hljs-keyword">const</span> PORT = <span class="hljs-number">8000</span>;
  expressApp.listen(PORT, <span class="hljs-function">() =&gt;</span>
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`*** SERVER RUNNING LOCALLY ON PORT <span class="hljs-subst">${PORT}</span> ***`</span>)
  );
} <span class="hljs-keyword">else</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"*** NOT LOCALLY SERVED - OR - LOCAL ENV VAR NOT SET  ****"</span>);
}

<span class="hljs-comment">//EXPORT either of the following two endpoints:  one express app, one dialogflow app</span>
<span class="hljs-built_in">exports</span>.fulfillmentExpressServer = functions.https.onRequest(expressApp);
<span class="hljs-built_in">exports</span>.dialogflowFirebaseFulfillment = functions.https.onRequest(dialogFlowApp);
</code></pre>
<p>The key parts of this code are that we create a POST route that takes, as the handler-callback, our DialogflowApp object. I created a GET route just to make quick browser GET requests to test the endpoint is working. But Dialogflow only uses the POST route.</p>
<p><strong>Note</strong> that I’ve made two exports here. One is the Express app and the other is the dialogflow App itself. This creates to Firebase functions with two endpoints which are identified by the property attached to the <code>exports</code>object. One endpoint will be &lt;……/fulfillmentExpressServer&gt; and the other will be &lt;……/dialogflowFirebaseFulfillment&gt;.</p>
<p>I can use either of these HTTP endpoints for fulfillment, once I’m done developing locally and have pushed the final code up to Firebase Cloud Functions.</p>
<h4 id="heading-ngrok-for-the-local-development-server-tunneling"><strong>NGROK for the local development server tunneling</strong></h4>
<p>There is some funny looking code in there on line 26. On my Mac terminal, I use <code>export IS_LOCAL_DEV=true</code> before I start the server locally. That codeblock on line 26 basically starts the server listening locally, which is <strong>not</strong>needed when we push the code up to Cloud Functions — it is for the local server only.</p>
<pre><code class="lang-js"><span class="hljs-string">"dependencies"</span>: {
    <span class="hljs-string">"actions-on-google"</span>: <span class="hljs-string">"^2.0.0"</span>,
    <span class="hljs-string">"body-parser"</span>: <span class="hljs-string">"^1.18.3"</span>,
    <span class="hljs-string">"express"</span>: <span class="hljs-string">"^4.16.4"</span>,
    <span class="hljs-string">"firebase-functions"</span>: <span class="hljs-string">"^2.2.0"</span>
  },
<span class="hljs-string">"devDependencies"</span>: {
    <span class="hljs-string">"ngrok"</span>: <span class="hljs-string">"^3.1.1"</span>
  },
<span class="hljs-string">"scripts"</span>: {
    <span class="hljs-string">"lint"</span>: <span class="hljs-string">"eslint ."</span>,
    <span class="hljs-string">"serve"</span>: <span class="hljs-string">"firebase serve --only functions"</span>,
    <span class="hljs-string">"shell"</span>: <span class="hljs-string">"firebase experimental:functions:shell"</span>,
    <span class="hljs-string">"start"</span>: <span class="hljs-string">"npm run shell"</span>,
    <span class="hljs-string">"deploy"</span>: <span class="hljs-string">"firebase deploy --only functions"</span>,
    <span class="hljs-string">"logs"</span>: <span class="hljs-string">"firebase functions:log"</span>,
    <span class="hljs-string">"tunnel"</span>: <span class="hljs-string">"ngrok http 8000"</span>,
    <span class="hljs-string">"dev"</span>: <span class="hljs-string">"nodemon index.js"</span>
  },
</code></pre>
<p>Which brings me to the above configuration in package.json. In the <code>scripts</code> property, you can see one called <code>tunnel</code>. Note the port number 8000 there. That can be set to anything you like, but make sure that the code in <code>index.js</code>that sets the <code>PORT</code> constant (line 27 in the Gist) is the same port number.</p>
<p>Before you run the code, check the following:</p>
<ol>
<li><p>You’ve set your environment variable and the code matches it — in my case I used <code>IS_LOCAL_DEV=true</code></p>
</li>
<li><p>Your port numbers match in <code>index.js</code> and the <code>tunnel</code> script</p>
</li>
</ol>
<p>Then you open two terminal windows and run the following:</p>
<p><code>npm run dev</code> and <code>npm run tunnel</code></p>
<p>In the terminal that ran the tunnel (ngrok) you will see something like this:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*N7SNAkcHzlVyWFoPTVk2eg.png" alt="Image" width="765" height="180" loading="lazy"></p>
<p>Note the two ‘Forwarding’ addresses. They’re identical, except one is https. Either works. That is your endpoint, assuming your POST route is <code>/</code> and not <code>/&lt;some path&gt;.</code> If you’ve added a path to the POST handler then you need to add that path to the Forwarding address.</p>
<p><strong>Note</strong> that every time you run ngrok it generates a new, temporary URL for you. Therefore, you need to update your Dialogflow fulfillment webhook every time you run <code>npm run tunnel</code>.</p>
<p>And there you go. No more pushing every little change in code to Firebase Cloud Functions and waiting a minute or two before testing. Develop on the fly, and with nodemon, your server restarts and you can keep testing as you code!</p>
<p>And if you are a code newbie, and find this overwhelming, that is natural. You’re jumping too far ahead. I made that mistake too, and it cost me too much.</p>
<p>If you would like to learn more about my journey into code, check out <a target="_blank" href="http://podcast.freecodecamp.org/53-zubin-pratap-from-lawyer-to-developer">episode 53</a> of the <a target="_blank" href="http://podcast.freecodecamp.org/">freeCodeCamp podcast</a>, where Quincy (founder of freeCodeCamp) and I share our experiences as career changers that may help you on your journey. You can also access the podcast on <a target="_blank" href="https://itunes.apple.com/au/podcast/ep-53-zubin-pratap-from-lawyer-to-developer/id1313660749?i=1000431046274&amp;mt=2">iTunes</a>, <a target="_blank" href="https://www.stitcher.com/podcast/freecodecamp-podcast/e/59201373?autoplay=true">Stitcher</a>, and <a target="_blank" href="https://open.spotify.com/episode/4lG0RGpzriG5vXRMgza05C">Spotify</a>.</p>
<p>I will also hold a few AMAs and webinars in the coming months. If this is of interest to you please let me know by going <a target="_blank" href="http://www.matchfitmastery.com/">here</a>. And of course, you can also Tweet me at <a target="_blank" href="https://twitter.com/zubinpratap">@ZubinPratap</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
