Serverless click tracker with OpenJS Architect
Published: 2021-01-06 19:34:38

Event functions are useful for running background tasks like handling newsletter signups or click tracking. These background tasks are going to allow your application to fan out the workload to independent and scalable Lambda functions. In this article, we will be building an interactive click tracker that generates an event with a data payload that is saved to DynamoDB.

## Clone repo and local development

The first step is to click the button to deploy this app to live infrastructure with Begin.

[![Deploy to Begin](https://static.begin.com/deploy-to-begin.svg)](https://begin.com/apps/create?template=https://github.com/begin-examples/node-events)

Underneath, Begin will create a new GitHub repo to your account to clone to work on locally. Each push to your default branch will trigger a new build and deploy to the `staging` environment. Your CI/CD is already complete!!

When your app deploys, clone the repo, and install the dependencies.

```bash
git clone https://github.com/[username]/[your-app-name.git]
cd your-app-name
npm install
```
Let's first take a look at the `app.arc` file: 

```bash
@app
node-events

@http
post /my-event

@static

@events
my-event

@tables
data
  scopeID *String
  dataID **String
  ttl TTL
```
The `app.arc` file is your Infrastructure as Code, IaC. It shows the cloud functions and database that we need to deploy. 

Let's look at the HTTP function that receives a POST request from the front-end. It will publish the event payload to our event function.

```javascript
// src/http/post-my_event/index.js

const arc = require('@architect/functions')

exports.handler = async function http (req) {
  const name = 'my-event'
  const payload = arc.http.helpers.bodyParser(req)
  await arc.events.publish({ name, payload })
  return {
    statusCode: 302,
    headers: {
      location: '/'
    }
  }
}
```
Next, we'll look at the event function itself. We use an Architect runtime helper to subscribe this function to the `my-event` SNS topic. 

```javascript
// src/events/my-event/index.js
const arc = require('@architect/functions')
const data = require('@begin/data')
const table = 'interactions'
const key = 'clicks'

async function myEvent(event) {
  let { name } = event
  await data.incr({
    table,
    key,
    prop: name
  })

  return
}

exports.handler = arc.events.subscribe(myEvent)
```
And finally, the front end is composed of form elements that POST the value to our HTTP function. 

```HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Begin Event Functions Example</title>
</head>
<style>
 :root {
    --light: #FEFEFE;
    --purple: #8D30FF;
    --magenta: #FF577B;
    --angle: 45deg;
  }
  * {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
  }
  body {
    color: var(--light);
    font-family:
      ui-sans-serif,
      system-ui,
      -system-ui,
      -apple-system,
      BlinkMacSystemFont,
      Roboto, Helvetica, Arial,
      sans-serif,
      "Apple Color Emoji";
      line-height: 1.6;
  }
  html,
  body,
  .height-100 {
    height: 100%;
  }
  .margin-bottom1 {
    margin-bottom: 1rem;
  }
  .padding-top5 {
    padding-top: 9rem;
  }
  .max-width22 {
    max-width: 22rem;
  }
  .display-flex {
    display: flex;
  }
  .flex-direction-column {
    flex-direction: column;
  }
  .align-items-initial {
    align-items: initial;
  }
  .justify-content-center {
    justify-content: center;
  }
  .justify-content-space-between {
    justify-content: space-between;
  }
  .cursor-pointer {
    cursor: pointer;
  }
  .border-solid {
    border: 2px solid;
  }
  .color-light {
    color: var(--light);
  }
  .color-purple-active:active {
    color: var(--purple);
  }
  .linear-gradient {
    background-image: linear-gradient(var(--angle), var(--magenta), var(--purple));
  }
  .background-size-cover {
    background-size: cover;
  }
  .background-color-transparent {
    background-color: transparent;
  }
  .background-color-light-active:active {
    background-color: var(--light);
  }
  .border-color-light {
    border-color: var(--light);
  }
  .border-radius-pill {
    border-radius: 999px;
  }
  .padding1 {
    padding: 1rem;
  }
  .padding-top1 {
    padding-top: 1rem;
  }
  .padding-right2 {
    padding-right: 1.5rem;
  }
  .padding-bottom1 {
    padding-bottom: 1rem;
  }
  .padding-left2 {
    padding-left: 1.5rem;
  }
  .font-size1 {
    font-size: 1rem;
  }
  .font-weight-500 {
    font-weight: 500;
  }

  @media only screen and (min-width:30em) {
    .flex-direction-row-lg {
      flex-direction: row;
    }
    .align-items-center-lg {
      align-items: center;
    }
  }
</style>
<body
  class="
    padding-top5
    padding-right2
    padding-left2
    linear-gradient
    display-flex
    flex-direction-column
    align-items-initial
    align-items-center-lg
  "
 >
  <div
    class="
      max-width22
    "
  >
    <h1
      class="
        margin-bottom1
      "
    >
      Publish an event
    </h1>
    <div
      class="
        display-flex
        flex-direction-column
        flex-direction-row-lg
        justify-content-space-between
        margin-bottom1
      "
    >
      <form
        action=/my-event
        method=POST
      >
        <input type="hidden" name="name" value="Bark">
        <button
          class="
            margin-bottom1
            padding-top1
            padding-right2
            padding-bottom1
            padding-left2
            font-size1
            font-weight-500
            color-light
            color-purple-active
            border-radius-pill
            border-solid
            border-color-light
            background-color-transparent
            background-color-light-active
            cursor-pointer
          "
        >
          🐶 Bark
        </button>
      </form>
      <form
        action=/my-event
        method=POST
      >
        <input type="hidden" name="name" value="Meow">
        <button
          class="
            margin-bottom1
            padding-top1
            padding-right2
            padding-bottom1
            padding-left2
            font-size1
            font-weight-500
            color-light
            color-purple-active
            border-radius-pill
            border-solid
            border-color-light
            background-color-transparent
            background-color-light-active
            cursor-pointer
          "
        >
          😺 Meow
        </button>
      </form>
      <form
        action=/my-event
        method=POST
      >
        <input type="hidden" name="name" value="Moo">
        <button
          class="
            margin-bottom1
            padding-top1
            padding-right2
            padding-bottom1
            padding-left2
            font-size1
            font-weight-500
            color-light
            color-purple-active
            border-radius-pill
            border-solid
            border-color-light
            background-color-transparent
            background-color-light-active
            cursor-pointer
          "
        >
          🐮 Moo
        </button>
      </form>
    </div>
    <p>
    Now go check out the click tracking in your app's <a class="color-light font-weight-500" href="https://begin.com/forward/data">Begin Data console →</a>
    </p>
  </div>
</body>
</html>
```
To sum up the pattern shown here, we have a web form element that POSTs data to a Lambda function. The data is then published to an SNS Topic which is processed by an event function that is subscribed to the same SNS Topic.