# Building an API Key Generator with Upstash Redis

> **Source:** https://upstash.com/blog/api-key-generator-upstash-redis
> **Date:** 2024-11-13
> **Author(s):** Evansso
> **Reading time:** 10 min read
> **Tags:** redis, serverless, api, cloudflare-workers, tutorial
> **Format:** text/markdown — machine-readable content for agents and LLMs

---

API keys are like the front-door keys to your service—they let users in while keeping things secure. In this blog, I’ll walk you through building a simple, secure API key generator using Upstash Redis for fast, serverless data storage and Cloudflare Workers to handle requests at the edge. Whether you’re setting up a new service or adding keys to an existing app, you’ll learn how to generate, store, and validate API keys to keep everything running smoothly and efficiently.

## What is an API Key?

An API key is a unique code that identifies and authenticates a user or application trying to access your API. Think of it like a personal pass: when someone wants to use your service, they need to show this “key” to prove they’re allowed in. API keys help you control who can access your resources, and they’re often used to track usage, enforce limits, or prevent unauthorized access. They’re a straightforward way to manage API access and keep your data secure.

## **What We'll Build**

In this guide, we’ll create an api key generator that provides two core functionalities:

1. Generating new API keys with custom settings
2. Validating API keys while retrieving their metadata

Key features will include:

- Customizable key prefixes
- Expiration dates
- Rate limiting
- Metadata storage
- Owner identification

### Let's visualize our API key system

This diagram illustrates the interaction between the client, our Cloudflare Worker, and Upstash Redis for both creating and validating API keys. With this overview in mind, let's start building our system.

![https://1wkkahasy6a0u3rf.public.blob.vercel-storage.com/blog/mermaid-diagram-2024-11-08-153609-LjqIaZNv8n7UsRSgACGbmHPfEGYfnt.png](https://1wkkahasy6a0u3rf.public.blob.vercel-storage.com/blog/mermaid-diagram-2024-11-08-153609-LjqIaZNv8n7UsRSgACGbmHPfEGYfnt.png)

## **Prerequisites**

To follow along, you’ll need:

- A [**Cloudflare Workers**](https://workers.cloudflare.com/) account
- An [**Upstash**](https://upstash.com/) account
- Node.js installed on your local machine

## Project Structure

Our project will have the following structure:

```
folder-name/
├── src/
│   ├── config/
│   │   ├── generateApiKey.ts
│   │   └── schema-validation.ts
│   ├── lib/
│   │   └── ratelimit.ts
│   ├── routes/
│   │   ├── create.ts
│   │   └── verify.ts
│   ├── types/
│   │   └── api.ts
│   └── index.ts
├── package.json
└── wrangler.toml
```

## **Step 1: Project Setup**

Let's start by setting up our project and installing the necessary dependencies.

### **Create a new project directory**

Open your terminal and run the following commands:

```bash
mkdir keyflow
cd keyflow
npm init -y
```

### **Install dependencies**

We'll need a few packages for our project:

```bash
npm install hono @upstash/redis @upstash/ratelimit @hono/zod-validator zod wrangler
```

`@upstash/redis`: Upstash Redis client for serverless environments
`@upstash/ratelimit`: Rate limiting library for Upstash Redis
`@hono/zod-validator`: Request validation middleware for Hono
`zod`: TypeScript-first schema validation library
`wrangler`: Cloudflare's CLI for Workers development and deployment

### **Set up Upstash Redis**

1. Log in to your Upstash account and create a new Redis database

![https://1wkkahasy6a0u3rf.public.blob.vercel-storage.com/blog/e639568c-7434-4986-aa6d-738d705e8139-UQ4NIDP70Hsu7hFdZt0n51fXmJQ91s.png](https://1wkkahasy6a0u3rf.public.blob.vercel-storage.com/blog/e639568c-7434-4986-aa6d-738d705e8139-UQ4NIDP70Hsu7hFdZt0n51fXmJQ91s.png)

1. Once created, navigate to the "REST API" section

![https://1wkkahasy6a0u3rf.public.blob.vercel-storage.com/706941d5-30bd-4fc8-8f5a-8c5c209638bd-7EfBnVEoMAL73Y7NizXz1IEbUXLgGM.png](https://1wkkahasy6a0u3rf.public.blob.vercel-storage.com/706941d5-30bd-4fc8-8f5a-8c5c209638bd-7EfBnVEoMAL73Y7NizXz1IEbUXLgGM.png)

1. Copy the `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` in the `.env` section

### **Configure Cloudflare Workers**

Create a `wrangler.toml` file in your project root with the following content:

```toml
name = "keyflow"
main = "src/index.ts"
compatibility_date = "2023-05-18"

[vars]
UPSTASH_REDIS_REST_URL = "your-redis-url"
UPSTASH_REDIS_REST_TOKEN = "your-redis-token"
```

Replace `"your-redis-url"` and `"your-redis-token"` with the values you copied from Upstash.

## **Step 2: Defining API Types**

Let's start by defining our TypeScript interfaces for our API requests and responses. These types will help us maintain type safety throughout our application. Create a new file `src/types/api.ts`:

```tsx
export type CreateKeyRequest = {
  apiId: string;
  prefix?: string;
  byteLength?: number;
  ownerId?: string;
  name: string;
  meta?: Record<string, unknown>;
  expires?: number;
  ratelimit?: {
    type: "fast" | "consistent";
    limit: number;
    refillRate: number;
    refillInterval: number;
  };
};

export type CreateKeyResponse = {
  key: string;
  keyId: string;
};

export type VerifyKeyRequest = {
  key: string;
};

export type VerifyKeyResponse = {
  valid: boolean;
  ownerId?: string;
  meta?: Record<string, unknown>;
  expires?: number;
  ratelimit?: {
    limit: number;
    remaining: number;
    reset: number;
  };
};

export type Env = {
  UPSTASH_REDIS_REST_URL: string;
  UPSTASH_REDIS_REST_TOKEN: string;
};

```

## **Step 3: Implementing API Key Generation**

Now, let's create a utility function to generate our API keys. Create a new file `src/config/generateApiKey.ts`:

```tsx
export function generateApiKey(
  prefix: string | undefined,
  byteLength: number,
): string {
  const randomBytes = crypto.getRandomValues(new Uint8Array(byteLength));
  const key = btoa(String.fromCharCode(...new Uint8Array(randomBytes)))
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=/g, "");
  return prefix ? `${prefix}_${key}` : key;
}

```

This function generates a random API key using cryptographically secure random bytes, encodes it in base64, and makes it URL-safe.

## Step 4: Implement Rate Limiting

Create a file `src/lib/ratelimit.ts`:

```tsx
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis/cloudflare";
import type { Context, Next } from "hono";
import { env } from "hono/adapter";
import type { Env } from "../types/api";

// Middleware for rate limiting
export async function rateLimitMiddleware(c: Context, next: Next) {
	const { UPSTASH_REDIS_REST_TOKEN, UPSTASH_REDIS_REST_URL } = env<Env>(c);

	const redis = new Redis({
		url: UPSTASH_REDIS_REST_URL,
		token: UPSTASH_REDIS_REST_TOKEN,
	});

	const ratelimit = new Ratelimit({
		redis: redis,
		limiter: Ratelimit.slidingWindow(5, "30 s"),
	});

	const ip = c.req.header("CF-Connecting-IP") || "127.0.0.1";
	const { success, limit, remaining, reset } = await ratelimit.limit(ip);

	if (!success) {
		return c.json({ error: "Rate limit exceeded" }, 429);
	}

	c.header("X-RateLimit-Limit", limit.toString());
	c.header("X-RateLimit-Remaining", remaining.toString());
	c.header("X-RateLimit-Reset", reset.toString());

	await next();
}
```

## **Step 5: Creating the API Routes**

Let's set up our main application file and create our API routes. This file sets up our Hono application with two main routes: `/keys/create` for generating new API keys and `/keys/verify` for validating existing keys with three separate files.   

### 1. Implement Create API Key Route

Create a file `src/routes/create.ts`:

```tsx
import { zValidator } from "@hono/zod-validator"
import { Redis } from "@upstash/redis/cloudflare"
import { Hono } from "hono"
import { generateApiKey } from "../config/generateApiKey"
import { createApiKeySchema } from "../config/schema-validation"
import type { CreateKeyRequest, CreateKeyResponse, Env } from "../types/api"

const create = new Hono<{
  Bindings: Env
}>()

create.post(
  "/create",
  zValidator("json", createApiKeySchema, (result, c) => {
    if (!result.success) {
      return c.text("Invalid!", 400)
    }
  }),
  async (c) => {
    // Initialize Redis client
    const { UPSTASH_REDIS_REST_TOKEN, UPSTASH_REDIS_REST_URL } = c.env
    const redis = new Redis({
      url: UPSTASH_REDIS_REST_URL,
      token: UPSTASH_REDIS_REST_TOKEN,
    })

    const body = await c.req.json<CreateKeyRequest>()

    // Generate unique identifier and API key
    const keyId = crypto.randomUUID()
    const key = generateApiKey(body.prefix, body.byteLength || 16)

    const keyData = {
      ...body,
      key,
      keyId,
      createdAt: Date.now(),
    }

    const encodedKey = encodeURIComponent(key)

    try {
      // Store key data and lookup reference in Redis
      await redis.set(`key:${keyId}`, JSON.stringify(keyData))
      await redis.set(`lookup:${encodedKey}`, keyId)

      return c.json<CreateKeyResponse>({ key, keyId })
    } catch (error) {
      console.error("Error in /keys/create:", error)
      return c.json({ error: "Internal Server Error" }, 500)
    }
  }
)

export default create
```

### 2. Implement Verify API Key Route

Create a file `src/routes/verify.ts`:

```tsx
import { zValidator } from "@hono/zod-validator";
import { Redis } from "@upstash/redis/cloudflare";
import { Hono } from "hono";
import { verifyApiKeySchema } from "../config/schema-validation";
import type {
  CreateKeyRequest,
  Env,
  VerifyKeyRequest,
  VerifyKeyResponse,
} from "../types/api";

// Initialize Hono app with environment bindings
const verify = new Hono<{ Bindings: Env }>();

// Define POST route for verifying an API key
verify.post(
  "/verify",
  // Validate request body against schema
  zValidator("json", verifyApiKeySchema, (result, c) => {
    if (!result.success) {
      return c.text("Invalid!", 400); // Return 400 if validation fails
    }
  }),
  async (c) => {
    // Set up Redis with environment variables
    const { UPSTASH_REDIS_REST_TOKEN, UPSTASH_REDIS_REST_URL } = c.env;
    const redis = new Redis({
      url: UPSTASH_REDIS_REST_URL,
      token: UPSTASH_REDIS_REST_TOKEN,
    });

    const body = await c.req.json<VerifyKeyRequest>();
    if (!body.key) {
      return c.json({ error: "key is required" }, 400); // Require key in the request body
    }

    const encodedKey = encodeURIComponent(body.key);
    const keyId = await redis.get<string>(`lookup:${encodedKey}`); // Retrieve key ID using encoded key

    if (!keyId) {
      return c.json<VerifyKeyResponse>({ valid: false }); // Key not found
    }

    const keyDataString = await redis.get<string>(`key:${keyId}`); // Retrieve key data by key ID

    if (!keyDataString || typeof keyDataString !== "string") {
      return c.json<VerifyKeyResponse>({ valid: false }); // Key data missing or invalid
    }

    let keyData: CreateKeyRequest & {
      key: string;
      keyId: string;
      createdAt: number;
    };

    try {
      keyData = JSON.parse(keyDataString); // Parse key data
    } catch (parseError) {
      // Handle parse error by deleting invalid data
      console.error("Key data parse error:", parseError);
      await Promise.all([
        redis.del(`key:${keyId}`),
        redis.del(`lookup:${encodedKey}`),
      ]);
      return c.json(
        {
          error: "Invalid key data in storage",
          details: parseError instanceof Error ? parseError.message : "Unknown parse error",
          valid: false,
        },
        500,
      );
    }

    // Check if key has expired
    if (keyData.expires && keyData.expires < Date.now()) {
      await Promise.all([
        redis.del(`key:${keyId}`),
        redis.del(`lookup:${encodedKey}`),
      ]);
      return c.json<VerifyKeyResponse>({ valid: false });
    }

    // Formulate response with validation status and metadata
    const response: VerifyKeyResponse = {
      valid: true,
      ownerId: keyData.ownerId,
      meta: keyData.meta,
      expires: keyData.expires,
    };

    if (keyData.ratelimit) {
      response.ratelimit = {
        limit: keyData.ratelimit.limit,
        remaining: keyData.ratelimit.limit,
        reset: Date.now() + keyData.ratelimit.refillInterval,
      };
    }

    return c.json(response); // Return verification response
  },
);

export default verify;

```

### 3. Implement the Main file index.ts

implement the main file `src/index.ts` to import `create.ts`, `verify.ts`, and `rateLimitMiddleWare`

```tsx
import { Hono } from "hono";
import { rateLimitMiddleware } from "./lib/ratelimit";
import create from "./routes/create";
import verify from "./routes/verify";
import type { Env } from "./types/api";

const app = new Hono<{
  Bindings: Env;
}>().basePath("/keys");

app.use("*", rateLimitMiddleware);

// add the create file route and verify file route
app.route("/", create);
app.route("/", verify);

export default app;

```

## Step 6: Deployment

Deploy your Keyflow application to Cloudflare Workers:

1. Install Wrangler:
    
    ```bash
    npm install -g wrangler
    ```
    
2. Authenticate with Cloudflare:
    
    ```bash
    wrangler login
    ```
    
3. Deploy your worker:
    
    ```bash
    wrangler deploy
    ```
    

## **Step 7: Deployment**

Now that we have our application ready, let's deploy it to Cloudflare Workers:

1. Make sure you have the Wrangler CLI installed:
    
    ```
    npm install -g wrangler
    ```
    
2. Authenticate with your Cloudflare account:
    
    ```
    wrangler login
    ```
    
3. Deploy your worker:
    
    ```
    wrangler deploy
    ```
    

## **Step 8: Testing Your API**

Let's test our newly deployed API:

### **Creating a New API Key**

```
curl -X POST https://keyflow.<your-subdomain>.workers.dev/keys/create \
  -H "Content-Type: application/json" \
  -d '{
    "apiId": "my-api",
    "prefix": "prod",
    "name": "Production API Key",
    "expires": 1735689600000,
    "meta": {
      "environment": "production",
      "team": "backend"
    }
  }'
```

### **Verifying an API Key**

```
curl -X POST https://keyflow.<your-subdomain>.workers.dev/keys/verify \
  -H "Content-Type: application/json" \
  -d '{
    "key": "prod_AbC123XyZ..."
  }'
```

Replace `<your-subdomain>` with your Cloudflare Workers subdomain and `prod_AbC123XyZ...` with an actual key generated from the create endpoint.

## Conclusion

Creating an API key generator is a key step in securing your application's APIs and managing who can access your services. By building a system that can generate, validate, and manage API keys, you add a strong layer of access control that helps keep your data safe and organized.

Here’s a quick rundown of what goes into a solid API key generator:

1. **Secure Key Generation**: Unique, secure keys that include options like custom prefixes or set lengths make each key distinct and harder to guess.
2. **Validation and Expiration**: Adding checks and expiration dates ensures each key is valid only for a set time, making it easy to control and limit access as needed.
3. **Metadata and Rate Limits**: Storing extra information with each key and setting rate limits lets you monitor key usage, track activity, and avoid any abuse of your API.

Using tools like Upstash Redis and Cloudflare Workers makes it easy to build a serverless and globally distributed key management system that scales well and works efficiently.

With this foundation, you’re set up to keep access to your APIs secure and manageable, giving you peace of mind that your resources are protected and easy to monitor.