Deal Fishing Tutorial

September 1 draft

Welcome to the HubSpot Deal Carnival. While you learn how to create a fun, in-browser game using the Phaser.js game framework using skills you’ve already got as a web developer, you’ll also get familiar with the HubSpot platform. In fact, we’re going to use HubSpot’s APIs to make our games work, and HubSpot’s platform will even make it possible for us to deploy our frontends, securely interact with HubSpot APIs, and link our games together into a Vue.js-backed scoreboard.

This is the first tutorial in a series, and it’s all about getting you started and productive (while learning a new trick or two). We’ll get you signed up and familiar with HubSpot’s platform and dev tools, then you’ll build a Deal Fishing minigame. Finally, we’ll integrate the game with HubSpot’s contacts API by using HubSpot Serverless Functions.

Getting set up with HubSpot

Sign up for HubSpot

We’re going to sign up for a HubSpot developer account. That’ll let us have a sandbox to safely explore the APIs, and build our carnival games. https://app.hubspot.com/signup/standalone-cms-developer

Once you’ve got that, we’re ready to get using any HubSpot features. We’ll be using the HubSpot CMS to deploy our games, the HubSpot CLI to develop locally and push our game to HubSpot’s cloud-based CRM, and we’ll use HubSpot Serverless Functions to securely speak to the various APIs we’ll be using.

Using the HubSpot CLI

To get started with the CLI, you can follow the instructions here:

// TODO:

https://developers.hubspot.com/docs/cms/developer-reference/local-development-cms-cli

Downloading and configuring it

To download the HubSpot CLI:

npm install -g @hubspot/cms-cli

That’ll install it globally. If you ever need a reference on the full scope of the CLI and how it works for local development, just check out this page.

Next, let’s initialize and authenticate in the appropriate folder. Create a new folder – maybe call it hubspot-local – and move into it:

mkdir hubspot-local
cd hubspot-local
hs init

You’ll first get a prompt to press Enter to get sent to a website to generate your personal CMS access key. Press enter!

Select the account you created above – if it doesn’t show up by default, press the “View your other accounts” button. Make sure you’re selecting the sandbox account you created above, or else you might send make a mess of your organization’s HubSpot data.

At the next screen, be sure to select all the checkboxes under “Permissions.” That’ll allow us to get the full local development experience, and to complete this tutorial without a hitch. And as a security note, this CMS access key is for your HubSpot CLI – not for the game you’ll be making.

On the next screen you can copy your personal CMS access key; go back to your terminal and paste it there. Give the account a nickname you’ll remember (I used carnival-account ). You can now open up the hubspot.config.yml file in this folder, and you’ll see all the info you just entered.

Bonus tip​: if you’ve got many accounts added to your CLI already, it might be a good idea to switch the defaultPortal value to the nickname of the new sandbox account you added. That way, you won’t accidentally make changes to someplace you shouldn’t.

Creating and pushing your first module

Now that we’re ready to use the CLI, let’s create a module. The module is where we’ll be doing our coding, and it’s what will get deployed to HubSpot – so that we can see our game deployed and playable in the cloud, simply by navigating to a HubSpot browser. Through modules we can also get the game to interact with Serverless Functions, which will let our simple game demo connect to the HubSpot API.

It’s just one command:

hs create module deal-fishing
? What should the module label be? Deal Fishing
? What types of content will this module be used in? Page
? Is this a global module? No
Creating ~/hubspot-local/hubspot-carnival/deal-fishing.module
Creating module at ~/hubspot-local/hubspot-carnival/deal-fishing.module

Fun! Let’s see what’s in there:

/deal-fishing.module:
> fields.json
> meta.json   
> module.css  
> module.html
> module.js

Modules are pretty simple. They’re just folders that end with a .module name, and they have to contain these five files.

Sync a module to HubSpot

From the deal-fishing.module folder, run:

hs watch . deal-fishing.module

After watch , we’ve got . for the root hubspot-local folder that we’re in, and we want it to show up in the HubSpot Design Manager as deal-fishing.module.

Let’s go see if it showed up. Sign into HubSpot with your sandbox account, then in the top bar, navigate from Marketing > Files and Template > Design Tools. You’ll see a sidebar that should show your new deal-fishing module:

Select it, and you’ll see an editor – it automatically displays the html, css and js files that we’ll be working in. You can happily work there for the rest of this tutorial, but I’ll be writing instructions for local development, where you can use your favourite shell and text editor.

Live updates to your module

Before we head back to our local development, we’re going to open the preview URL from the Design Manager.

There’s nothing there yet – just a sample “rich text field” and an empty page. But that’s where we’ll see our changes show up.

Head back to your terminal, and open module.html with your favourite text editor. Make sure that hs watch is still running, and edit the file to look as follows:

<!-- module html  -->
<div>Hello, world!</div>

Now save it, and you should see the changes show up immediately in your preview:

Great. Now that we’ve learned how to work locally with the HubSpot CMS, we’re ready to get working with Phaser!

Creating Deal Fishing

Introducing Phaser.js

Phaser.js is a JavaScript game framework that works in any modern browser. It handles many things for you, including display and graphics, physics (which includes gravity and collisions), and more – all by tracking game objects, and passing events between them. We’ll learn how to wire up your game objects so they do what you want them to.

Setup and loading of assets (like sprites, which are images that’ll turn into your characters, or sound effects) is handled in preload , and create is where you’ll turn those assets into actual game objects. Those two methods are run once, at the creation of the game instance.

After that, the game loop continuously calls the update function. We can also respond to events emitted by animations, game objects, and more – we just have to subscribe to them and pass them a callback. We’ll learn more about when it’s appropriate to use update as opposed to handling changes in an event-driven way with callbacks.

That’s probably enough introduction! What better way to learn how this all works than to just get started? We’ll walk through preload, create and update in turn.

Creating the Deal Fishing mini game

First, download the assets we’ll be using from this link:

// TODO

Unzip those, and add them to your hubspot-local folder. We’ll come back to the assets in a few steps.

Next, copy and paste this boilerplate into the module.js file that was created in your deal-fishing.module folder:

var config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    physics: {
      default: 'arcade',
      arcade: {
        gravity: { y: 0 },
        debug: false
      }
    },
    scene: {
        preload: preload,
        create: create,
        update: update
    }
};

var pond;
var pondGroup;
var player;
var fishingZone;

var cursors;
var spacebarHeld = false;

var score = 0;
var scoreText;

var fishText;

var game = new Phaser.Game(config);

function preload ()
{
	// this gets called once at the very beginning when `game` is instantiated
}

function create ()
{
	// this gets called once, after `preload` is finished
	// anything loaded in `preload` is guaranteed to be accessible here
}

function update ()
{
	// the game loop calls `update` constantly
}

In this skeleton, we create the game object by passing it a config, which defines some basic pieces of what our game will contain (such as having arcade physics with a gravity key with y set to 0 – meaning it’s a top-scrolling game). We also define the variables we’ll be using later on as we load, use, and update our game objects.

And in your module.html file, we’ll add a couple lines so that it looks like this:

{{ module.text }}
{{ require_js('https://cdn.jsdelivr.net/npm/phaser@3.24.1/dist/phaser.min.js', 'head')}}

So far, we’ve loaded Phaser via a CDN. The require_js template tag there is part of the HubL templating language. Using it in your code rather than <script> gives you some helpful automatic optimizations, like preventing you from accidentally loading the same library many times over all your modules. And for Phaser to work, we need to have it load in the header, so we pass in the head parameter here to indicate to the CMS where it should load the script.

Now, let’s look back at the live preview. You can treat this window just like you would any tab you were using for local development. You can open up your browser’s Developer Tools and examine the console. Also, you can take a peek at the source of the page to see how the CRM nicely merged your module.js code, imported the Phaser.js file, and got things ready.

Right now, all we can see is the "Hello, world!" heading. That’s because of our text field, which is accessible via the HubL template tag ``. If we want to change that to Deal Fishing, just click on the field on the left of the Previewer screen:

Easy. That whole field is in rich, styled text, so all relevant HTML tags are included with the template tag already.

Otherwise, there’s not a lot to look at. That’s because we haven’t yet added any assets! So let’s try that out in preload().

Getting our assets ready to use

Loading your assets

Phaser loads everything upfront using the preload() function. So open up your module.js file, and add the following to preload() :

function preload ()
{
	// this gets called once at the very beginning when `game` is instantiated
  this.load.image('ground', 'https://f.hubspotusercontent20.net/hubfs/REPLACE_ME/assets/map.png');
	this.load.spritesheet('pond', 'https://f.hubspotusercontent20.net/hubfs/REPLACE_ME/assets/pond.png', { frameWidth: 54, frameHeight: 39});
	this.load.spritesheet('tile', 'https://f.hubspotusercontent20.net/hubfs/REPLACE_ME/assets/tile.png', { frameWidth: 16, frameHeight: 16});

	this.load.spritesheet('fishA', 'https://f.hubspotusercontent20.net/hubfs/REPLACE_ME/assets/FishA.png', { frameWidth: 16, frameHeight: 16});

	this.load.spritesheet('player', 'https://f.hubspotusercontent20.net/hubfs/REPLACE_ME/assets/hero.png', { frameWidth: 16, frameHeight: 24});
}

It’s pretty straightforward code: we use the load instance store on the scene to load an image, and also some spritesheets, which we’ll soon add to our game and make visible.

However, you still need to upload your assets, and replace the REPLACE_ME section of the URL with your own account’s unique ID.

Uploading your assets to HubSpot’s File Manager

To upload your assets and make them available to your module, we’ll use HubSpot’s File Manager to help us serve the files and make them accessible to our module in the cloud. I’ll show you how to upload from the CLI, and then see those files in the File Manager.

In your terminal, you should be in your root hubspot-local directory, and it should look like this:

deal-fishing.module/
assets/
hubspot.config.yml

Now, we’ll just upload the assets folder to the File Manager with the following command:

hs filemanager upload assets assets

That’ll take our local assets folder and put it in a new top-level folder on the File Manager also called assets .

Now, open your sandbox account back up in HubSpot, and open the File Manager from the top navigation bar:

Marketing > Files and Templates > Files

That’ll show you a screen like this:

Great! It looks like it worked – you can see assets hanging out there. Now, open up the assets folder, and select any of the files. We’ll be using their URL to load the file into Phaser. When you’ve clicked on a file inside the assets folder, a sidebar opens. Press ‘Copy URL’:

That should give you a URL like https://f.hubspotusercontent20.net/hubfs/1234567/assets/atlas.png . Now we’re going to add that URL in front of our assets. Take the first part of the URL – the part before /assets/atlas.png , and add it to each load call URL so that your code looks like this:

function preload ()
{
	// this gets called once at the very beginning when `game` is instantiated
  this.load.image('ground', 'https://f.hubspotusercontent20.net/hubfs/1234567/assets/map.png');
	this.load.spritesheet('pond', 'https://f.hubspotusercontent20.net/hubfs/1234567/assets/pond.png', { frameWidth: 54, frameHeight: 39});
	this.load.spritesheet('tile', 'https://f.hubspotusercontent20.net/hubfs/1234567/assets/tile.png', { frameWidth: 16, frameHeight: 16});

	this.load.spritesheet('fishA', 'https://f.hubspotusercontent20.net/hubfs/1234567/assets/FishA.png', { frameWidth: 16, frameHeight: 16});

	this.load.spritesheet('player', 'https://f.hubspotusercontent20.net/hubfs/1234567/assets/hero.png', { frameWidth: 16, frameHeight: 24});
}

Bravo. You’ve written your first chunk of code for Phaser. Let’s walk through it!

this refers to the scene. A scene contains game objects – it’s a bit like the world that you’re immediately playing in. More complicated games can have several different scenes that the game will move you through (for instance, as you’d complete one level and move onto the next). But we’re going to do all our work in just one of them.

After that, we call the spritesheet and image methods. What’s the difference? Well, image just takes the whole image, figures out its dimensions, and pastes it on the screen. But if we want to create animations, or just select one piece of an image to create a game object from, we should use spritesheet . Our player uses a spritesheet, but the main part of the map just uses image .

Each spritesheet and image is passed two parameters. The first is the key we’ll use to refer to the asset in the future – for atlas.png , it’s ground .

Now we’re ready to start adding stuff to the map, and we’ll see our first game objects appear.

Creating your scene

Let’s take those assets and put them to use!

First, we’ll add our main map – the atlas.png image, which we’ve given a key of ground . Now, in create() , we’ll get to use it:

function create ()
{
	// this gets called once, after `preload` is finished
	// anything loaded in `preload` is guaranteed to be accessible here

  // a generic background tile
  this.add.tileSprite(400, 300, 800, 600, "tile");

  // the nicer background
  this.add.image(400, 300, 'ground').setScale(2);

  // the on-screen text for scores and fish info
  scoreText = this.add.text(700, 550, "Score: 0", { align: 'right', fontFamily: 'Helvetica', color: 'black' });
  fishText = this.add.text(400, 50, "Welcome to Deal Fishing!", { align: 'center', fontFamily: 'Helvetica', color: 'black' });
  fishText.setOrigin(0.5);

  // let's create the pond we'll fish in
  pondGroup = this.physics.add.staticGroup();

  pond = pondGroup.create(390, 420, 'pond', 0).setScale(2).refreshBody();

  // create player
  player = this.physics.add.sprite(100, 450, 'player').setScale(2).refreshBody();
  player.setCollideWorldBounds(true);

  // create a collider between the player and the pond
  this.physics.add.collider(player, pondGroup);

  // create a zone to track when the player can fish
  fishingZone = this.add.zone(pond.x, pond.y, pond.width + 2, pond.height).setScale(2);
  this.physics.world.enable(fishingZone);


  // player animations

  this.anims.create({
    key: 'sideways',
    frames: this.anims.generateFrameNumbers('player', { start: 0, end: 2}),
    frameRate: 10,
    repeat: -1
  });

  this.anims.create({
    key: 'down',
    frames: this.anims.generateFrameNumbers('player', {start: 8, end: 10}),
    frameRate: 10,
    repeat: -1
  });

  this.anims.create({
    key: 'cast',
    frames: [ {key: 'player', frame: 3}],
    frameRate: 20,
    repeat: -1
  });

  this.anims.create({
    key: 'up',
    frames: this.anims.generateFrameNumbers('player', {start: 4, end: 6}),
    frameRate: 10,
    repeat: -1
  });

  this.anims.create({
    key: 'caughtFish',
    frames: [{key: 'player', frame: 13}],
    frameRate: 20,
    repeat: -1
  });


  // pond animations
  this.anims.create({
    key: 'pondFishing',
    frames: this.anims.generateFrameNumbers('pond', {start: 1, end: 2}),
    frameRate: 1,
    repeat: -1
  });

  this.anims.create({
    key: 'pondStill',
    frames: [ {key: 'pond', frame: 0}],
    frameRate: 20,
    repeat: -1
  });

  this.anims.create({
    key: 'pondBite',
    frames: this.anims.generateFrameNumbers('pond', {start: 3, end: 4}),
    frameRate: 5,
    repeat: 4
  });

  // create cursors for the pond
  cursors = this.input.keyboard.createCursorKeys();
}

There are three broad sections here. The first is where we set up a few game objects and define how they interact: the player’s object, the pond, and the fishing zone. The player and pond are both visible, but the zone isn’t – it’s an invisible layer that is just a teensy bit bigger than the pond, which we use to check for overlaps later on. We also set up some collisions and physics interactions there.

The second section is where we create a bunch of animations for the player and the pond. That’ll help us communicate what’s happening to the player. It’s one thing to have game logic that works, but without making it obvious through animations, it’s not that helpful! Each animation is tied to the above assets that we loaded in preload() - we generate an animation from spritesheets we created there. We also give each animation its own key so that we can easily use it later on.

Also, every animation has a -1 repeat value, except for the pondBite animation. Why is that? Because -1 means the animation repeats indefinitely, we’ll have to manually stop those animations or change the animation to another one. For pondBite, we want it to run for a limited amount of time so as to make the game harder. So it repeats 4 times, for a total of about 1 second. After all, fish don’t hold on forever.

Finally, we need some way to notice what the player does, so we create inputs using the cursors variable.

If you save your module.js file now, that should show up immediately in your Previewer – but it’s probably boring to play, as we haven’t done anything with the inputs yet:

That means we better go and get the player inputs working, as well as trigger specific animations in response to the right inputs.

Handle inputs and trigger animations

function update ()
{
  if (player.state != "fishing") {
    // movement should be allowed when they're not actively fishing
    if (cursors.up.isDown) {
      player.setVelocity(0, -160);

      player.anims.play('up', true);
    } else if (cursors.down.isDown) {
      player.setVelocity(0, 160);

      player.anims.play('down', true);
    } else if (cursors.left.isDown) {
      player.setVelocity(-160, 0);

      player.setFlipX(false);
      player.anims.play('sideways', true);
    } else if (cursors.right.isDown) {
      player.setVelocity(160, 0);

      player.setFlipX(true);
      player.anims.play('sideways', true);
    } else {
      // if the above keys are being pressed, the user shouldn't be moving
      player.setVelocity(0, 0);

      player.anims.pause();
    }
  }

  // the player pressed space while inside the fishing zone, and isn't already holding the spacebar
  if (cursors.space.isDown && (this.physics.world.overlap(player, fishingZone)) && spacebarHeld === false) {
    spacebarHeld = true;
    console.log("Fishing!")

    if (player.state === "fishing") {
      // The player is currently fishing
      if (pond.anims.getCurrentKey() === "pondBite") {
        // they pressed space while there was a fish biting
        // successful catch!
        // first, create a fish object
        fish = this.physics.add.sprite(pond.getCenter().x, pond.getCenter().y, 'fishA', 4).setOrigin(0.5, 0.5).setScale(3).refreshBody();
        // then animate it up to the top of the player's head
        var tween = this.tweens.add({
          targets: fish,
          x: player.getTopCenter().x,
          y: player.getTopCenter().y,
          ease: 'Linear',
          completeDelay: 1000,
          onComplete: function () {
            fish.destroy();
            player.state = "normal";

            // TODO: POST new fish here
          }
        });

        // now we'll show the player celebrate
        player.anims.play('caughtFish', true);

        // we'll clear the pond's animation chain and reset everything
        pond.anims.stop();
        pond.anims.play('pondStill');
      } else {
        // the player missed the fish bite :(
        // now to stop fishing
        player.anims.play('sideways', true);

        pond.anims.play('pondStill', true);

        player.state = "normal";
      }

    } else {
      // The player should begin fishing!
      player.anims.play('cast', true);

      pond.anims.play('pondFishing');

      pond.anims.chain('pondBite');

      pond.anims.stopAfterDelay(Phaser.Math.Between(2000,4000));

      pond.on('animationcomplete-pondBite', finishedFishing);
      pond.anims.chain('pondFishing');

      player.state = "fishing";
    }

  } else if (cursors.space.isUp) {
    spacebarHeld = false;
  }
}

function finishedFishing (animation, frame, gameObject)
{
  if (player.state === 'fishing') {
    // stop the current animation
    pond.anims.stopAfterDelay(Phaser.Math.Between(2000,4000)); // random

    // add a new animation
    pond.anims.chain('pondBite');
    pond.anims.chain('pondFishing');

  }
}

That’s it – you’ve got a working game now!

The game continuously loops and calls the update() function. So, that’s where we respond to user-initiated input. Here, we have to basic sections. In the first set of conditions, we’re just making sure the user isn’t already fishing, and handle all the movement-based inputs behind that logic.

In the second set of conditions, we’re watching to see when we should get the user to start fishing, catch a fish, and stop fishing if they miss the fish. We create an animation chain that will show a fishing animation, then after a random amount of time will show the pondBite animation for a brief moment (remember: it runs for a limited amount of time, unlike the other animations), before returning to the typical fishing animation. In order to keep that going, we also have to create a callback, and so we subscribe to the animationcomplete-pondBite event. We get it to call the finishedFishing() function, where we then set up this same animation chain all over again if the player missed the opportunity to catch the fish on the first bite.

To keep track of what happens, we check what the player.state property is set to, as well as check what animation is playing. That’s all it takes to make a game!

Winning the game

When the player successfully catches the fish by pressing space when the pondBite animation is playing, we create a fish object, then animate it towards the player object using a tween object. It’s an object managed by the scene which allows us to manipulate values over time. We use it to change the x and y position of the fish, then pass an onComplete callback to destroy the fish and reset the player state to "normal" after a delay. Then they can go fish again.

That onComplete callback is also where we’ll be posting the fish to the Serverless Function that we’ll be making shortly. Take note:

…
onComplete: function () {
  fish.destroy();
  player.state = "normal";

  // TODO: POST new fish here
}
…

That’s it! If you open up your Previewer tab again, you can move your player around, get them to either side of the pond, and press spacebar to start fishing. Happy fishing, everyone!

Introducing Serverless Functions

Setting up the Serverless Function

hs create function "fishing-deals"
? Name of the folder where your function will be created: fishing-deals
? Name of the JavaScript file for your function: post-deals
? Select the HTTP method for the endpoint: POST
? Path portion of the URL created for the function: deals
Created "/Users/konoff/Developer/hubspot/1-deal-fishing/hubspot-components/fishing-deals/..functions"
Created "/Users/konoff/Developer/hubspot/1-deal-fishing/hubspot-components/fishing-deals/..functions/post-deals.js"
Created "/Users/konoff/Developer/hubspot/1-deal-fishing/hubspot-components/fishing-deals/..functions/serverless.json"
[SUCCESS] A function for the endpoint "/_hcms/api/deals" has been created. Upload "..functions" to try it out

Now, we’ll go into the fishing-deals/fishing-deals.function folder, and open up post-deals.js. That’s where our function lives. Every time we get a call to _hcms/api/deals, it runs the code in here. At the beginning, it just looks like this:

exports.main = ({ accountId, body, params }, sendResponse) => {
  console.log('Your HubSpot account ID: %i', accountId);
  sendResponse({
    statusCode: 200,
    body: {
      message: 'Hello, world!',
    },
  });
};

We’re going to make a few changes: we’ll create a random name for the fish, give it a score, post it to the HubSpot CRM (which we’ll use as our datastore), and return a 201 instead of a 200, as well as the score and name.

Add your API key

Before all that, let’s get your API key added to your CLI, then safely bundled into your Serverless Function.

First, head to your Account Settings to grab your API key. Make sure you select the appropriate sandbox account. You may have to create an API key if you haven’t before, then copy it.

Now, in your terminal, we’ll add the secret to the HubSpot CLI, which in turn makes it available in your cloud environment:

hs secrets add hubapikey YOUR_API_KEY
The secret "hubapikey" was added to the HubSpot portal: 8311725

Now, open up the serverless.json file in your function folder. We’ll add the name of the secret there, inside the secrets array:

{
  "runtime": "nodejs12.x",
  "version": "1.0",
  "environment": {},
  "secrets": ["hubapikey"],
  "endpoints": {
    "deals": {
      "method": "POST",
      "file": "post-deals.js"
    }
  }
}

That’s all we need to do to make hubapikey available in the function as an environment variable.

Back to the post-deals.js file, we’ll set it up to create a new contact each time you catch a fish:

var request = require("request");

var fishNames = ["Nemo",  "Bubbles",  "Jack",  "Captain",  "Finley",  "Blue",  "Moby",  "Bubba",  "Squirt",  "Shadow",  "Goldie",  "Dory",  "Ariel",  "Angel",  "Minnie",  "Jewel",  "Nessie",  "Penny",  "Crystal",  "Coral"]
var colors = ["White", "Yellow", "Blue", "Red", "Green", "Black", "Brown", "Azure", "Ivory", "Teal", "Silver", "Purple", "Navy", "PeaGreen", "Gray", "Orange", "Maroon", "Charcoal", "Aquamarine", "Coral", "Fuchsia", "Wheat", "Lime", "Crimson", "Khaki", "HotPink", "Magenta", "Olden", "Plum", "Olive", "Cyan"]

exports.main = ({ accountId, body, params }, sendResponse) => {
  console.log('Your HubSpot account ID: %i', accountId);
  console.log('Your fish size: ' + body.fish_size);

  var firstName = fishNames[Math.floor(Math.random() * fishNames.length)];
  var lastName = ("Mc" + colors[Math.floor(Math.random() * colors.length)]);

  console.log("The API key: " + process.env.hubapikey);

  var options = {
    method: 'POST',
    url: 'https://api.hubapi.com/crm/v3/objects/contacts',
    qs: {hapikey: process.env.hubapikey},
    headers: {accept: 'application/json', 'content-type': 'application/json'},
    body: {
      properties: {
        firstname: firstName,
        lastname: lastName,
        annualrevenue: body.fish_size
      }
    },
    json: true
  };

  request(options, function (error, response, body) {
    if (error) throw new Error(error);

    console.log("The " + firstName + " " + lastName + " contact was successfully created");

    sendResponse({
      statusCode: 201,
      body: {
        fish_name: (firstName + ' ' + lastName),
        score: body.properties.annualrevenue,
        message: ('Your fish is named ' + firstName + ' ' + lastName)
      },
    });
  });
};

From the top to the bottom, let’s walk through how our function works.

First, we import request using require(). That’ll let us make a POST request to the HubSpot /contacts API, which you can read more about here. Then we create a couple arrays to make some creative fish names.

Inside the exports.main method, we first log a couple helpful things, which we’ll be able to see in the logs. Then we create a first and last name, and add those to a request. Finally, we handle the request. If there’s no error, we use sendResponse() to send the saved fish name, score, and a helpful message back to the client.

Now we’re going to get a webpage going, so that we can test out the Serverless Function, and share our new working game.

Testing and publishing your game

Creating a Template

Templates allow you to combine as many modules as you’d like into a

Head back to your root hubspot-local folder, and create a new template in the CLI:

hs create template deal-fishing-demo
? Select the type of template to create: page
Creating file at ~/hubspot-local/deal-fishing-demo.html

That creates a simple deal-fishing-demo.html file. Open it up – you’ll see there’s already a special comment at the top indicating that it’s a template. Next, replace the template’s HTML with this:

<!--
    templateType: page
    label: Deal Fishing template
    isAvailableForNewContent: true
-->
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>{{ content.html_title }}</title>
    <meta name="description" content="{{ content.meta_description }}">
    {{ standard_header_includes }}
  </head>
  <body>
    {% module "module_YOURMODULENUMBER" path="/deal-fishing-tutorial", label="deal-fishing-tutorial" %}
    {{ standard_footer_includes }}
  </body>
</html>

We changed the label up top to be something easier to find, but the important line here is the first line in <body>. You’ll have to grab your module’s unique ID back in the Design Manager – just open the deal-fishing module in there, and scroll down to the bottom of the sidebar to copy a module template snippet:

Alternatively, if you have your Previewer tab open, you can just replace the module_YOURMODULENUMBER part of the template code with the final part of your Previewer’s URL. My URL is https://app.hubspot.com/design-previewer/1234567/modules/34070159116, so I’d change that to module_34070159116.

Now, we’ll create a new page using the template you just made. In the top bar, go to Marketing > Website > Website Pages, and press Create in the top right corner, then select Website page.

With the search bar in the top right, let’s search for “Deal Fishing template” and select that template. Finally, give it a name – maybe Deal Fishing Game. Click Create Page, then we’re nearly done.

But it won’t let us press publish yet. You’ll need to head to the Settings bar and do two things there. First, add a Page Title. Then copy the link to the page. Finally, press Publish, and open up your brand new page:

From the page link we just copied, we’re only going to copy the subdomain part of that URL:

http://yoursandboxusername-1234567.hs-sites.com

In this case, it’d be yoursandboxusername-1234567. That’s all we need to be able to call our Serverless Function from within the app. Keep that tab around just so we can test it.

Putting it all together

It’s time to put it all together by calling the function in our game’s code. Head back to your module.js inside the deal-fishing.module folder, and find the comment called // POST new fish here, inside the update() function. There, you should add this code:

// POST new fish here
fetch("//YOUR_SITE_SUBDOMAIN.hs-sites.com/_hcms/api/deals", {
  method: "POST",
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ fish_size: Phaser.Math.Between(75, 250)})
}).then(res => {
  return res.json()
}).then(data => {
  console.log(data);

  // update the score
  score += data.score;
  scoreText.setText("Score: " + score);

  // create a message about the fish you caught
  var fishSize = data.score;

  var fishMessage;
  if (fishSize > 200) {
    fishMessage = "You caught a huge fish!";
  } else if (fishSize > 100) {
    fishMessage = "You caught a very medium fish!";
  } else {
    fishMessage = "You caught a tiny little fish!";
  }
  fishText.setText(fishMessage + "\n" + data.message + ".");
});

Replace the YOUR_SITE_SUBDOMAIN with the page, save your changes, and refresh the new hs-sites.com page you created. If you open up your console in that tab, you’ll see something like this:

Finally, you can check your logs using the CLI. Just pass it the name of your serverless function – fishing-deals, in this case:

hs logs fishing-deals
2020-09-02T23:02:25.861Z - SUCCESS - Execution Time: 1853ms
{ body:
   { score: '200',
     message: 'Your fish is named Squirt McYellow',
     fish_name: 'Squirt McYellow' },
  statusCode: 201 }
2020-09-02T23:02:26.758Z	INFO	Your HubSpot account ID: 8311725
2020-09-02T23:02:26.765Z	INFO	Your fish size: 197
2020-09-02T23:02:26.765Z	INFO	The API key: YOUR_API_KEY
2020-09-02T23:02:27.686Z	INFO	The Squirt McYellow contact was successfully created

That’s it! In a coming tutorial, we’ll learn how to create scoreboard that’ll add up all our contacts annualrevenue property values to give us our high score for the day. For now, just enjoy the great outdoors and the HubSpot Carnival.

https://knowledge.hubspot.com/contacts/hubspots-default-contact-properties?_ga=2.230543132.1282486909.1598903654-1418744260.1597871852