# Source: https://upstash.com/docs/redis/tutorials/histogram.md

> ## Documentation Index
> Fetch the complete documentation index at: https://upstash.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Build a Serverless Histogram API with Redis

> This tutorial shows how to build a histogram API with Redis.

While developing
[the latency benchmark for the serverless databases (DynamoDB, FaunaDB, Upstash)](https://blog.upstash.com/latency-comparison),
I wished there was an API where I will record the latency numbers and get the
histogram back. In this tutorial, I will build such an API where you can record
your latency values from any application. It will be a REST API with following
methods:

* record: Records numeric values into the histogram.
* get: Returns the histogram object.

### Motivation

I will show how easy to develop a generic API using AWS Lambda and Serverless
Redis.

See [code](https://github.com/upstash/examples/tree/master/examples/histogram-api).

### `1` Create a Redis (Upstash) Database

Create a database as [getting started](../overall/getstarted)

### `2` Serverless Project Setup

If you do not have it already install serverless framework via:
`npm install -g serverless`

In any folder run `serverless` as below:

```text  theme={"system"}
>> serverless

Serverless: No project detected. Do you want to create a new one? Yes
Serverless: What do you want to make? AWS Node.js
Serverless: What do you want to call this project? histogram-api

Project successfully created in 'histogram-api' folder.

You can monitor, troubleshoot, and test your new service with a free Serverless account.

Serverless: Would you like to enable this? No
You can run the “serverless” command again if you change your mind later.
```

<Note>
  See [Using AWS SAM](/redis/tutorials/using_aws_sam), if you prefer AWS SAM
  over Serverless Framework.
</Note>

Inside the project folder create a node project with the command:

```
npm init
```

Then install the redis client and histogram library with:

```
npm install ioredis

npm install hdr-histogram-js
```

Update the `serverless.yml` as below. Copy your Redis URL from console and
replace below:

```yaml  theme={"system"}
service: histogram-api
frameworkVersion: "2"

provider:
  name: aws
  runtime: nodejs12.x
  lambdaHashingVersion: 20201221
  environment:
    REDIS_URL: REPLACE_YOUR_URL_HERE

functions:
  record:
    handler: handler.record
    events:
      - httpApi:
          path: /record
          method: post
          cors: true
  get:
    handler: handler.get
    events:
      - httpApi:
          path: /get
          method: get
          cors: true
```

<Snippet file="redis/ioredisnote.mdx" />

### `3` Code

Edit handler.js as below.

```javascript  theme={"system"}
const hdr = require("hdr-histogram-js");
const Redis = require("ioredis");
if (typeof client === "undefined") {
  var client = new Redis(fixUrl(process.env.REDIS_URL));
}
const headers = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Credentials": true,
};
const SIZE = 10000;

module.exports.get = async (event) => {
  if (!event.queryStringParameters || !event.queryStringParameters.name) {
    return {
      statusCode: 400,
      headers: headers,
      body: JSON.stringify({
        message: "Invalid parameters. Name is needed.",
      }),
    };
  }
  const name = event.queryStringParameters.name;
  const data = await client.lrange(name, 0, SIZE);
  const histogram = hdr.build();
  data.forEach((item) => {
    histogram.recordValue(item);
  });

  return {
    statusCode: 200,
    body: JSON.stringify({
      histogram: histogram,
    }),
  };
};

module.exports.record = async (event) => {
  let body = JSON.parse(event.body);
  if (!body || !body.name || !body.values) {
    return {
      statusCode: 400,
      headers: headers,
      body: JSON.stringify({
        message: "Invalid parameters. Name and values are needed.",
      }),
    };
  }
  const name = body.name;
  const values = body.values;
  await client.lpush(name, values);
  return {
    statusCode: 200,
    body: JSON.stringify({
      message: "Success",
      name: name,
    }),
  };
};

function fixUrl(url) {
  if (!url) {
    return "";
  }
  if (url.startsWith("redis://") && !url.startsWith("redis://:")) {
    return url.replace("redis://", "redis://:");
  }
  if (url.startsWith("rediss://") && !url.startsWith("rediss://:")) {
    return url.replace("rediss://", "rediss://:");
  }
  return url;
}
```

We have two serverless functions above. `get` takes `name` as parameter and
loads a list from Redis. Then builds a histogram using the values in the list.

The `record` function takes `name` and `values` as parameters. It adds the
`values` to the Redis List with name `name`.

The `get` function calculates the histogram over the latest 10000 latency
records. Update the SIZE parameter to change this number.

The `fixUrl` is a helper method which corrects the Redis url format.

### `4` Deploy and Try the API

Deploy your functions with:

```bash  theme={"system"}
serverless deploy
```

The command will deploy two functions and output two endpoints. Try the
endpoints with setting parameters as below:

Record latency numbers to `perf-test-1`:

```shell  theme={"system"}
curl --header "Content-Type: application/json" -d "{\"name\":\"perf-test-1\", \"values\": [90,80,34,97,93,45,49,57,99,12]}" https://v7xx4aa2ib.execute-api.us-east-1.amazonaws.com/record
```

Get the histogram for `perf-test-1`:

```shell  theme={"system"}
curl https://v7xx4aa2ib.execute-api.us-east-1.amazonaws.com/get?name=perf-test-1
```

### Batching

It can be costly to call a remote function each time for latency calculation. In
your application, you should keep an array or queue as a buffer for the latency
numbers, then submit them in batches to the API when the array reaches the batch
size. Something like below:

```javascript  theme={"system"}
let records = [];
let batchSize = 1000;
function recordLatency(value) {
  records.push(value);
  if (records.length >= batchSize) {
    // the below submits the records to the API then empties the records array.
    submitToAPI(records);
  }
}
```