Building Functional Web Apps with Redis

Simon MacDonald’s avatar

by Simon MacDonald
@macdonst
on

hero image

In this tutorial, we’ll use Begin to quickly develop a to-do list application. We’ll use AWS Lambda as our serverless platform and Redis Enterprise Cloud as our database provider.

Let’s get started.

Prerequisites

You will need to have git and Node.js installed to your local computer to follow along with this tutorial. (Learn more about installing git and installing Node.js.)

You’ll also need a GitHub account. (Learn more about signing up with GitHub.)

This tutorial also assumes some familiarity with such things as:

  • Text editors
  • Terminal / CLI
  • Git and version control
  • General software development using JavaScript

You do not need to be an expert in any of these things to follow along though.

Getting started

First, click the Deploy to Begin button below. This starts the process of authorizing Begin with your GitHub account. (You may be prompted to log in to GitHub, and/or be asked to create a Begin username.)

deploy-to-begin

Name your app & repo

You’ll then be prompted to name your new app and repository – this is optional. Feel free to use the default app and repo name if you like!

Note: your Begin app name and repository name cannot be changed later.

Create App

Once you’ve clicked the Create... button, Begin will spin up your new project on GitHub (under github.com/{your GH username}/{your repo name}).

By default your Begin app repo is created as a public GitHub repo; it can be set to private by granting Begin additional permissions from this screen (or later from the Settings screen found in the left nav of your Begin app).

Your first deploy

After creating your app, you’ll be taken to its Activity stream. Welcome to the main backend interface of your Begin app!

Build App

From the Activity view, you’ll be able to watch your app build & deploy in real-time. Any time you push to main, you’ll see a new build get kicked off in Begin.

Each build undergoes several predefined build steps (learn more about build steps here); these build steps may install your app’s dependencies (install), test your code’s syntax (lint), generate any files or assets needed to run your app (build), and/or run an automated test suite (test).

If no build steps fail, then the build containing your latest commit to main is automatically deployed to your staging environment.

Go ahead and click the Staging link in the upper left corner of your left nav to open your new app’s staging URL. You should now see your new app:

Staging

Get set up locally

Next, let’s get your new site running in your local environment (i.e. the computer you work on).

First, head to your GitHub repo (from the first card in your Activity, or the left nav). Find the clone or download button and copy the git URL.

Then head to your terminal and clone your repo to your local filesystem.

git clone https://github.com/your-github-username/your-new-begin-app.git

Once you’ve got your project cloned on your local machine, cd into the project directory and install your dependencies:

cd your-new-begin-app
npm install

Now you are all set to work on your app locally!

Project structure

Now that your app is live on staging and running locally, let’s take a quick look into how the project itself is structured so you’ll know your way around. Here are the key folders and files in the source tree of your new app:

.
├── public/
│   ├── index.html
│   └── index.js
├── src/
│   ├── http/
│   │    ├── get-todos/
│   │    ├── post-todos/
│   │    ├── post-todos-000id/
│   │    └── post-todos-delete/
│   └── shared/
│        └── redis-client.js
└── app.arc

public/index.html & public/index.js

public/index.html is the page served to the browser. This is where our JSON data will be appended to a DOM element of our choosing. public/index.js is where we will write our function that fetches the JSON data from get /todos and displays it in our HTML page.

Your app utilizes built-in small, fast, individually executing cloud functions that handle HTTP API requests and responses. (We call those HTTP functions, for short.)

The HTTP function that handles requests:

  • get /todos is found in src/http/get-todos/
  • post /todos is found in src/http/post-todos/
  • post /todos/:id is found in src/http/post-todos-000id/
  • post /todos/delete is found in src/http/post-todos/delete.

In the next section, we will go more in-depth about how to manipulate data stored in Redis from an HTTP Function.

src/shared/redis-client.js

The redis-client.js file is where we will write common code that will be used in all of our HTTP functions. This code will automatically get copied into each HTTP function during the hydration phase of the install build step.

app.arc

Your app.arc file is where you will provision new routes and functions.

Infrastructure-as-code is the practice of provisioning and maintaining cloud infrastructure using a declarative manifest file. It’s like package.json, except for cloud resources like API Gateway, Lambda, and DynamoDB (all of which Begin apps use).

By checking in your Begin app’s project manifest (app.arc) file with your code, you can ensure you have exactly the cloud resources your code depends on. This is crucial for ensuring reproducibility and improving iteration speed.

Access Redis Enterprise Cloud from HTTP Functions

Let’s dig into how we connect to Redis Enterprise Cloud and manipulate data via HTTP Functions. Open src/http/get-todos/index.js:

In the first four lines of the function:

  • We require the http middleware from @architect/functions.
  • Include some helper functions from our redis-client.js package which are shared with all of our HTTP functions
  • Setup our handler to call the async functions clientContext and read in that order.
// src/http/get-todos/index.js
const { http } = require('@architect/functions')
const { createConnectedClient, clientContext, clientClose } = require('@architect/shared/redis-client')

exports.handler = http.async(clientContext, read)

Over in src/shared/redis-client.js we will find the clientContext function.

// src/shared/redis-client.js
async function clientContext(req, context) {
 process.env.ARC_ENV === 'testing' ?
   context.callbackWaitsForEmptyEventLoop = true :
   context.callbackWaitsForEmptyEventLoop = false
}

This function may look weird, and that’s because it is. When we are running locally the architect sandbox spins up a new Node.js process to handle each incoming request. Once the request is fulfilled the process exits. When the function runs as an AWS Lambda we will have the option of keeping the connection to Redis Enterprise Cloud open to serve multiple requests.

Now let’s look at the read function from src/http/get-todos/index.js. The first line in the function is where we await the createConnectedClient helper function.

// src/http/get-todos/index.js
const client = await createConnectedClient()

Our createConnectedClient function will setup some event listeners that will log the connect, error and end events which are useful when debugging your application. Then the function will connect to your Redis Enterprise Cloud database and return a connected redis client.

// src/shared/redis-client.js
const redis = require('redis');

const client = redis.createClient({
  url: process.env.REDIS_URL
})

async function createConnectedClient() {
  client.on('connect', () => {
    console.log("connected to redis");
  })

  client.on("error", (error) => {
    console.error(error);
  });

  client.on("end", () => {
    console.log("dis-connected from redis");
  });

  await client.connect()
  return client;
}

Aside

Our todo list items will be stored in the Redis database as a hash. Each todo item will have a unique key, and the value of that key is the JSON stringified version of the todo. For example:

{
  'tmhfqiVZq8Y7TFnaQHwOS': '{"text":"publish the blog post"}',
  '6lHfPRoOMvm8EgWqHOxsS': '{"completed":true,"text":"create screenshots"}',
  'EcrTh_FrpWp3sHgN3-3Lw': '{"completed":true,"text":"write the docs"}'
}

The client from the redis npm package provides access to all of the out-of-the-box Redis commands. Since we are going to store our todos as a hash in the database, we will use the following commands:

  • hGetAll: returns all of our todo list items stored at our key
  • hSet: creates or updates a single todo list item
  • hDel: removes a todo list item

In order to fetch our todos from our collection we execute the following code:

// src/http/get-todos/index.js
let todos = await client.hGetAll('todos')

The next line in src/http/get-todos.js is:

// src/http/get-todos/index.js
await clientClose()

The redis npm package provides two ways to close connections to the database. quit gracefully closes the connection and awaits a response from Redis. disconnect immediately closes the connection. Since we want our CRUD actions to succeed we will use quit.

// src/shared/redis-client.js
async function clientClose() {
  client.quit()
}

Finally we’ll return the todos as the HTTP functions response

// src/http/get-todos/index.js
return {
  statusCode: 200,
  cacheControl: 'no-cache, no-store, must-revalidate, max-age=0, s-maxage=0',
  json: todos
}

Since we are using the arc.http.async helper function we can use some shortcuts when we return a payload from the function.

  • statusCode sets the response to 200 OK
  • cacheControl sets the cache-control header
  • json set the content-type header to application/json; charset=utf8 while also JSON encoding the todos array for us.

Full code listings for:

Getting Up and Running with Redis Enterprise Cloud

Visit developer.redis.com/create/rediscloud/ and create a free Redis Enterprise Cloud account. Once you complete this tutorial, you will be provided with the database PUBLIC ENDPOINT and DEFAULT USER PASSWORD. Save them for future reference.

Redis Console

Developing Locally

One of the main advantages of using Begin is the ability to develop your application business logic locally.

At the root of your project create a file called .env. This is where we will store the connection string we generated in the previous step.

# .env

@testing

REDIS_URL=redis://:<DEFAULT USER PASSWORD>@<PUBLIC ENDPOINT>

Now, in the same folder that you ran npm install previously run:

npm start

Navigate to localhost:3333/ and you will see a copy of your application running locally. Experiment with adding, editing and deleting some todo list items then come back here, we’ll wait.

You can also setup a Redis instance locally. The Redis Developer Portal has detailed instructions on how to install Redis for Mac, Windows or build it from source.

Deploy Your Site

Head back over to Begin and open the environments panel. It is here that we will need to set our REDIS_URL environment variable. Scroll down to the Staging section and add a new environment variable called REDIS_URL. The REDIS_URL follows the format:

redis://:<DEFAULT USER PASSWORD>@<PUBLIC ENDPOINT>

Environment Variables

Now in the top left-hand corner click on the Staging link. Try adding, editing and deleting todos. Notice that it is even faster than the local development as our HTTP functions are able to re-use the database connection?

Once you are satisfied that everything is working it’s time to deploy to production. Click the Deploy to production button in the upper left, pick a version, leave an optional message summarizing your changes, and Ship it!

When your next build is done, click the production link in the upper left corner to see the latest release of your app.

Congratulations!

You now have a good idea how HTTP functions work on Begin and how to use Redis Enterprise Cloud as a backend data store.