diff --git a/llms-full.txt b/llms-full.txt
index 4ce6a125..629e1152 100644
--- a/llms-full.txt
+++ b/llms-full.txt
@@ -28379,5678 +28379,39 @@ messages that are delivered to a consumer but not yet acknowledged via
Acknowledge the consumed messages via [XACK](https://redis.io/commands/xack/)
from the list of the associated group and consumer.
-# Agent Memory with Redis Search
-Source: https://upstash.com/docs/redis/tutorials/agent_memory
-
-Large language models are stateless: once a request returns, the model forgets
-everything. To build an agent that remembers who a user is and what happened in
-past conversations, you need to store that context yourself and feed it back into
-the prompt.
-
-In this tutorial we build a small but complete **agent memory** layer on Upstash
-Redis, with two tiers:
-
-* **Working memory**: the running conversation for the current session, stored
- in a single Redis key with a TTL so it expires on its own.
-* **Long-term memory**: durable facts about the user (preferences, events,
- decisions) stored as JSON documents and recalled with [Redis Search](/docs/redis/search/introduction)
- full-text queries.
-
-On every turn the agent **recalls** relevant long-term memories, answers using
-those plus the recent conversation, then **remembers** any new facts worth keeping.
-
-
-This tutorial uses OpenAI for the chat and fact-extraction calls, but the memory
-layer itself is model-agnostic, so swap in any LLM you like.
-
-
-## Prerequisites
-
-* An [Upstash Redis](https://console.upstash.com) database (the REST URL and token).
-* An OpenAI API key.
-
-Install the dependencies:
-
-
-
-
-```bash
-npm install @upstash/redis openai
-```
-
-
-
-```bash
-pip install upstash-redis openai
-```
-
-
-
-
-Set your environment variables:
-
-```bash
-UPSTASH_REDIS_REST_URL="https://..."
-UPSTASH_REDIS_REST_TOKEN="..."
-OPENAI_API_KEY="sk-..."
-```
-
-## Step 1: Create the long-term memory index
-
-Long-term memories are JSON documents stored under the `memory:` prefix. We index
-the `text` field for full-text recall, and keep `userId` and `kind` as exact-match
-keywords so we can scope a search to a single user. `createdAt` is a sortable
-number we can use to favor recent memories.
-
-Create the index **once** (e.g. in a setup script), not on every request.
-
-
-
-
-```ts
-// setup.ts
-import { Redis, s } from "@upstash/redis";
-
-const redis = Redis.fromEnv();
-
-try {
- await redis.search.createIndex({
- name: "memories",
- dataType: "json",
- prefix: "memory:",
- schema: s.object({
- text: s.string(), // full-text searchable fact
- userId: s.keyword(), // exact-match owner
- kind: s.keyword(), // "preference" | "event" | "fact" ...
- createdAt: s.number(), // epoch ms, sortable
- }),
- });
-} catch {
- // Index already exists, safe to ignore when re-running setup.
-}
-```
-
-
-
-```python
-# setup.py
-from upstash_redis import Redis
-
-redis = Redis.from_env()
-
-redis.search.create_index(
- name="memories",
- data_type="json",
- prefixes="memory:",
- exists_ok=True, # idempotent: don't error if the index already exists
- schema={
- "text": "TEXT", # full-text searchable fact
- "userId": "KEYWORD", # exact-match owner
- "kind": "KEYWORD", # "preference" | "event" | "fact" ...
- "createdAt": "F64", # epoch ms, sortable
- },
-)
-```
-
-
-
-
-## Step 2: Working (short-term) memory
-
-Working memory is just the recent message history for a session. We store it as a
-single JSON value with a one-hour TTL and cap it to the last 20 messages so the
-prompt stays small. When the session goes quiet, Redis expires the key for us.
-
-
-
-
-```ts
-// memory.ts
-import { Redis } from "@upstash/redis";
-
-const redis = Redis.fromEnv();
-
-export type Message = { role: "user" | "assistant"; content: string };
-
-const SESSION_TTL = 60 * 60; // 1 hour
-const MAX_MESSAGES = 20;
-
-export async function loadHistory(sessionId: string): Promise {
- return (await redis.get(`chat:${sessionId}`)) ?? [];
-}
-
-export async function saveHistory(sessionId: string, messages: Message[]) {
- const trimmed = messages.slice(-MAX_MESSAGES);
- await redis.set(`chat:${sessionId}`, trimmed, { ex: SESSION_TTL });
-}
-```
-
-
-
-```python
-# memory.py
-import json
-from upstash_redis import Redis
-
-redis = Redis.from_env()
-
-SESSION_TTL = 60 * 60 # 1 hour
-MAX_MESSAGES = 20
-
-def load_history(session_id: str) -> list[dict]:
- raw = redis.get(f"chat:{session_id}")
- return json.loads(raw) if raw else []
-
-def save_history(session_id: str, messages: list[dict]) -> None:
- trimmed = messages[-MAX_MESSAGES:]
- redis.set(f"chat:{session_id}", json.dumps(trimmed), ex=SESSION_TTL)
-```
-
-
-
-
-## Step 3: Recall relevant memories
-
-To answer well, the agent needs the long-term facts that relate to the current
-message. We run a full-text query against the `memories` index, scoped to the
-user with the `userId` keyword. Redis Search ranks matches by relevance, so we
-take the top few.
-
-
-
-
-```ts
-const memories = redis.search.index({ name: "memories" });
-
-export async function recall(
- userId: string,
- query: string,
- limit = 5,
-): Promise {
- const results = await memories.query({
- filter: { text: query, userId },
- limit,
- });
-
- // No memories yet → the index may not exist → results is null
- return (results ?? []).map((r) => r.data.text as string);
-}
-```
-
-
-
-```python
-memories = redis.search.index(name="memories")
-
-def recall(user_id: str, query: str, limit: int = 5) -> list[str]:
- results = memories.query(filter={"text": query, "userId": user_id}, limit=limit)
-
- # No memories yet → the index may not exist → results is None
- return [r.data["text"] for r in (results or [])]
-```
-
-
-
-
-
-To bias recall toward recent memories, you can boost the score with the
-`createdAt` field using a [score function](/docs/redis/search/querying#4-score-function),
-or sort with `orderBy` / `order_by`. We keep plain relevance ranking here for
-simplicity.
-
-
-## Step 4: Remember new facts
-
-After each exchange we ask the model to pull out durable facts, the things worth
-remembering across sessions, not small talk. Each fact becomes a JSON document
-under the `memory:` prefix, so the index picks it up automatically.
-
-Because full-text search gives us a cheap similarity check, we **deduplicate**
-before writing: if a very similar memory already exists for this user, we skip it.
-
-
-
-
-```ts
-import OpenAI from "openai";
-
-const openai = new OpenAI();
-
-// Heuristic: full-text scores are unbounded, so this threshold is tuned by feel.
-const DEDUPE_SCORE = 8;
-
-async function alreadyKnown(userId: string, text: string): Promise {
- const hits = await memories.query({ filter: { text, userId }, limit: 1 });
- return !!hits?.length && hits[0].score > DEDUPE_SCORE;
-}
-
-export async function remember(userId: string, conversation: Message[]) {
- const completion = await openai.chat.completions.create({
- model: "gpt-4o-mini",
- response_format: { type: "json_object" },
- messages: [
- {
- role: "system",
- content:
- "Extract durable facts about the user worth remembering across " +
- "sessions (preferences, decisions, personal details). Ignore " +
- 'small talk. Respond as JSON: {"facts": ["..."]}. Empty if none.',
- },
- { role: "user", content: JSON.stringify(conversation) },
- ],
- });
-
- const { facts } = JSON.parse(completion.choices[0].message.content ?? '{"facts":[]}');
-
- for (const text of facts as string[]) {
- if (await alreadyKnown(userId, text)) continue;
- const id = crypto.randomUUID();
- await redis.json.set(`memory:${userId}:${id}`, "$", {
- text,
- userId,
- kind: "fact",
- createdAt: Date.now(),
- });
- }
-}
-```
-
-
-
-```python
-import json
-import uuid
-import time
-from openai import OpenAI
-
-openai = OpenAI()
-
-# Heuristic: full-text scores are unbounded, so this threshold is tuned by feel.
-DEDUPE_SCORE = 8
-
-def already_known(user_id: str, text: str) -> bool:
- hits = memories.query(filter={"text": text, "userId": user_id}, limit=1)
- return bool(hits) and hits[0].score > DEDUPE_SCORE
-
-def remember(user_id: str, conversation: list[dict]) -> None:
- completion = openai.chat.completions.create(
- model="gpt-4o-mini",
- response_format={"type": "json_object"},
- messages=[
- {
- "role": "system",
- "content": (
- "Extract durable facts about the user worth remembering "
- "across sessions (preferences, decisions, personal details). "
- "Ignore small talk. Respond as JSON: {\"facts\": [\"...\"]}. "
- "Empty if none."
- ),
- },
- {"role": "user", "content": json.dumps(conversation)},
- ],
- )
-
- facts = json.loads(completion.choices[0].message.content or '{"facts":[]}')["facts"]
-
- for text in facts:
- if already_known(user_id, text):
- continue
- memory_id = uuid.uuid4().hex
- redis.json.set(
- f"memory:{user_id}:{memory_id}",
- "$",
- {
- "text": text,
- "userId": user_id,
- "kind": "fact",
- "createdAt": int(time.time() * 1000),
- },
- )
-```
-
-
-
-
-## Step 5: The chat loop
-
-Now we wire it together. Each turn: **recall** relevant memories, build a prompt
-from those plus the working memory, call the model, persist the updated history,
-and **remember** new facts.
-
-
-
-
-```ts
-export async function chat(userId: string, sessionId: string, input: string) {
- const [history, recalled] = await Promise.all([
- loadHistory(sessionId),
- recall(userId, input),
- ]);
-
- const system =
- "You are a helpful assistant. Use the following remembered facts about " +
- `the user when relevant:\n${recalled.map((m) => `- ${m}`).join("\n") || "(none yet)"}`;
-
- const completion = await openai.chat.completions.create({
- model: "gpt-4o-mini",
- messages: [
- { role: "system", content: system },
- ...history,
- { role: "user", content: input },
- ],
- });
-
- const reply = completion.choices[0].message.content ?? "";
-
- const updated: Message[] = [
- ...history,
- { role: "user", content: input },
- { role: "assistant", content: reply },
- ];
-
- await saveHistory(sessionId, updated);
- await remember(userId, updated); // fire-and-forget in production
-
- return reply;
-}
-```
-
-
-
-```python
-def chat(user_id: str, session_id: str, user_input: str) -> str:
- history = load_history(session_id)
- recalled = recall(user_id, user_input)
-
- facts = "\n".join(f"- {m}" for m in recalled) or "(none yet)"
- system = (
- "You are a helpful assistant. Use the following remembered facts "
- f"about the user when relevant:\n{facts}"
- )
-
- completion = openai.chat.completions.create(
- model="gpt-4o-mini",
- messages=[
- {"role": "system", "content": system},
- *history,
- {"role": "user", "content": user_input},
- ],
- )
-
- reply = completion.choices[0].message.content or ""
-
- updated = history + [
- {"role": "user", "content": user_input},
- {"role": "assistant", "content": reply},
- ]
-
- save_history(session_id, updated)
- remember(user_id, updated) # run in the background in production
-
- return reply
-```
-
-
-
-
-## Try it
-
-Run two sessions for the same user. Even after the first session's working memory
-expires, the facts learned there are recalled in the second:
-
-
-
-
-```ts
-await chat("user-1", "session-a", "I'm vegetarian and I love spicy food.");
-
-// Redis Search indexes writes asynchronously, wait so the demo is deterministic.
-await memories.waitIndexing();
-
-// ...a brand new session...
-const reply = await chat("user-1", "session-b", "Suggest a dinner for me.");
-console.log(reply); // recalls "vegetarian" + "spicy" from long-term memory
-```
-
-
-
-```python
-chat("user-1", "session-a", "I'm vegetarian and I love spicy food.")
-
-# Redis Search indexes writes asynchronously, wait so the demo is deterministic.
-memories.wait_indexing()
-
-# ...a brand new session...
-reply = chat("user-1", "session-b", "Suggest a dinner for me.")
-print(reply) # recalls "vegetarian" + "spicy" from long-term memory
-```
-
-
-
-
-
-Redis Search indexes writes asynchronously: a `JSON.SET` returns before the
-document is searchable. For a deterministic demo or test, call `waitIndexing()` /
-`wait_indexing()` to block until pending updates are applied. In a real app the
-next user turn normally arrives later than the indexing window, so an explicit
-wait isn't needed.
-
-
-## How it fits together
-
-* **Working memory** lives under `chat:{sessionId}` with a TTL: fast to read,
- self-expiring, scoped to one conversation.
-* **Long-term memory** lives under `memory:{userId}:{id}` and is searchable across
- sessions through the `memories` index.
-* **Recall** uses full-text relevance to surface the facts that matter for the
- current message; **remember** extracts and deduplicates new ones.
-
-## Next steps
-
-* Add a `kind` such as `"preference"` vs `"event"` and filter recall by it.
-* Boost recent memories with a [score function](/docs/redis/search/querying#4-score-function).
-* Summarize older working-memory messages instead of dropping them.
-* Stream the reply to a chat UI and animate it smoothly. See
- [Smooth Text Streaming in AI SDK v5](https://upstash.com/blog/smooth-streaming).
-* Learn more about what Redis Search can do in the [Search docs](/docs/redis/search/introduction).
-
-# Building Analytics with Redis
-Source: https://upstash.com/docs/redis/tutorials/analytics_with_redis
-
-Most teams reach for a dedicated analytics product the moment they want to count
-something. But if you already run Redis (for caching, sessions, or rate limiting),
-you are sitting on one of the best analytics engines available.
-
-Why Redis?
-
-* **It's already there.** No new vendor, no new pipeline, no nightly ETL job. You
- write events from the same code that serves your requests.
-* **It's fast.** Counters, sets, and bitmaps are O(1) or close to it. You can
- increment a metric on every request without thinking about it, and read the
- result back in single-digit milliseconds.
-* **It's serverless-friendly.** With [Upstash Redis](https://upstash.com/) you talk
- to it over HTTP, so it works from edge functions, Lambdas, and the browser-facing
- routes of a Next.js app, exactly where analytics events originate.
-
-There are two broad philosophies for doing analytics on Redis. This tutorial walks
-through both, when to use each, and the tooling we've built to make the second one
-painless.
-
-## Two philosophies
-
-There are two ways to think about this:
-
-1. **Plan ahead.** Decide what you want to measure up front and record it in a
- compact structure (like a bitmap) built to answer that exact question.
-2. **Record everything.** Capture each event with its metadata, index it with Redis
- Search, and figure out the questions later.
-
-| | Plan ahead | Record everything |
-| --- | --- | --- |
-| **You decide...** | the questions up front | the questions later |
-| **Storage** | counters, bitmaps, sets | event documents (JSON) |
-| **Cost** | tiny, fixed | grows with event volume |
-| **Querying** | read the counter | filter / aggregate with Redis Search |
-| **Good for** | DAU, funnels, feature usage | ad-hoc product analytics |
-
-
-You don't have to pick one. Many apps use bitmaps for the handful of metrics they
-watch daily, and event recording for everything they might want to explore later.
-
-
-## Philosophy 1: Plan ahead
-
-If you know in advance what you want to measure, you can record it in a structure
-that answers that exact question for almost no storage. The classic example is the
-**bitmap**: one bit per user, per day.
-
-### Daily active users with a bitmap
-
-A bitmap is a string where you can flip individual bits by offset. Use the user ID
-as the offset and you get a per-day "did this user show up?" record that costs one
-bit per user, roughly **1.2 MB for 10 million users**.
-
-```ts
-import { Redis } from "@upstash/redis";
-
-const redis = Redis.fromEnv();
-
-// Mark user 1234 as active today
-function markActive(userId: number, day = today()) {
- return redis.setbit(`active:${day}`, userId, 1);
-}
-
-// How many unique users were active today?
-function dailyActiveUsers(day = today()) {
- // bitcount accepts a key alone at runtime, but the TS SDK wants an
- // explicit byte range; 0..-1 covers the whole bitmap.
- return redis.bitcount(`active:${day}`, 0, -1);
-}
-
-function today() {
- return new Date().toISOString().slice(0, 10); // "2024-06-15"
-}
-```
-
-`setbit` is O(1) and `bitcount` counts the set bits in one pass. Done.
-
-### Combining days: weekly actives and retention
-
-Because each day is its own bitmap, you can answer questions about *ranges* of days
-with bitwise operations, without storing anything extra.
-
-```ts
-// Weekly active users: OR the last 7 daily bitmaps together
-async function weeklyActiveUsers(days: string[]) {
- if (days.length === 0) return 0;
- const [first, ...rest] = days.map((d) => `active:${d}`);
- await redis.bitop("or", "active:week", first, ...rest);
- return redis.bitcount("active:week", 0, -1);
-}
-
-// Retention: users active on BOTH day A and day B
-async function retained(dayA: string, dayB: string) {
- await redis.bitop("and", "tmp:retained", `active:${dayA}`, `active:${dayB}`);
- return redis.bitcount("tmp:retained", 0, -1);
-}
-```
-
-`OR` gives you "active on any of these days", `AND` gives you "active on all of
-them", the building blocks of retention and funnel analysis. The same idea powers
-feature-adoption flags: keep a bitmap per feature and `AND` it against your active
-users to see adoption.
-
-### When this breaks down
-
-The catch is right there in the name: you have to **plan ahead**. A bitmap answers
-the one question you designed it for. The moment someone asks "okay, but how many of
-those users were on mobile, in Germany, on the new checkout flow?" you're stuck:
-that dimension was never recorded. You'd need to have created a separate bitmap for
-every combination in advance, which doesn't scale.
-
-That's where the second philosophy comes in.
-
-## Philosophy 2: Record events, query later
-
-Instead of deciding the questions up front, record each event as a document with
-whatever metadata you have on hand, index it with
-[Redis Search](/docs/redis/search/introduction), and ask your questions *afterwards*.
-
-The flow is:
-
-1. Write each event as a JSON document under a known key prefix.
-2. Define a search index over that prefix once.
-3. Query and aggregate however you like (filters, ranges, group-bys) without
- having planned for any specific question.
-
-### Define the index
-
-You define the index a single time. Redis Search then automatically picks up any
-key matching the prefix; there is no separate "insert into index" step.
-
-```ts
-import { Redis, s } from "@upstash/redis";
-
-const redis = Redis.fromEnv();
-
-const events = await redis.search.createIndex({
- name: "events-idx",
- prefix: "event:",
- dataType: "json",
- existsOk: true, // don't throw if the index already exists
- schema: s.object({
- name: s.keyword(), // "pageview", "signup", "purchase"
- path: s.keyword(), // "/pricing"
- country: s.keyword(), // "DE"
- device: s.keyword(), // "mobile"
- amount: s.number("F64"), // for purchase events
- ts: s.date(),
- }),
-});
-```
-
-### Record events
-
-Events are just JSON written with a regular Redis command. The index picks them up
-on its own.
-
-```ts
-async function track(event: {
- name: string;
- path?: string;
- country?: string;
- device?: string;
- amount?: number;
-}) {
- const id = crypto.randomUUID();
- await redis.json.set(`event:${id}`, "$", {
- ...event,
- ts: new Date().toISOString(),
- });
-}
-
-await track({ name: "pageview", path: "/pricing", country: "DE", device: "mobile" });
-await track({ name: "purchase", amount: 49.0, country: "DE", device: "mobile" });
-```
-
-
-Indexing is asynchronous. In a long-running app you don't need to think about it,
-but in a tight loop, like a script or a test that writes events and immediately
-queries them, call `await events.waitIndexing()` first so the documents you just
-wrote are searchable:
-
-```ts
-await events.waitIndexing();
-```
-
-
-### Now ask anything
-
-Here's the payoff. None of these queries needed to be anticipated when you recorded
-the events.
-
-```ts
-// How many mobile pageviews from Germany?
-const { count } = await events.count({
- filter: {
- $and: [
- { name: { $eq: "pageview" } },
- { device: { $eq: "mobile" } },
- { country: { $eq: "DE" } },
- ],
- },
-});
-
-// Total revenue and average order value, broken down by country
-const revenue = await events.aggregate({
- filter: { name: { $eq: "purchase" } },
- aggregations: {
- by_country: {
- $terms: { field: "country", size: 20 },
- $aggs: {
- total: { $avg: { field: "amount" } },
- orders: { $count: { field: "amount" } },
- },
- },
- },
-});
-
-// Top pages this week
-const topPages = await events.aggregate({
- filter: { ts: { $gte: "2024-06-10T00:00:00Z" } }, // s.date() expects an RFC 3339 string
- aggregations: {
- pages: { $terms: { field: "path", size: 10 } },
- },
-});
-```
-
-Filters, numeric ranges, date ranges, group-bys (`$terms`), histograms, facets,
-percentiles: all available, all decided at query time. See the
-[querying](/docs/redis/search/querying) and [aggregating](/docs/redis/search/aggregations)
-docs for the full set.
-
-## The complexity, and a PoC that hides it
-
-The event-recording approach is more flexible, but it does come with moving parts
-that the simple examples above gloss over:
-
-* **Schema management**: keeping the index schema in sync as your events evolve.
-* **Capturing events from the frontend**: you need an endpoint, batching, and a
- client to send events without slowing down the page.
-* **Sessions and context**: tying events together and attaching shared metadata.
-* **Exploring the data**: a query API is not a dashboard.
-
-To explore how far this can be smoothed over, we built a proof-of-concept SDK,
-[**`@upstash/redis-analytics`**](https://github.com/upstash/redis-analytics), that packages these pieces:
-
-* A **ready-to-use React hook** that auto-creates a session and captures pageviews.
-* A single **backend endpoint** you drop into your app (e.g. a Next.js route).
-* **Middleware** hooks for resolving feature flags and attaching server-side context.
-* A **schema registry** that infers field types from your events and maintains the
- Redis Search index for you.
-* An **admin dashboard** for exploring captured analytics.
-
-A minimal end-to-end wiring looks like this:
-
-```ts
-// lib/analytics.ts (backend client)
-import { AnalyticsBackendClient } from "@upstash/redis-analytics";
-
-export const analytics = new AnalyticsBackendClient({
- redis: {
- url: process.env.UPSTASH_REDIS_REST_URL!,
- token: process.env.UPSTASH_REDIS_REST_TOKEN!,
- },
-});
-```
-
-```ts
-// app/api/analytics/route.ts (the single endpoint)
-import { analytics } from "@/lib/analytics";
-
-const handler = analytics.getHandler();
-export const POST = handler;
-export const GET = handler;
-```
-
-```tsx
-// frontend (the hook)
-"use client";
-import { createAnalyticsHook } from "@upstash/redis-analytics/react";
-
-const useAnalytics = createAnalyticsHook({ endpoint: "/api/analytics" });
-
-export function BuyButton() {
- const { captureEvent, sessionId } = useAnalytics();
- return (
-
- );
-}
-```
-
-## Which should you use?
-
-* Reach for **bitmaps and counters** when you have a short, stable list of metrics
- you watch every day. They're nearly free and answer instantly.
-* Reach for **event recording with Redis Search** when you want to explore your data
- and can't predict every question in advance.
-
-Both run on the Redis you already have. Start with whichever matches the questions
-you have today; you can always add the other later.
-
-# Deploy a Serverless API with AWS CDK and AWS Lambda
-Source: https://upstash.com/docs/redis/tutorials/api_with_cdk
-
-
- You can find the project source code on GitHub.
-
-
-In this tutorial, we will implement a Serverless API using AWS Lambda and we
-will deploy it using AWS CDK. We will use Typescript as the CDK language. It
-will be a view counter where we keep the state in Redis.
-
-### What is AWS CDK?
-
-AWS CDK is an interesting project which allows you to provision and deploy AWS
-infrastructure with code. Currently TypeScript, JavaScript, Python, Java,
-C#/.Net and Go are supported. You can compare AWS CDK with following technologies:
-
-* AWS CloudFormation
-* AWS SAM
-* Serverless Framework
-
-The above projects allows you to set up the infrastructure with configuration
-files (yaml, json) while with AWS CDK, you set up the resources with code. For
-more information about CDK see the related
-[AWS Docs](https://docs.aws.amazon.com/cdk/latest/guide/home.html).
-
-### Prerequisites
-
-* Complete all steps in [Getting started with the AWS CDK](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html)
-
-### Project Setup
-
-Create and navigate to a directory named `counter-cdk`. The CDK CLI uses this directory name to name things in your CDK code, so if you decide to use a different name, don't forget to make the appropriate changes when applying this tutorial.
-
-```shell
-mkdir counter-cdk && cd counter-cdk
-```
-
-Initialize a new CDK project.
-
-```shell
-cdk init app --language typescript
-```
-
-Install `@upstash/redis`.
-
-```shell
-npm install @upstash/redis
-```
-
-### Counter Function Setup
-
-Create `/api/counter.ts`.
-
-```ts /api/counter.ts
-import { Redis } from '@upstash/redis';
-
-const redis = Redis.fromEnv();
-
-export const handler = async function() {
- const count = await redis.incr("counter");
- return {
- statusCode: 200,
- body: JSON.stringify('Counter: ' + count),
- };
-};
-```
-
-### Counter Stack Setup
-
-Update `/lib/counter-cdk-stack.ts`.
-
-```ts /lib/counter-cdk-stack.ts
-import * as cdk from 'aws-cdk-lib';
-import { Construct } from 'constructs';
-import * as lambda from 'aws-cdk-lib/aws-lambda';
-import * as nodejs from 'aws-cdk-lib/aws-lambda-nodejs';
-
-export class CounterCdkStack extends cdk.Stack {
- constructor(scope: Construct, id: string, props?: cdk.StackProps) {
- super(scope, id, props);
-
- const counterFunction = new nodejs.NodejsFunction(this, 'CounterFunction', {
- entry: 'api/counter.ts',
- handler: 'handler',
- runtime: lambda.Runtime.NODEJS_20_X,
- environment: {
- UPSTASH_REDIS_REST_URL: process.env.UPSTASH_REDIS_REST_URL || '',
- UPSTASH_REDIS_REST_TOKEN: process.env.UPSTASH_REDIS_REST_TOKEN || '',
- },
- bundling: {
- format: nodejs.OutputFormat.ESM,
- target: "node20",
- nodeModules: ['@upstash/redis'],
- },
- });
-
- const counterFunctionUrl = counterFunction.addFunctionUrl({
- authType: lambda.FunctionUrlAuthType.NONE,
- });
-
- new cdk.CfnOutput(this, "counterFunctionUrlOutput", {
- value: counterFunctionUrl.url,
- })
- }
-}
-```
-
-### Database Setup
-
-Create a Redis database using [Upstash Console](https://console.upstash.com) or [Upstash CLI](https://github.com/upstash/cli) and export `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` to your environment.
-
-```shell
-export UPSTASH_REDIS_REST_URL=
-export UPSTASH_REDIS_REST_TOKEN=
-```
-
-### Deploy
-
-Run in the top folder:
-
-```shell
-cdk synth
-cdk bootstrap
-cdk deploy
-```
-
-Visit the output URL.
-
-# Autocomplete API with Serverless Redis
-Source: https://upstash.com/docs/redis/tutorials/auto_complete_with_serverless_redis
-
-This tutorial implements an autocomplete API powered by serverless Redis. See
-[the demo](https://auto-complete-example.vercel.app/) and
-[API endpoint](https://wfgz7cju24.execute-api.us-east-1.amazonaws.com/query?term=ca)
-and
-[the source code](https://github.com/upstash/examples/tree/main/examples/auto-complete-api).
-
-We will keep country names in a Redis Sorted set. In Redis sorted set, elements
-with the same score are sorted lexicographically. So in our case, all country
-names will have the same score, 0. We keep all prefixes of country and use ZRANK
-to find the terms to suggest. See
-[this blog post](https://oldblog.antirez.com/post/autocomplete-with-redis.html)
-for the details of the algorithm.
-
-### Step 1: Project Setup
-
-
- I will use Serverless framework for this tutorial. You can also use [AWS
- SAM](/docs/redis/tutorials/using_aws_sam)
-
-
-If you do not have it already install serverless framework via:
-`npm install -g serverless`
-
-In any folder run `serverless` as below:
-
-```text
->> 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? test-upstash
-
-Project successfully created in 'test-upstash' 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.
-```
-
-Inside the project folder create a node project with the command:
-
-```
-npm init
-```
-
-Then install the redis client with:
-
-```
-npm install ioredis
-```
-
-### Step 2: API Implementation
-
-Edit handler.js file as below. See
-[the blog post](https://oldblog.antirez.com/post/autocomplete-with-redis.html)
-for the details of the algorithm.
-
-```javascript
-var Redis = require("ioredis");
-if (typeof client === "undefined") {
- var client = new Redis(process.env.REDIS_URL);
-}
-const headers = {
- "Access-Control-Allow-Origin": "*",
- "Access-Control-Allow-Credentials": true,
-};
-
-module.exports.query = async (event, context, callback) => {
- if (!event.queryStringParameters || !event.queryStringParameters.term) {
- return {
- statusCode: 400,
- headers: headers,
- body: JSON.stringify({
- message: "Invalid parameters. Term needed as query param.",
- }),
- };
- }
- let term = event.queryStringParameters.term.toUpperCase();
- let res = [];
- let rank = await client.zrank("terms", term);
- if (rank != null) {
- let temp = await client.zrange("terms", rank, rank + 100);
- for (const el of temp) {
- if (!el.startsWith(term)) {
- break;
- }
- if (el.endsWith("*")) {
- res.push(el.substring(0, el.length - 1));
- }
- }
- }
- return {
- statusCode: 200,
- headers: headers,
- body: JSON.stringify({
- message: "Query:" + event.queryStringParameters.term,
- result: res,
- }),
- };
-};
-```
-
-### Step 3: Create database on Upstash
-
-If you do not have one, create a database following this
-[guide](../overall/getstarted). Copy the Redis URL by clicking `Redis Connect`
-button inside database page. Copy the URL for ioredis as we use ioredis in our
-application. Create .env file and paste your Redis URL:
-
-```text
-REDIS_URL=YOUR_REDIS_URL
-```
-
-
- This example uses ioredis, you can copy the connection string from the
- **Node** tab in the console.
-
-
-### Step 4: Initialize Database
-
-We will initialize the database with country names. Copy and run initdb.js
-script from
-[here](https://github.com/upstash/examples/tree/main/examples/auto-complete-api/initdb.js).
-
-We simply put the country names and all their prefixes to the sorted set.
-
-```javascript
-require('dotenv').config()
-var Redis = require("ioredis");
-
-var countries = [
- {"name": "Afghanistan", "code": "AF"},
- {"name": "Åland Islands", "code": "AX"},
- {"name": "Albania", "code": "AL"},
- {"name": "Algeria", "code": "DZ"},
- ...
-]
-var client = new Redis(process.env.REDIS_URL);
-
-for (const country of countries) {
- let term = country.name.toUpperCase();
- let terms = [];
-
- for (let i = 1; i < term.length; i++) {
- terms.push(0);
- terms.push(term.substring(0, i));
- }
- terms.push(0);
- terms.push(term + "*");
- (async () => {
- await client.zadd("terms", ...terms)
- })();
-}
-```
-
-### Step 5: Deploy Your Function
-
-Edit `serverless.yml` as below and replace your Redis URL:
-
-```yaml
-service: auto-complete-api
-# add this if you set REDIS_URL in .env
-useDotenv: true
-frameworkVersion: "2"
-
-provider:
- name: aws
- runtime: nodejs14.x
- lambdaHashingVersion: 20201221
- environment:
- REDIS_URL: REPLACE_YOUR_REDIS_URL
-
-functions:
- query:
- handler: handler.query
- events:
- - httpApi:
- path: /query
- method: get
- cors: true
-```
-
-In the project folder run:
-
-```
-serverless deploy
-```
-
-Now you can run your function with:
-
-```shell
-serverless invoke -f query -d '{ "queryStringParameters": {"term":"ca"}}'
-```
-
-It should give the following output:
-
-```json
-{
- "statusCode": 200,
- "headers": {
- "Access-Control-Allow-Origin": "*",
- "Access-Control-Allow-Credentials": true
- },
- "body": "{\"message\":\"Query:ca\",\"result\":[\"CAMBODIA\",\"CAMEROON\",\"CANADA\",\"CAPE VERDE\",\"CAYMAN ISLANDS\"]}"
-}
-```
-
-You can also test your function using AWS console. In your AWS Lambda section,
-click on your function. Scroll down to the code sections and click on the `Test`
-button on the top right. Use `{ "queryStringParameters": {"term":"ar"}}` as your
-event data.
-
-### Step 6: Run Your Function Locally
-
-In your project folder run:
-
-```shell
-serverless invoke local -f query -d '{ "queryStringParameters": {"term":"ca"}}'
-```
-
-It should give the following output:
-
-```json
-{
- "statusCode": 200,
- "headers": {
- "Access-Control-Allow-Origin": "*",
- "Access-Control-Allow-Credentials": true
- },
- "body": "{\"message\":\"Query:ca\",\"result\":[\"CAMBODIA\",\"CAMEROON\",\"CANADA\",\"CAPE VERDE\",\"CAYMAN ISLANDS\"]}"
-}
-```
-
-# Build Stateful Applications with AWS App Runner and Serverless Redis
-Source: https://upstash.com/docs/redis/tutorials/aws_app_runner_with_redis
-
-AWS App Runner is a container service where AWS runs and scales your container
-in a serverless way. The container storage is ephemeral so you should keep the
-state in an external data store. In this tutorial we will build a simple
-application which will keep the state on Redis and deploy the application to AWS
-App Runner.
-
-### The Stack
-
-* Serverless compute: AWS App Runner (Node.js)
-* Serverless data store: Redis via Upstash
-* Deployment source: github repo
-
-### Project Setup
-
-Create a directory for your project:
-
-```
-mkdir app_runner_example
-
-cd app_runner_example
-```
-
-Create a node project and install dependencies:
-
-```
-npm init
-
-npm install ioredis
-```
-
-Create a Redis DB from [Upstash](https://console.upstash.com). In the database
-details page, copy the connection code (Node tab).
-
-### The Code
-
-In your node project folder, create server.js and copy the below code:
-
-```javascript
-var Redis = require("ioredis");
-const http = require("http");
-
-if (typeof client === "undefined") {
- var client = new Redis(process.env.REDIS_URL);
-}
-
-const requestListener = async function (req, res) {
- if (req.url !== "/favicon.ico") {
- let count = await client.incr("counter");
- res.writeHead(200);
- res.end("Page view:" + count);
- }
-};
-
-const server = http.createServer(requestListener);
-server.listen(8080);
-```
-
-
- This example uses ioredis, you can copy the connection string from the
- **Node** tab in the console.
-
-
-As you see, the code simple increment a counter on Redis and returns the
-response as the page view count.
-
-### Deployment
-
-You have two options to deploy your code to the App Runner. You can either share
-your Github repo with AWS or register your docker image to ECR. In this
-tutorial, we will share
-[our Github repo](https://github.com/upstash/app_runner_example) with App
-Runner.
-
-Create a github repo for your project and push your code. In AWS console open
-the App Runner service. Click on `Create Service` button. Select
-`Source code repository` option and add your repository by connecting your
-Github and AWS accounts.
-
-
-
-In the next page, choose `Nodejs 12` as your runtime, `npm install` as your
-build command, `node server` as your start command and `8080` as your port.
-
-
-
-The next page configures your App Runner service. Set a name for your service.
-Set your Redis URL that you copied from Upstash console as `REDIS_URL`
-environment variable. Your Redis URL should be something like this:
-`rediss://:d34baef614b6fsdeb01b25@us1-lasting-panther-33618.upstash.io:33618`
-You can leave other settings as default.
-
-
-
-Click on `Create and Deploy` at the next page. Your service will be ready in a
-few minutes. Click on the default domain, you should see the page with a view
-counter as [here](https://xmzuanrpf3.us-east-1.awsapprunner.com/).
-
-### App Runner vs AWS Lambda
-
-* AWS Lambda runs functions, App Runner runs applications. So with App Runner
- you do not need to split your application to functions.
-* App Runner is a more portable solution. You can move your application from App
- Runner to any other container service.
-* AWS Lambda price scales to zero, App Runner's does not. With App Runner you
- need to pay for an at least one instance unless you pause the system.
-
-App Runner is great alternative when you need more control on your serverless
-runtime and application. Check out
-[this video](https://www.youtube.com/watch?v=x_1X_4j16A4) to learn more about
-App Runner.
-
-# Session Management on Google Cloud Run with Serverless Redis
-Source: https://upstash.com/docs/redis/tutorials/cloud_run_sessions
-
-Developers are moving their apps to serverless architectures and one of the most
-common questions is
-[how to store user sessions](https://stackoverflow.com/questions/57711095/are-users-sessions-on-google-cloud-run-apps-directed-to-the-same-instance).
-You need to keep your state and session data in an external data store because
-serverless environments are stateless by design. Unfortunately most of the
-databases are not serverless friendly. They do not support per-request pricing
-or they require heavy and persistent connections. These also explain the
-motivations why we built Upstash. Upstash is a serverless Redis database with
-per-request pricing, durable storage.
-
-In this article I will write a basic web application which will run on Google
-Cloud Run and keep the user sessions in Upstash Redis. Google Cloud Run provides
-Serverless Container service which is also stateless. Cloud Run is more powerful
-than serverless functions (AWS Lambda, Cloud Functions) as you can run your own
-container. But you can not guarantee that the same container instance will
-process the requests of the same user. So you need to keep the user session in
-an external storage. Redis is the most popular choice to keep the session data
-thanks to its speed and simplicity. Upstash gives you the serverless Redis
-database which fits perfectly to your serverless stack.
-
-If you want to store your session data manually on Redis, check
-[here](/docs/redis/tutorials/using_google_cloud_functions). But in
-this article I will use [Express session](https://github.com/expressjs/session)
-middleware which can work with Redis for user session management.
-
-Here is the [live demo.](https://cloud-run-sessions-dr7fcdmn3a-uc.a.run.app)
-
-Here is the
-[source code](https://github.com/upstash/examples/tree/master/examples/cloud-run-sessions)
-
-## The Stack
-
-Serverless processing: Google Cloud Run
-
-Serverless data: Upstash
-
-Web framework: Express
-
-## Project Setup
-
-Create a directory for your project:
-
-```
-mkdir cloud-run-sessions
-
-cd cloud-run-sessions
-```
-
-Create a node project and install dependencies:
-
-```
-npm init
-
-npm install express redis connect-redis express-session
-```
-
-Create a Redis DB from [Upstash](https://console.upstash.com). In the database
-details page, click the Connect button, copy the connection code (Node.js
-node-redis).
-
-If you do not have it already, install Google Cloud SDK as described
-[here.](https://cloud.google.com/sdk/docs/install) Set the project and enable
-Google Run and Build services:
-
-```
-gcloud config set project cloud-run-sessions
-
-gcloud services enable run.googleapis.com
-
-gcloud services enable cloudbuild.googleapis.com
-```
-
-## The Code
-
-Create index.js and update as below:
-
-```javascript
-var express = require("express");
-var parseurl = require("parseurl");
-var session = require("express-session");
-const redis = require("redis");
-
-var RedisStore = require("connect-redis")(session);
-var client = redis.createClient({
- // REPLACE HERE
-});
-
-var app = express();
-
-app.use(
- session({
- store: new RedisStore({ client: client }),
- secret: "forest squirrel",
- resave: false,
- saveUninitialized: true,
- })
-);
-
-app.use(function (req, res, next) {
- if (!req.session.views) {
- req.session.views = {};
- }
-
- // get the url pathname
- var pathname = parseurl(req).pathname;
-
- // count the views
- req.session.views[pathname] = (req.session.views[pathname] || 0) + 1;
- next();
-});
-
-app.get("/", function (req, res, next) {
- res.send("you viewed this page " + req.session.views["/"] + " times");
-});
-
-app.get("/foo", function (req, res, next) {
- res.send("you viewed this page " + req.session.views["/foo"] + " times");
-});
-
-app.get("/bar", function (req, res, next) {
- res.send("you viewed this page " + req.session.views["/bar"] + " times");
-});
-
-app.listen(8080, function () {
- console.log("Example app listening on port 8080!");
-});
-```
-
-Run the app: `node index.js`
-
-Check [http://localhost:3000/foo](http://localhost:3000/foo) in different
-browsers to validate it keeps the session.
-
-Add the start script to your `package.json`:
-
-```json
-"scripts": {
- "test": "echo \"Error: no test specified\" && exit 1",
- "start": "node index"
- }
-```
-
-## Build
-
-Create a Docker file (Dockerfile) in the project folder as below:
-
-```
-# Use the official lightweight Node.js 12 image.
-# https://hub.docker.com/_/node
-FROM node:12-slim
-
-# Create and change to the app directory.
-WORKDIR /usr/src/app
-
-# Copy application dependency manifests to the container image.
-# A wildcard is used to ensure both package.json AND package-lock.json are copied.
-# Copying this separately prevents re-running npm install on every code change.
-COPY package*.json ./
-
-# Install dependencies.
-RUN npm install
-
-# Copy local code to the container image.
-COPY . ./
-
-# Run the web service on container startup.
-CMD [ "npm", "start" ]
-```
-
-Build your container image:
-
-```
-gcloud builds submit --tag gcr.io/cloud-run-sessions/main
-```
-
-List your container images: `gcloud container images list`
-
-Run the container locally:
-
-```
-gcloud auth configure-docker
-
-docker run -d -p 8080:8080 gcr.io/cloud-run-sessions/main:v0.1
-```
-
-In case you have an issue on docker run, check
-[here](https://cloud.google.com/container-registry/docs/troubleshooting).
-
-## Deploy
-
-Run:
-
-```
-gcloud run deploy cloud-run-sessions \
-
- --image gcr.io/cloud-run-sessions/main:v0.1 \
-
- --platform managed \
-
- --region us-central1 \
-
- --allow-unauthenticated
-```
-
-This command should give you
-[the URL of your application](https://cloud-run-sessions-dr7fcdmn3a-uc.a.run.app)
-as below:
-
-```
-Deploying container to Cloud Run service [cloud-run-sessions] in project [cloud-run-sessions] region [us-central1]
-
- ✓ Deploying... Done.
-
- ✓ Creating Revision...
-
- ✓ Routing traffic...
-
- ✓ Setting IAM Policy...
-
-Done.
-
-Service [cloud-run-sessions] revision [cloud-run-sessions-00006-dun] has been deployed and is serving 100 percent of traffic.
-
-Service URL: https://cloud-run-sessions-dr7fcdmn3a-uc.a.run.app
-```
-
-## Cloud Run vs Cloud Functions
-
-I have developed two small prototypes with both. Here my impression:
-
-* Simplicity: Cloud functions are simpler to deploy as it does not require any
- container building step.
-* Portability: Cloud Run leverages your container, so anytime you can move your
- application to any containerized system. This is a plus for Cloud Run.
-* Cloud Run looks more powerful as it runs your own container with more
- configuration options. It also allows running longer tasks (can be extended to
- 60 minutes)
-* Cloud Run looks more testable as you can run the container locally. Cloud
- Functions require a simulated environment.
-
-Personally, I see Cloud Functions as a pure serverless solution where Cloud Run
-is a hybrid solution. I would choose Cloud functions for simple, self contained
-tasks or event driven solutions. If my use case is more complex with
-portability/testability requirements, then I would choose Cloud Run.
-
-# Cloudflare Workers with Websockets and Redis
-Source: https://upstash.com/docs/redis/tutorials/cloudflare_websockets_redis
-
-# Use Redis in Cloudflare Workers
-Source: https://upstash.com/docs/redis/tutorials/cloudflare_workers_with_redis
-
-
- You can find the project source code on GitHub.
-
-
-This tutorial showcases using Redis with REST API in Cloudflare Workers. We will
-write a sample edge function (Cloudflare Workers) which will show a custom
-greeting depending on the location of the client. We will load the greeting
-message from Redis so you can update it without touching the code.
-
-### Why Upstash?
-
-* Cloudflare Workers does not allow TCP connections. Upstash provides REST API
- on top of the Redis database.
-* Upstash is a serverless offering with per-request pricing which fits for edge
- and serverless functions.
-* Upstash Global database provides low latency all over the world.
-
-### Prerequisites
-
-1. Install the Cloudflare Wrangler CLI with `npm install wrangler --save-dev`
-
-### Project Setup
-
-Create a Cloudflare Worker with the following options:
-
-```shell
-➜ tutorials > ✗ npx wrangler init
-╭ Create an application with Cloudflare Step 1 of 3
-│
-├ In which directory do you want to create your application?
-│ dir ./greetings-cloudflare
-│
-├ What would you like to start with?
-│ category Hello World example
-│
-├ Which template would you like to use?
-│ type Hello World Worker
-│
-├ Which language do you want to use?
-│ lang TypeScript
-│
-├ Copying template files
-│ files copied to project directory
-│
-├ Updating name in `package.json`
-│ updated `package.json`
-│
-├ Installing dependencies
-│ installed via `npm install`
-│
-╰ Application created
-
-╭ Configuring your application for Cloudflare Step 2 of 3
-│
-├ Installing @cloudflare/workers-types
-│ installed via npm
-│
-├ Adding latest types to `tsconfig.json`
-│ added @cloudflare/workers-types/2023-07-01
-│
-├ Retrieving current workerd compatibility date
-│ compatibility date 2024-10-22
-│
-├ Do you want to use git for version control?
-│ no git
-│
-╰ Application configured
-```
-
-Install Upstash Redis:
-
-```shell
-cd greetings-cloudflare
-npm install @upstash/redis
-```
-
-### Database Setup
-
-Create a Redis database using [Upstash Console](https://console.upstash.com) or [Upstash CLI](https://github.com/upstash/cli) and copy the `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` into your `wrangler.toml` file.
-
-```toml wrangler.toml
-# existing config
-
-[vars]
-UPSTASH_REDIS_REST_URL =
-UPSTASH_REDIS_REST_TOKEN =
-```
-
-Using CLI Tab in the Upstash Console, add some greetings to your database:
-
-![CLI Tab]()
-
-### Greetings Function Setup
-
-Update `src/index.ts`:
-
-```typescript src/index.ts
-import { Redis } from '@upstash/redis/cloudflare';
-
-type RedisEnv = {
- UPSTASH_REDIS_REST_URL: string;
- UPSTASH_REDIS_REST_TOKEN: string;
-};
-
-export default {
- async fetch(request: Request, env: RedisEnv) {
- const redis = Redis.fromEnv(env);
-
- const country = request.headers.get('cf-ipcountry');
- if (country) {
- const greeting = await redis.get(country);
- if (greeting) {
- return new Response(greeting);
- }
- }
-
- return new Response('Hello!');
- },
-};
-```
-
-The code tries to find out the user's location checking the "cf-ipcountry"
-header. Then it loads the corresponding greeting for that location using the Redis
-REST API.
-
-### Run Locally
-
-Run the following command to start your dev session:
-
-```shell
-npx wrangler dev
-```
-
-Visit [localhost:8787](http://localhost:8787)
-
-### Build and Deploy
-
-Build and deploy your app to Cloudflare:
-
-```shell
-npx wrangler deploy
-```
-
-Visit the output url.
-
-# Backendless Coin Price List with GraphQL API, Serverless Redis and Next.JS
-Source: https://upstash.com/docs/redis/tutorials/coin_price_list
-
-In this tutorial, we will develop a simple coin price list using GraphQL API of
-Upstash. You can call the application `backendless` because we will access the
-database directly from the client (javascript). See the
-[code](https://github.com/upstash/examples/tree/master/examples/coin-price-list).
-
-
-
-## Motivation
-
-We want to give a use case where you can use the GraphQL API without any backend
-code. The use case is publicly available read only data for web applications
-where you need low latency. The data is updated frequently by another backend
-application, you want your users to see the last updated data. Examples:
-Leaderboards, news list, blog list, product list, top N items in the homepages.
-
-### `1` Project Setup:
-
-Create a Next application: `npx create-next-app`.
-
-Install Apollo GraphQL client: `npm i @apollo/client`
-
-### `2` Database Setup
-
-If you do not have one, create a database following this
-[guide](../overall/getstarted). Connect your database via Redis CLI and run:
-
-```shell
-rpush coins '{ "name" : "Bitcoin", "price": 56819, "image": "https://s2.coinmarketcap.com/static/img/coins/64x64/1.png"}' '{ "name" : "Ethereum", "price": 2130, "image": "https://s2.coinmarketcap.com/static/img/coins/64x64/1027.png"}' '{ "name" : "Cardano", "price": 1.2, "image": "https://s2.coinmarketcap.com/static/img/coins/64x64/2010.png"}' '{ "name" : "Polkadot", "price": 35.96, "image": "https://s2.coinmarketcap.com/static/img/coins/64x64/6636.png"}' '{ "name" : "Stellar", "price": 0.506, "image": "https://s2.coinmarketcap.com/static/img/coins/64x64/512.png"}'
-```
-
-### `3` Code
-
-In the Upstash console, copy the read only access key in your API configuration
-page (GraphQL Explorer > Configure API). In the `_app.js` create the Apollo
-client and replace the your access key as below:
-
-
- You need to use Read Only Access Key, because the key will be accessible
- publicly.
-
-
-```javascript
-import "../styles/globals.css";
-import {
- ApolloClient,
- ApolloProvider,
- createHttpLink,
- InMemoryCache,
-} from "@apollo/client";
-
-const link = createHttpLink({
- uri: "https://graphql-us-east-1.upstash.io/",
- headers: {
- Authorization: "Bearer YOUR_ACCESS_TOKEN",
- },
-});
-const client = new ApolloClient({
- uri: "https://graphql-us-east-1.upstash.io/",
- cache: new InMemoryCache(),
- link,
-});
-
-function MyApp({ Component, pageProps }) {
- return (
-
- {" "}
-
- );
-}
-
-export default MyApp;
-```
-
-Edit `index.js` as below:
-
-```javascript
-import Head from "next/head";
-import styles from "../styles/Home.module.css";
-import { gql, useQuery } from "@apollo/client";
-import React from "react";
-
-const GET_COIN_LIST = gql`
- query {
- redisLRange(key: "coins", start: 0, stop: 6)
- }
-`;
-
-export default function Home() {
- let coins = [];
- const { loading, error, data } = useQuery(GET_COIN_LIST);
-
- if (!loading && !error) {
- for (let x of data.redisLRange) {
- let dd = JSON.parse(x);
- coins.push(dd);
- }
- }
-
- return (
-
-
- Create Next App
-
-
-
-
-
Coin Price List
-
-
-
-
- {!loading ? (
- coins.map((item, ind) => (
-
-
-
-
-
{item.name}
-
${item.price}
-
- ))
- ) : (
-
-
-
-
-
- )}
-
-
-
-
-
-
-
- );
-}
-```
-
-### `4` Run
-
-Run your application locally: `npm run dev`
-
-### `5` Live!
-
-Go to [http://localhost:3000/](http://localhost:3000/) 🎉
-
-# Build a Leaderboard API At Edge using Cloudflare Workers and Redis
-Source: https://upstash.com/docs/redis/tutorials/edge_leaderboard
-
-With edge functions, it is possible to run your backend at the closest location
-to your users. Cloudflare Workers and Fastly Compute@Edge runs your function at
-the closest location to your user using their CDN infrastructure.
-
-In this article we will implement a very common web use case at Edge. We will
-implement a leaderboard API without any backend servers, containers or even
-serverless functions. We will just use edge functions. Leaderboard will have the
-following APIs:
-
-* addScore: Adds a score with the player's name. This will write the score to
- the Upstash Redis directly from the Edge functions.
-* getLeaderBoard: Returns the list of score-player pairs. This call will first
- check the Edge cache. If the leaderboard does not exist at the Edge Cache then
- it will fetch it from the Upstash Redis.
-
-Edge caching is deprecated. Please use global database instead.
-
-## Project Setup
-
-In this tutorial, we will use Cloudflare Workers and Upstash. You can create a
-free database from [Upstash Console](https://console.upstash.com). Then create a
-Workers project using
-[Wrangler](https://developers.cloudflare.com/workers/get-started/guide).
-
-Install wrangler: `npm install -g @cloudflare/wrangler`
-
-Authenticate: `wrangler login` or `wrangler config`
-
-Then create a project: `wrangler generate edge-leaderboard`
-
-Open `wrangler.toml`. Run `wrangler whoami` and copy/paste your account id to
-your wrangler.toml.
-
-Find your REST token from database details page in the
-[Upstash Console](https://console.upstash.com). Copy/paste your token to your
-wrangler toml as below:
-
-```
-name = "edge-leaderboard"
-type = "javascript"
-
-account_id = "REPLACE_YOUR_ACCOUNT_ID"
-workers_dev = true
-route = ""
-zone_id = ""
-
-[vars]
-TOKEN = "REPLACE_YOUR_UPSTASH_REST_TOKEN"
-```
-
-## The Code
-
-The only file we need is the Workers Edge function. Update the index.js as
-below:
-
-```javascript
-addEventListener("fetch", (event) => {
- event.respondWith(handleRequest(event.request));
-});
-
-async function handleRequest(request) {
- if (request.method === "GET") {
- return getLeaderboard();
- } else if (request.method === "POST") {
- return addScore(request);
- } else {
- return new Response("Invalid Request!");
- }
-}
-
-async function getLeaderboard() {
- let url =
- "https://us1-full-bug-31874.upstash.io/zrevrange/scores/0/1000/WITHSCORES/?_token=" +
- TOKEN;
- let res = await fetch(new Request(url), {
- cf: {
- cacheTtl: 10,
- cacheEverything: true,
- cacheKey: url,
- },
- });
- return res;
-}
-
-async function addScore(request) {
- const { searchParams } = new URL(request.url);
- let player = searchParams.get("player");
- let score = searchParams.get("score");
- let url =
- "https://us1-full-bug-31874.upstash.io/zadd/scores/" +
- score +
- "/" +
- player +
- "?_token=" +
- TOKEN;
- let res = await fetch(url);
- return new Response(await res.text());
-}
-```
-
-We route the request to two methods: if it is a GET, we return the leaderboard.
-If it is a POST, we read the query parameters and add a new score.
-
-In the getLeaderboard() method, you will see we pass a cache configuration to
-the fetch() method. It caches the result of the request at the Edge for 10
-seconds.
-
-## Test The API
-
-In your project folder run `wrangler dev`. It will give you a local URL. You can
-test your API with curl:
-
-Add new scores:
-
-```shell
-curl -X POST http://127.0.0.1:8787\?player\=messi\&score\=13
-
-curl -X POST http://127.0.0.1:8787\?player\=ronaldo\&score\=17
-
-curl -X POST http://127.0.0.1:8787\?player\=benzema\&score\=18
-```
-
-Get the leaderboard:
-
-```shell
-curl -w '\n Latency: %{time_total}s\n' http://127.0.0.1:8787
-```
-
-Call the “curl -w '\n Total: %{time_total}s\n'
-[http://127.0.0.1:8787](http://127.0.0.1:8787)” multiple times. You will see the
-latency becomes very small with the next calls as the cached result comes from
-the edge.
-
-If you wait more than 10 seconds then you will see the latency becomes higher as
-the cache is evicted and the function fetches the leaderboard from the Upstash
-Redis again.
-
-## Deploy The API
-
-First change the type in the wrangler.toml to `webpack`
-
-```
-name = "edge-leaderboard"
-type = "webpack"
-```
-
-Then, run `wrangler publish`. Wrangler will output the URL. If you want to
-deploy to a custom domain see
-[here](https://developers.cloudflare.com/workers/get-started/guide#optional-configure-for-deploying-to-a-registered-domain).
-
-# Express Session with Serverless Redis
-Source: https://upstash.com/docs/redis/tutorials/express_session
-
-This tutorial shows how to use Serverless Redis as your session storage for your
-Express Applications.
-
-See the
-[code](https://github.com/upstash/examples/tree/main/examples/express-session-with-redis)
-
-### Step-1: Create Project
-
-Create a folder for your project and run: `npm init`
-
-### Step-2: Install Redis and Express
-
-In your project folder run:
-`npm install express redis connect-redis express-session`
-
-### Step-3: Create a Redis (Upstash) Database For Free
-
-Create a database as described [here](../overall/getstarted).
-
-### Step-4: index.js
-
-In Upstash console, click the `Connect` button, copy the connection code
-(Node.js node-redis). Create index.js file as below and replace the Redis
-connection part.
-
-```javascript
-var express = require("express");
-var parseurl = require("parseurl");
-var session = require("express-session");
-const redis = require("redis");
-
-var RedisStore = require("connect-redis")(session);
-var client = redis.createClient({
- // REPLACE HERE
-});
-
-var app = express();
-
-app.use(
- session({
- store: new RedisStore({ client: client }),
- secret: "forest squirrel",
- resave: false,
- saveUninitialized: true,
- })
-);
-
-app.use(function (req, res, next) {
- if (!req.session.views) {
- req.session.views = {};
- }
-
- // get the url pathname
- var pathname = parseurl(req).pathname;
-
- // count the views
- req.session.views[pathname] = (req.session.views[pathname] || 0) + 1;
- next();
-});
-
-app.get("/foo", function (req, res, next) {
- res.send("you viewed this page " + req.session.views["/foo"] + " times");
-});
-
-app.get("/bar", function (req, res, next) {
- res.send("you viewed this page " + req.session.views["/bar"] + " times");
-});
-
-app.listen(3000, function () {
- console.log("Example app listening on port 3000!");
-});
-```
-
-### Step-5: Run the app
-
-`node index.js`
-
-### Step-6: Check your work
-
-Open http://localhost:3000/bar and http://localhost:3000/foo in different
-browsers. Check if the view-count is incrementing as expected.
-
-### FAQ:
-
-**There is a default session storage of express-session. Why do I need Redis?**
-
-_Default session store loses the session data when the process crashes.
-Moreover, it does not scale. You can not utilize multiple web servers to serve
-your sessions._
-
-**Why Upstash?**
-
-_You can use any Redis offering or self hosted one. But Upstash's serverless
-approach with per-request-pricing will help you to minimize your cost with zero
-maintenance._
-
-**How to configure the session storage?**
-
-_See [here](https://github.com/expressjs/session#readme)_
-
-# Serverless Golang API with Redis
-Source: https://upstash.com/docs/redis/tutorials/goapi
-
-This tutorial shows how to build a serverless API with Golang and Redis. The API
-will simply count the page views and show it in JSON format.
-
-### The Stack
-
-* Serverless compute: AWS Lambda (Golang)
-* Serverless data store: Redis via Upstash
-* Deployment tool: AWS SAM
-
-### Prerequisites:
-
-* An AWS account for AWS Lambda functions.
-* Install AWS SAM CLI tool as described here to create and deploy the project.
-* An Upstash account for serverless Redis.
-
-### Step 1: Init the Project
-
-Run the sam init and then
-
-* Select AWS Quick Start Templates
-* Select 4 - go1.x
-* Enter your project name: go-redis-example
-* Select 1 - Hello World Example SAM will generate your project in a new folder.
-
-### Step 2: Install a Redis Client
-
-Our only dependency is redis client. Install go-redis via
-`go get github.com/go-redis/redis/v8`
-
-### Step 3: Create a Redis Database
-
-Create a Redis database from Upstash console. Free tier should be enough. It is
-pretty straight forward but if you need help, check
-[getting started](../overall/getstarted) guide. In the database details page,
-click the Connect button. You will need the endpoint and password in the next
-step.
-
-### Step 4: The function Code
-
-Edit the hello-world>main.go as below:
-
-```go
-package main
-import (
- "context"
- "encoding/json"
- "github.com/aws/aws-lambda-go/events"
- "github.com/aws/aws-lambda-go/lambda"
- "github.com/go-redis/redis/v8"
- "strconv"
-)
-var ctx = context.Background()
-
-type MyResponse struct {
- Count string `json:"count:"`
-}
-
-var rdb = redis.NewClient(&redis.Options{
- Addr: "YOUR_REDIS_ENDPOINT",
- Password: "YOUR_REDIS_PASSWORD",
- DB: 0,
-})
-
-func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
- count, err := rdb.Incr(ctx, "count").Result()
- if err != nil {
- panic(err)
- }
- response := &MyResponse{
- Count: strconv.FormatInt(count, 10),
- }
- body, err := json.Marshal(response)
- return events.APIGatewayProxyResponse{
- Headers: map[string]string{"Content-Type": "application/json"},
- Body: string(body),
- StatusCode: 200,
- }, nil
-}
-
-func main() {
- lambda.Start(handler)
-}
-```
-
-Replace the "YOUR_REDIS_ENDPOINT" and "YOUR_REDIS_PASSWORD" with your database's
-endpoint and password which you created in the Step 3. The code simply
-increments a counter in Redis database and returns its value in json format.
-
-### Step 5: Deployment
-
-Now we are ready to deploy our API. First build it via `sam build`. Then run the
-command `sam local start-api`. You can check your API locally on
-[http://127.0.0.1:3000/hello](http://127.0.0.1:3000/hello)
-
-
- If it is working, you can deploy your app to AWS by running `sam deploy --guided`.
-Enter a stack name and pick your region. After confirming changes, the deployment
-should begin. The command will output API Gateway endpoint URL, check the API in
-your browser. You can also check your deployment on your AWS console. You will see
-your function has been created.
-
- Click on your function, you will see the code is uploaded and API Gateway
-is configured.
-
-### Notes
-
-* Check the template.yaml file. You can add new functions and APIGateway
- endpoints editing this file.
-* It is a good practice to keep your Redis endpoint and password as environment
- variable.
-* You can use [serverless framework](https://www.serverless.com/) instead of AWS
- SAM to deploy your function.
-
-# Build a Serverless Histogram API with Redis
-Source: https://upstash.com/docs/redis/tutorials/histogram
-
-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
->> 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.
-```
-
-
- See [Using AWS SAM](/docs/redis/tutorials/using_aws_sam), if you prefer AWS SAM
- over Serverless Framework.
-
-
-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
-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
-```
-
-
- This example uses ioredis, you can copy the connection string from the
- **Node** tab in the console.
-
-
-### `3` Code
-
-Edit handler.js as below.
-
-```javascript
-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
-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
-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
-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
-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);
- }
-}
-```
-
-# Job Processing and Event Queue with Serverless Redis
-Source: https://upstash.com/docs/redis/tutorials/job_processing
-
-### Motivation
-
-Serverless functions are great for many tasks with their dynamic scaling and
-flexible pricing models. But when you have a task which is composed of long
-running complex steps, it is not feasible to run it in a single serverless
-function. A simple solution is simply to offload complicated tasks from the
-serverless function. You can process those asynchronously in your preferred
-environment, this can be other serverless functions, serverless containers or
-traditional server based processes too. To offload your tasks, you need a
-reliable event queue. In this article we will use Upstash Redis for this
-purpose.
-
-### Scenario
-
-You are developing a `New Employee Registration` form for your company. Saving
-employee records to the database is the easy part. Here possible things to do:
-
-* Create accounts (email, slack etc).
-* Send email to the employee.
-* Send email to the hiring manager and others.
-* Create a JIRA ticket for the IT department so they will set up the employee’s
- computer.
-
-This list can be longer for bigger companies.
-
-* You want the form to be responsive. You do want a new employee to wait for
- minutes after clicking submit.
-* The above steps are subject to change. You do not want to update your code
- whenever a new procedure is added.
-
-Decoupling the side procedures will solve the above issues. When a new employee
-is registered, you can push a new event to the related task queue; then another
-process will consume the task.
-
-Let’s build the sample application:
-
-### Project Setup
-
-The project will consist of two modules:
-
-* Producer will be a serverless function which will receive input parameters
- required to register a new employee. It will also produce events for the task
- queue.
-* Consumer will be a worker application which will continuously consume the task
- queue.
-
-(See
-[the source code](https://github.com/upstash/examples/tree/main/examples/task-queue))
-
-### Tech Stack
-
-* AWS Lambda for Serverless computing
-* [Upstash](https://upstash.com) as Serverless Redis
-* [Bull](https://github.com/OptimalBits/bull) as task queue implementation
-* [Serverless framework](https://www.serverless.com/) for project deployment
-
-### Upstash Database
-
-You can create a free Redis database from [Upstash](https://docs.upstash.com/).
-
-After creating a database, copy the endpoint, port and password as you will need
-in the next steps.
-
-### Producer Code
-
-Our producer will be the serverless function which will get the request
-parameters and produce the task for the queue. In the real world this code
-should do things like saving to the database but I will not implement this for
-the sake of simplicity.
-
-1- Create a Serverless project by `serverless` command.
-
-```shell
-➜ 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? producer
-
-Project successfully created in 'producer' 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.
-```
-
-2- Install [bull](https://github.com/OptimalBits/bull):
-
-`npm install bull`
-
-3- Function code:
-
-```javascript
-var Queue = require("bull");
-
-var settings = {
- stalledInterval: 300000, // How often check for stalled jobs (use 0 for never checking).
- guardInterval: 5000, // Poll interval for delayed jobs and added jobs.
- drainDelay: 300, // A timeout for when the queue is in drained state (empty waiting for jobs).
-};
-
-module.exports.hello = async (event) => {
- var taskQueue = new Queue(
- "employee registration",
- {
- redis: {
- port: 32016,
- host: "us1-upward-ant-32016.upstash.io",
- password: "ake4ff120d6b4216df220736be7eab087",
- tls: {},
- },
- },
- settings
- );
- await taskQueue.add({ event: event });
-
- // TODO save the employee record to a database
- return { message: "New employee event enqueued! 34", event };
-};
-```
-
-Note1: Do not forget to replace your own Redis endpoint, port and password.
-Remove the TLS part if you disabled TLS.
-
-Note2: We give extra parameters (settings) to the event queue (Bull), so it will
-not exploit Upstash quotas. Update the interval parameters depending on your
-tolerance to event latency.
-
-### Consumer Code
-
-We will write a basic Node application to consume the events. Create a new
-directory and run `npm init` and `npm install bull`. Then create index.js as
-below:
-
-```javascript
-var Queue = require("bull");
-
-var settings = {
- stalledInterval: 300000, // How often check for stalled jobs (use 0 for never checking).
- guardInterval: 5000, // Poll interval for delayed jobs and added jobs.
- drainDelay: 300, // A timeout for when the queue is in drained state (empty waiting for jobs).
-};
-
-var taskQueue = new Queue(
- "employee registration",
- {
- redis: {
- port: 32016,
- host: "us1-upward-ant-32016.upstash.io",
- password: "ake4ff120d6b4216df220736be7eab087",
- tls: {},
- },
- },
- settings
-);
-
-taskQueue
- .process(function (job, done) {
- console.log(job.data);
- // TODO process the new employee event
- done();
- })
- .catch((err) => {
- console.log(err);
- });
-```
-
-Note1: Do not forget to replace your own Redis endpoint, port and password.
-Remove the TLS part if you disabled TLS.
-
-Note2: We give extra parameters (settings) to the event queue (Bull), so it will
-not exploit Upstash quotas. Update the interval parameters depending on your
-tolerance to event latency.
-
-### Test the Application
-
-First run the consumer application with
-
-`node index`
-
-To test the producer code, run:
-
-```shell
-serverless invoke local -f hello -d "{name:'Bill Gates', email:'bill@upstash.com', position:'Developer', date:'20210620'}"
-```
-
-You will see producer will log as below:
-
-![alt_text]()
-
-And consumer will log as below:
-
-![alt_text]()
-
-# Caching in Laravel with Redis
-Source: https://upstash.com/docs/redis/tutorials/laravel_caching
-
-## Project Setup
-
-Create a new Laravel application:
-
-```shell
-laravel new todo-cache
-cd todo-cache
-```
-
-## Database Setup
-
-Create a Redis database using [Upstash Console](https://console.upstash.com). Go to the **Connect to your database** section and click on Laravel. Copy those values into your .env file:
-
-```shell .env
-REDIS_HOST=""
-REDIS_PORT=6379
-REDIS_PASSWORD=""
-```
-
-### Cache Setup
-To use Upstash Redis as your caching driver, update the CACHE_STORE in your .env file:
-
-```shell .env
-CACHE_STORE="redis"
-REDIS_CACHE_DB="0"
-```
-
-## Creating a Todo App
-
-First, we'll create a Todo model with its associated controller, factory, migration, and API resource files:
-
-```shell
-php artisan make:model Todo -cfmr --api
-```
-
-Next, we'll set up the database schema for our todos table with a simple structure including an ID, title, and timestamps:
-
-```php database/migrations/2025_02_10_111720_create_todos_table.php
-id();
- $table->string('title');
- $table->timestamps();
- });
- }
-
- /**
- * Reverse the migrations.
- */
- public function down(): void
- {
- Schema::dropIfExists('todos');
- }
-};
-```
-
-We'll create a factory to generate fake todo data for testing and development:
-
-```php database/factories/TodoFactory.php
-
- */
-class TodoFactory extends Factory
-{
- /**
- * Define the model's default state.
- *
- * @return array
- */
- public function definition(): array
- {
- return [
- 'title' => $this->faker->sentence,
- ];
- }
-}
-```
-
-In the database seeder, we'll set up the creation of 50 sample todo items:
-
-```php database/seeders/DatabaseSeeder.php
-times(50)->create();
- }
-}
-```
-
-Run the migration to create the todos table in the database:
-
-```shell
-php artisan migrate
-```
-
-Seed the database with our sample todo items:
-
-```shell
-php artisan db:seed
-```
-
-Install the API package:
-
-```shell
-php artisan install:api
-```
-
-Set up the API routes for our Todo resource:
-
-```php routes/api.php
- */
- use HasFactory;
-
- protected $fillable = ['title'];
-}
-```
-
-Next, we'll update the methods in the TodoController to use caching:
-
-```php app/Http/Controllers/TodoController.php
-validate([
- 'title' => 'required|string|max:255',
- ]);
-
- $todo = Todo::create($request->all());
-
- // Invalidate the todos cache
- Cache::forget(self::CACHE_KEY);
-
- return response()->json($todo, Response::HTTP_CREATED);
- }
-
- /**
- * Display the specified resource.
- */
- public function show(Todo $todo): Todo
- {
- return Cache::flexible(
- "todo.{$todo->id}",
- self::CACHE_TTL,
- function () use ($todo) {
- return $todo;
- }
- );
- }
-
- /**
- * Update the specified resource in storage.
- */
- public function update(Request $request, Todo $todo): JsonResponse
- {
- $request->validate([
- 'title' => 'required|string|max:255',
- ]);
-
- $todo->update($request->all());
-
- // Invalidate both the collection and individual todo cache
- Cache::forget(self::CACHE_KEY);
- Cache::forget("todo.{$todo->id}");
-
- return response()->json($todo);
- }
-
- /**
- * Remove the specified resource from storage.
- */
- public function destroy(Todo $todo): JsonResponse
- {
- $todo->delete();
-
- // Invalidate both the collection and individual todo cache
- Cache::forget(self::CACHE_KEY);
- Cache::forget("todo.{$todo->id}");
-
- return response()->json(null, Response::HTTP_NO_CONTENT);
- }
-}
-```
-
-Now we can test our methods with the following curl commands:
-
-```shell
-# Get all todos
-curl http://todo-cache.test/api/todos
-
-# Get a specific todo
-curl http://todo-cache.test/api/todos/1
-
-# Create a new todo
-curl -X POST http://todo-cache.test/api/todos \
- -H "Content-Type: application/json" \
- -d '{"title":"New Todo"}'
-
-# Update a todo
-curl -X PUT http://todo-cache.test/api/todos/1 \
- -H "Content-Type: application/json" \
- -d '{"title":"Updated Todo"}'
-
-# Delete a todo
-curl -X DELETE http://todo-cache.test/api/todos/1
-```
-
-Visit Redis Data Browser in Upstash Console to see the cached data.
-
-# Next.js with Redis
-Source: https://upstash.com/docs/redis/tutorials/nextjs_with_redis
-
-
- You can find the project source code on GitHub.
-
-
- This tutorial uses Next.js App Router. If you want to use Pages Router, check out our [Pages Router tutorial](/docs/redis/quickstarts/nextjs-pages-router).
-
-
-This tutorial uses Redis as state store for a Next.js application. We simply add
-a counter that pulls the data from Redis.
-
-### Project Setup
-
-Let's create a new Next.js application with App Router and install `@upstash/redis` package.
-
-```shell
-npx create-next-app@latest
-cd my-app
-npm install @upstash/redis
-```
-
-### Database Setup
-
-Create a Redis database using [Upstash Console](https://console.upstash.com) or [Upstash CLI](https://github.com/upstash/cli) and copy the `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` into your `.env` file.
-
-```shell .env
-UPSTASH_REDIS_REST_URL=
-UPSTASH_REDIS_REST_TOKEN=
-```
-
-
- If you are using the Vercel & Upstash integration, you may use the following environment variables:
-
- ```shell .env
- KV_REST_API_URL=
- KV_REST_API_TOKEN=
- ```
-
-
-### Home Page Setup
-
-Update `/app/page.tsx`:
-```tsx /app/page.tsx
-import { Redis } from "@upstash/redis";
-
-const redis = Redis.fromEnv();
-
-export default async function Home() {
- const count = await redis.incr("counter");
- return (
-
-
Counter: {count}
-
- )
-}
-```
-
-### Run & Deploy
-Run the app locally with `npm run dev`, check `http://localhost:3000/`
-
-Deploy your app with `vercel`
-
-
- You can also integrate your Vercel projects with Upstash using Vercel
- Integration module. Check [this article](../howto/vercelintegration).
-
-
-# Building a Serverless Notification API for Your Web Application with Redis
-Source: https://upstash.com/docs/redis/tutorials/notification
-
-Notifications and announcements help you communicate with your web site
-visitors. It is not feasible to update your code and redeploy your website each
-time you want to show a new message. It may also be too much investment to set
-up a backend and maintain it to just serve these notifications. In this article,
-we will build a website which will load the notification message directly from
-the Redis database without a backend.
-
-### Backendless? How is that possible?
-
-Yes, we will not use any backend service, even a serverless function. We will
-access Redis from the client side directly. This is possible with the read only
-REST API provided by Upstash.
-
-### Requirements
-
-* The page will display a notification if the user has not already seen the
- notification before.
-* The page will only show the latest notification.
-
-Check out
-[the code here](https://github.com/upstash/examples/tree/master/examples/serverless-notification-api).
-
-### Project Setup
-
-I will create a React application but you can use any other web framework. It
-will simply call the Redis REST API and show the message as a notification.
-
-Create the app:
-
-```shell
-npx create-react-app serverless-notification-api
-```
-
-Install a toast component to show the notification:
-
-```shell
-npm install --save react-toastify
-```
-
-Create a free database from [Upstash](https://console.upstash.com/) and copy the
-REST url and read only token. You should switch the Read-Only Token setting. In
-the database details page, click on the `Read-Only Token` switch.
-
-![alt_text]()
-
-### Implementation
-
-The logic is simple. We will keep the notifications in a Redis Sorted Set. We
-will keep a version (integer) in the local storage. We will use the versions as
-scores in the sorted set. Each notification message will have a version (score)
-and the higher score means the newer message. At each page load, we will query
-the Redis sorted set to load the messages which have higher scores than the
-locally stored version. After loading a notification message I will set my local
-version equal to the latest notification’s version. This will prevent showing
-the same notification to the same users more than once. Here the implementation:
-
-```javascript
-import logo from "./logo.svg";
-import "./App.css";
-import { toast, ToastContainer } from "react-toastify";
-import "react-toastify/dist/ReactToastify.css";
-import { useEffect } from "react";
-
-function App() {
- useEffect(() => {
- async function fetchData() {
- try {
- let version = localStorage.getItem("notification-version");
- version = version ? version : 0;
- const response = await fetch(
- "REPLACE_UPSTASH_REDIS_REST_URL/zrevrangebyscore/messages/+inf/" +
- version +
- "/WITHSCORES/LIMIT/0/1",
- {
- headers: {
- Authorization: "Bearer REPLACE_UPSTASH_REDIS_REST_TOKEN",
- },
- }
- );
- const res = await response.json();
- const v = parseInt(res.result[1]);
- if (v) {
- localStorage.setItem("notification-version", v + 1);
- }
- toast(res.result[0]);
- } catch (e) {
- console.error(e);
- }
- }
- fetchData();
- });
-
- return (
-
- );
-}
-
-export default App;
-```
-
-### How to Add New Notification Messages
-
-You can simply add new messages to the Redis sorted set with a highest score so
-it will be displayed to the user with page loads. For our application the name
-of the sorted set is `messages`.
-
-![alt_text]()
-
-You can also remove a message using the [ZREM](https://redis.io/commands/zrem)
-command.
-
-### Conclusion
-
-You do not need a backend to access Upstash Redis thanks to the REST API. You
-can expose the token with your client side application, as the token only allows
-read-only access. This helps developers to build applications without backend
-for many use cases where the data is already available publicly.
-
-# Nuxt with Redis
-Source: https://upstash.com/docs/redis/tutorials/nuxtjs_with_redis
-
-This tutorial uses Redis as state store for a Nuxt application. In it, we will build an application
-which simply increments a counter and saves & fetches the last increment time.
-
-See [code](https://github.com/upstash/examples/tree/master/examples/nuxt-with-redis) and
-[demo](https://nuxt-with-redis.vercel.app)
-
-### `1` Create Nuxt.js Project
-
-Run this in terminal
-
-```bash
-npx nuxi@latest init nuxtjs-with-redis
-```
-
-Go to the new directory `nuxtjs-with-redis` and install `@upstash/redis`:
-
-```
-npm install @upstash/redis
-```
-
-### `2` Create a Upstash Redis database
-
-Next, you will need an Upstash Redis database. You can follow
-[our guide for creating a new database](/docs/redis/overall/getstarted).
-
-### `3` Set up environment variables
-
-Copy the `.env.example` file in this directory to `.env`
-
-```bash
-cp .env.example .env
-```
-
-Then, set the following environment variables:
-
-```
-UPSTASH_REDIS_REST_URL=""
-UPSTASH_REDIS_REST_TOKEN=""
-```
-
-You can get the values of these env variables on the page of your Redis database.
-
-
- If you are using the Vercel & Upstash integration, you may use the following environment variables:
-
- ```shell .env
- KV_REST_API_URL=
- KV_REST_API_TOKEN=
- ```
-
-
-### `4` Define the endpoint
-
-Next, we will define the endpoint which will call Redis:
-
-```javascript title="server/api/increment.ts"
-import { defineEventHandler } from "h3";
-import { Redis } from "@upstash/redis";
-
-// Initialize Redis
-const redis = new Redis({
- url: process.env.UPSTASH_REDIS_REST_URL || "",
- token: process.env.UPSTASH_REDIS_REST_TOKEN || ""
-});
-
-export default defineEventHandler(async () => {
- const identifier = "api_call_counter";
-
- try {
- // Increment the API call counter and get the updated value
- const count = await redis.incr(identifier);
-
- // Optionally, you can also retrieve other information like the last time it was called
- const lastCalled = await redis.get("last_called");
- const lastCalledAt = lastCalled || "Never";
-
- // Store the current timestamp as the last called time
- await redis.set("last_called", new Date().toISOString());
-
- // Return the count and last called time
- return {
- success: true,
- count: count,
- lastCalled: lastCalledAt,
- };
- } catch (error) {
- console.error("Redis error:", error);
- return {
- success: false,
- message: "Error interacting with Redis",
- };
- }
-});
-```
-
-### `5` Run
-
-Finally, we can run the application and call our endpoint:
-
-```bash
-npm run dev
-```
-
-If you are using [our example app](https://github.com/upstash/examples/tree/master/examples/nuxt-with-redis),
-you can simply click the `Increment` button to run the endpoint we defined.
-
-Otherwise, you can simply make a curl request:
-
-```
-curl http://localhost:3000/api/increment
-```
-
-When you make the request, you should see something like this:
-
-```
-{
- "success": true,
- "count": 166,
- "lastCalled": "2024-10-10T07:04:42.381Z"
-}
-```
-
-### Notes:
-
-* For best performance the application should run in the same region with the
- Redis database's region.
-
-# Redis as a Cache for Your FastAPI App
-Source: https://upstash.com/docs/redis/tutorials/python_fastapi_caching
-
-### Introduction
-
-In this tutorial, we’ll learn how to use Redis to add caching to a FastAPI application. By caching API responses in Redis, we can reduce database queries, improve response times, and ensure that frequently requested data is delivered quickly.
-
-We’ll create a simple FastAPI app that fetches weather data from an external API. The app will store the results in Redis, so the next time someone requests the same data, it can be returned from the cache instead of making a new API request. Let’s get started!
-
-### Environment Setup
-
-First, install FastAPI, the Upstash Redis client, and an ASGI server:
-
-```shell
-pip install fastapi upstash-redis uvicorn[standard]
-```
-
-### Database Setup
-
-Create a Redis database using the [Upstash Console](https://console.upstash.com) or [Upstash CLI](https://github.com/upstash/cli), and export the `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` to your environment:
-
-```shell
-export UPSTASH_REDIS_REST_URL=
-export UPSTASH_REDIS_REST_TOKEN=
-```
-
-We'll also need to generate a `WEATHER_API_KEY` from [Weather API Website](https://www.weatherapi.com) for free and we will export it.
-
-```shell
-export WEATHER_API_KEY=
-```
-
-You can also use `python-dotenv` to load environment variables from your `.env` file.
-
-### Application Setup
-
-In this example, we will build an API that fetches weather data and caches it in Redis.
-
-Create `main.py`:
-
-```python main.py
-from fastapi import FastAPI
-from upstash_redis import Redis
-import requests
-import os
-
-app = FastAPI()
-
-# Connect to Redis using environment variables
-redis = Redis.from_env()
-
-# Mock API endpoint for weather data
-WEATHER_API_URL = "https://api.weatherapi.com/v1/current.json"
-API_KEY = os.getenv("WEATHER_API_KEY")
-
-@app.get("/weather/{city}")
-def get_weather(city: str):
- cache_key = f"weather:{city}"
-
- # Check if the data exists in cache
- cached_data = redis.get(cache_key)
- if cached_data:
- return {"source": "cache", "data": cached_data}
-
- # Fetch data from external API
- response = requests.get(f"{WEATHER_API_URL}?key={API_KEY}&q={city}")
- weather_data = response.json()
-
- # Store the data in Redis cache with a 10-minute expiration
- redis.setex(cache_key, 600, weather_data)
-
- return {"source": "api", "data": weather_data}
-```
-
-### Running the Application
-
-Run the FastAPI app with Uvicorn:
-
-```shell
-uvicorn main:app --reload
-```
-
-To test the application you can visit `http://127.0.0.1:8000/weather/istanbul` in your browser or use curl to get the weather data for Istanbul. The first request will fetch the data from the weather API and cache it, and subsequent requests will return the cached data until the cache expires after 10 minutes.
-
-To monitor your data in Redis, you can use the [Upstash Console](https://console.upstash.com) and check out the Data Browser tab.
-
-### Code Breakdown
-
-1. **Redis Setup**: We use `Redis.from_env()` to initialize the Redis connection using the environment variables. Redis will store the weather data with city names as cache keys.
-
-2. **Cache Lookup**: When a request is made to the `/weather/{city}` endpoint, we check if the weather data is already cached by looking up the `weather:{city}` key in Redis. If the data is found in cache, it's returned immediately.
-
-3. **Fetching External Data**: If the data is not in cache, the app sends a request to the external weather API to fetch the latest data. The response is then cached using `redis.setex()`, which stores the data with a 10-minute expiration.
-
-4. **Cache Expiration**: We use a 10-minute TTL (time-to-live) for the cached weather data to ensure it's periodically refreshed. After the TTL expires, the next request will fetch fresh data from the external API and store it in cache again.
-
-# Multithreaded Web Scraping with Redis Caching
-Source: https://upstash.com/docs/redis/tutorials/python_multithreading
-
-In this tutorial, we’ll build a multithreaded web scraper in Python that leverages Redis for caching responses to minimize redundant HTTP requests. The scraper will be capable of handling groups of URLs across multiple threads while caching responses to reduce load and improve performance.
-
-### Database Setup
-
-Create a Redis database using the [Upstash Console](https://console.upstash.com) or [Upstash CLI](https://github.com/upstash/cli), and add `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` to your `.env` file:
-
-```bash
-UPSTASH_REDIS_REST_URL=your_upstash_redis_url
-UPSTASH_REDIS_REST_TOKEN=your_upstash_redis_token
-```
-
-This file will be used to load environment variables.
-
-### Installation
-
-First, install the necessary libraries using the following command:
-
-```bash
-pip install threading requests upstash-redis python-dotenv
-```
-
-### Code Explanation
-
-We’ll create a multithreaded web scraper that performs HTTP requests on a set of grouped URLs. Each thread will check if the response for a URL is cached in Redis. If the URL has been previously requested, it will retrieve the cached response; otherwise, it will perform a fresh HTTP request, cache the result, and store it for future requests.
-
-### Code
-
-Here’s the complete code:
-
-```python
-import threading
-import requests
-from upstash_redis import Redis
-from dotenv import load_dotenv
-
-# Load environment variables from .env file
-load_dotenv()
-
-# Initialize Redis client
-redis = Redis.from_env()
-
-# Group URLs by thread, with one or two overlapping URLs across groups
-urls_to_scrape_groups = [
- [
- 'https://httpbin.org/delay/1',
- 'https://httpbin.org/delay/4',
- 'https://httpbin.org/delay/2',
- 'https://httpbin.org/delay/5',
- 'https://httpbin.org/delay/3',
- ],
- [
- 'https://httpbin.org/delay/5', # Overlapping URL
- 'https://httpbin.org/delay/6',
- 'https://httpbin.org/delay/7',
- 'https://httpbin.org/delay/2', # Overlapping URL
- 'https://httpbin.org/delay/8',
- ],
- [
- 'https://httpbin.org/delay/3', # Overlapping URL
- 'https://httpbin.org/delay/9',
- 'https://httpbin.org/delay/10',
- 'https://httpbin.org/delay/4', # Overlapping URL
- 'https://httpbin.org/delay/11',
- ],
-]
-
-class Scraper(threading.Thread):
- def __init__(self, urls):
- threading.Thread.__init__(self)
- self.urls = urls
- self.results = {}
-
- def run(self):
- for url in self.urls:
- cache_key = f"url:{url}"
-
- # Attempt to retrieve cached response
- cached_response = redis.get(cache_key)
-
- if cached_response:
- print(f"[CACHE HIT] {self.name} - URL: {url}")
- self.results[url] = cached_response
- continue # Skip to the next URL if cache is found
-
- # If no cache, perform the HTTP request
- print(f"[FETCHING] {self.name} - URL: {url}")
- response = requests.get(url)
- if response.status_code == 200:
- self.results[url] = response.text
- # Store the response in Redis cache
- redis.set(cache_key, response.text)
- else:
- print(f"[ERROR] {self.name} - Failed to retrieve {url}")
- self.results[url] = None
-
-def main():
- threads = []
- for urls in urls_to_scrape_groups:
- scraper = Scraper(urls)
- threads.append(scraper)
- scraper.start()
-
- # Wait for all threads to complete
- for scraper in threads:
- scraper.join()
-
- print("\nScraping results:")
- for scraper in threads:
- for url, result in scraper.results.items():
- print(f"Thread {scraper.name} - URL: {url} - Response Length: {len(result) if result else 'Failed'}")
-
-if __name__ == "__main__":
- main()
-```
-
-### Explanation
-
-1. **Threaded Scraper Class**: The `Scraper` class is a subclass of `threading.Thread`. Each thread takes a list of URLs and iterates over them to retrieve or fetch their responses.
-
-2. **Redis Caching**:
- * Before making an HTTP request, the scraper checks if the response is already in the Redis cache.
- * If a cached response is found, it uses that response instead of making a new request, marked with `[CACHE HIT]` in the logs.
- * If no cached response exists, it fetches the content from the URL, caches the result in Redis, and proceeds.
-
-3. **Overlapping URLs**:
- * Some URLs are intentionally included in multiple groups to demonstrate the cache functionality across threads. Once a URL’s response is cached by one thread, another thread retrieving the same URL will pull it from the cache instead of re-fetching.
-
-4. **Main Function**:
- * The `main` function initiates and starts multiple `Scraper` threads, each handling a group of URLs.
- * It waits for all threads to complete before printing the results.
-
-### Running the Code
-
-Once everything is set up, run the script using:
-
-```bash
-python your_script_name.py
-```
-
-### Sample Output
-
-You will see output similar to this:
-
-```
-[FETCHING] Thread-1 - URL: https://httpbin.org/delay/1
-[FETCHING] Thread-1 - URL: https://httpbin.org/delay/4
-[CACHE HIT] Thread-2 - URL: https://httpbin.org/delay/5
-[FETCHING] Thread-3 - URL: https://httpbin.org/delay/3
-...
-```
-
-### Benefits of Using Redis Cache
-
-Using Redis as a cache reduces the number of duplicate requests, particularly for overlapping URLs. It allows for quick retrieval of previously fetched responses, enhancing performance and reducing load.
-
-# Rate Limiting for Your FastAPI App
-Source: https://upstash.com/docs/redis/tutorials/python_rate_limiting
-
-### Introduction
-
-In this tutorial, we’ll learn how to add rate limiting to a FastAPI application using Upstash Redis. Rate limiting is essential for controlling API usage and with Upstash Redis, you can easily implement rate limiting to protect your API resources.
-
-We’ll set up a simple FastAPI app and apply rate limiting to its endpoints. With Upstash Redis, we’ll configure a fixed window rate limiter that allows a specific number of requests per given time period.
-
-### Environment Setup
-
-First, install FastAPI, the Upstash Redis client, the Upstash rate limiting package, and an ASGI server:
-
-```shell
-pip install fastapi upstash-redis upstash-ratelimit uvicorn[standard]
-```
-
-### Database Setup
-
-Create a Redis database using the [Upstash Console](https://console.upstash.com) or [Upstash CLI](https://github.com/upstash/cli), and export the `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` to your environment:
-
-```shell
-export UPSTASH_REDIS_REST_URL=
-export UPSTASH_REDIS_REST_TOKEN=
-```
-
-You can also use `python-dotenv` to load environment variables from your `.env` file.
-
-### Application Setup
-
-In this example, we will build an API endpoint that is rate-limited to a certain number of requests per time window. If the limit is exceeded (e.g., by making more than 10 requests in 10 seconds), the API will return an HTTP 429 error with the message "Rate limit exceeded. Please try again later."
-
-Create `main.py`:
-
-```python main.py
-from fastapi import FastAPI, HTTPException
-from upstash_ratelimit import Ratelimit, FixedWindow
-from upstash_redis import Redis
-from dotenv import load_dotenv
-import requests
-
-# Load environment variables from .env file
-load_dotenv()
-
-# Initialize the FastAPI app
-app = FastAPI()
-
-# Initialize Redis client
-redis = Redis.from_env()
-
-# Create a rate limiter that allows 10 requests per 10 seconds
-ratelimit = Ratelimit(
- redis=redis,
- limiter=FixedWindow(max_requests=10, window=10), # 10 requests per 10 seconds
- prefix="@upstash/ratelimit"
-)
-
-@app.get("/expensive_calculation")
-def expensive_calculation():
- identifier = "api" # Common identifier for rate limiting all users equally
- response = ratelimit.limit(identifier)
-
- if not response.allowed:
- raise HTTPException(status_code=429, detail="Rate limit exceeded. Please try again later.")
-
- # Placeholder for a resource-intensive operation
- result = do_expensive_calculation()
- return {"message": "Here is your result", "result": result}
-
-# Simulated function for an expensive calculation
-def do_expensive_calculation():
- return "Expensive calculation result"
-
-# Test function to check rate limiting
-def test_rate_limiting():
- url = "http://127.0.0.1:8000/expensive_calculation"
- success_count = 0
- fail_count = 0
-
- # Attempt 15 requests in quick succession
- for i in range(15):
- response = requests.get(url)
-
- if response.status_code == 200:
- success_count += 1
- print(f"Request {i+1}: Success - {response.json()['message']}")
- elif response.status_code == 429:
- fail_count += 1
- print(f"Request {i+1}: Failed - Rate limit exceeded")
-
- # Small delay to avoid flooding
-
- print("\nTest Summary:")
- print(f"Total Successful Requests: {success_count}")
- print(f"Total Failed Requests due to Rate Limit: {fail_count}")
-
-if __name__ == "__main__":
- # Run the FastAPI app in a separate thread or terminal with:
- # uvicorn main:app --reload
-
- # To test rate limiting after the server is running
- test_rate_limiting()
-```
-
-### Running the Application
-
-Run the FastAPI app with Uvicorn:
-
-```shell
-uvicorn main:app --reload
-```
-
-Run the test function to check the rate limiting:
-
-```shell
-python main.py
-```
-
-### Testing Rate Limiting
-
-Here's the output you should see when running the test function:
-
-```
-Request 1: Success - Here is your result
-Request 2: Success - Here is your result
-Request 3: Success - Here is your result
-Request 4: Success - Here is your result
-Request 5: Success - Here is your result
-Request 6: Success - Here is your result
-Request 7: Success - Here is your result
-Request 8: Success - Here is your result
-Request 9: Success - Here is your result
-Request 10: Success - Here is your result
-Request 11: Failed - Rate limit exceeded
-Request 12: Failed - Rate limit exceeded
-Request 13: Failed - Rate limit exceeded
-Request 14: Failed - Rate limit exceeded
-Request 15: Failed - Rate limit exceeded
-
-Test Summary:
-Total Successful Requests: 10
-Total Failed Requests due to Rate Limit: 5
-```
-
-### Code Breakdown
-
-1. **Redis and Rate Limiter Setup**:
- * We initialize a `Redis` client with `Redis.from_env()` using environment variables for configuration.
- * We create a rate limiter using `Ratelimit` with a `FixedWindow` limiter that allows 10 requests per 10 seconds. The `prefix` option is set to organize the Redis keys used by the rate limiter.
-
-2. **Rate Limiting the Endpoint**:
- * For the `/expensive_calculation` endpoint, the rate limiter is applied by calling `ratelimit.limit(identifier)`.
- * The `identifier` variable uniquely identifies this rate limit. You could use user-specific identifiers (like user IDs) to implement per-user limits.
- * If the request exceeds the allowed limit, an HTTP 429 error is returned.
-
-3. **Expensive Calculation Simulation**:
- * The `do_expensive_calculation` function simulates a resource-intensive operation. In real scenarios, this could represent database queries, file processing, or other time-consuming tasks.
-
-### Benefits of Rate Limiting with Redis
-
-Using Redis for rate limiting helps control API usage across multiple instances of your app, making it highly scalable. Redis’s in-memory storage provides fast access to rate-limiting data, ensuring minimal performance impact on your API.
-
-# Build a Real-Time Chat Application with Serverless Redis
-Source: https://upstash.com/docs/redis/tutorials/python_realtime_chat
-
-In this tutorial, we will build a real-time chat application using Flask and SocketIO, leveraging Upstash Redis for efficient message handling. Redis, being a fast, in-memory data store, provides an ideal backbone for real-time messaging systems due to its low latency and support for Pub/Sub messaging patterns.
-
-## Why Upstash Redis?
-
-* **Scalability:** Handles large volumes of messages with minimal latency.
-* **Simplicity:** Easy to set up with minimal configuration.
-* **Cost-Efficiency:** Serverless model reduces operational costs.
-
-***
-
-## **Setup**
-
-### **1. Install the Required Libraries**
-
-Install Flask, Flask-SocketIO, and the Redis library by running:
-
-```bash
-pip install flask flask-socketio redis
-```
-
-### **2. Create a Redis Database**
-
-Create a Redis database using the [Upstash Console](https://console.upstash.com) or [Upstash CLI](https://github.com/upstash/cli).
-
-Create a `.env` file in the root of your project with the following content:
-
-```bash
-UPSTASH_REDIS_HOST=your_upstash_redis_host
-UPSTASH_REDIS_PORT=your_upstash_redis_port
-UPSTASH_REDIS_PASSWORD=your_upstash_redis_password
-```
-
-## **Code**
-
-Now, it's time to implement the chat application. We'll create a Flask server that uses SocketIO for real-time communication. We'll also configure the server to use Upstash Redis as the message queue.
-
-
-We need to use the `rediss://` protocol instead of `redis://` to connect to Redis over TLS. This ensures secure communication between the server and the Redis instance.
-
-
-```python main.py
-from flask import Flask, render_template
-from flask_socketio import SocketIO
-import os
-
-# Initialize Flask app
-app = Flask(__name__)
-app.config["SECRET_KEY"] = os.getenv("SECRET_KEY", os.urandom(24))
-
-# Set up Redis URL with TLS
-redis_password = os.getenv('UPSTASH_REDIS_PASSWORD')
-redis_host = os.getenv('UPSTASH_REDIS_HOST')
-redis_port = int(os.getenv('UPSTASH_REDIS_PORT', 6379))
-redis_url = f"rediss://:{redis_password}@{redis_host}:{redis_port}"
-
-# Initialize SocketIO with Redis message queue
-socketio = SocketIO(app, message_queue=redis_url, cors_allowed_origins="*")
-
-# WebSocket handlers
-@socketio.on("connect")
-def handle_connect():
- print("Client connected.")
-
-@socketio.on("disconnect")
-def handle_disconnect():
- print("Client disconnected.")
-
-@socketio.on("message")
-def handle_message(data):
- """Handle incoming chat messages."""
- print(f"Message received: {data}")
- # Broadcast the message to all connected clients except the sender
- socketio.emit("message", data, include_self=False)
-
-# Serve the chat HTML page
-@app.route("/")
-def index():
- return render_template("chat.html") # Render the chat interface template
-
-if __name__ == "__main__":
- socketio.run(app, debug=True, host="0.0.0.0", port=8000)
-```
-
-### **Code Explanation**
-
-* We initialized a Flask app and set a secret key for session management.
-* We set up the Redis URL with TLS for secure communication.
-* We initialize a SocketIO instance with the Flask app and configure it to use Redis as the message queue.
-* We define WebSocket event handlers for `connect`, `disconnect`, and `message` events.
-* The `handle_message` function broadcasts the received message to all connected clients except the sender.
-* We define a route to serve the chat interface template.
-
-Now let's create a template for the chat interface. We're not going to go into the details of the HTML and CSS, as the focus is on the real-time messaging functionality.
-
-```html chat.html
-
-
-
-
-
- Real-Time Chat
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-***
-
-### **Running the Application**
-
-1. Start the server:
- ```bash
- python app.py
- ```
-2. Open your web browser and go to `http://localhost:8000/`.
-
-You should see the chat interface. You can send and recieve messages in real-time. Just open the same URL in multiple tabs or browsers to simulate multiple users chatting with each other.
-
-
-***
-
-## **Conclusion**
-
-In this tutorial, we built a real-time chat application using Flask, SocketIO, and Upstash Redis. Redis, with its low latency and high throughput, is an ideal choice for real-time messaging systems.
-
-To learn more about Upstash Redis, visit the [Upstash Redis Documentation](/docs/redis).
-
-# Manage Sessions in Python with Serverless Redis
-Source: https://upstash.com/docs/redis/tutorials/python_session
-
-In this tutorial, we’ll see how to implement session management in a FastAPI application using Upstash Redis. We’ll use cookies to store session IDs, while session data is maintained in Redis for its speed and expiration features.
-
-## **What Are Sessions and Cookies?**
-
-* **Session:** A session is a mechanism to store user-specific data (like authentication status) between requests. It allows the server to "remember" users as they interact with the application.
-* **Cookie:** A small piece of data stored in the client’s browser. In this tutorial, we’ll use cookies to store session IDs, which the server uses to fetch session details from Redis.
-
-## **Why Redis?**
-Redis is a great choice for session management because:
-1. **Fast Lookups:** Redis is an in-memory database, ensuring near-instantaneous access to session data.
-2. **Expiration Control:** Built-in expiration functionality allows sessions to automatically expire after a defined timeout.
-
-***
-
-## **Setup**
-
-### **1. Install the Required Libraries**
-
-Install FastAPI, Upstash Redis, and other necessary dependencies:
-```bash
-pip install fastapi upstash-redis uvicorn python-dotenv
-```
-
-### **2. Create a Redis Database**
-
-Create a Redis database using the [Upstash Console](https://console.upstash.com) or [Upstash CLI](https://github.com/upstash/cli).
-
-Create a `.env` file in the root of your project with the following content:
-```bash
-UPSTASH_REDIS_REST_URL=your_upstash_redis_url
-UPSTASH_REDIS_REST_TOKEN=your_upstash_redis_token
-```
-
-## **Code**
-
-Let's implement a simple FastAPI application that handles login, profile access, and logout using Redis for session management. We use sliding expiration by updating the session expiration time on every request. If a session is inactive for 15 minutes (900 seconds), it will automatically expire.
-
-```python main.py
-from fastapi import FastAPI, Response, Cookie, HTTPException
-from pydantic import BaseModel
-from upstash_redis import Redis
-from dotenv import load_dotenv
-import uuid
-
-# Load environment variables
-load_dotenv()
-redis = Redis.from_env()
-
-app = FastAPI()
-
-SESSION_TIMEOUT_SECONDS = 900 # 15 minutes
-
-# Define the request body model for login
-class LoginRequest(BaseModel):
- username: str
-
-@app.post("/login/")
-async def login(request: LoginRequest, response: Response):
- session_id = str(uuid.uuid4())
- redis.hset(f"session:{session_id}", values={"user": request.username, "status": "active"})
- redis.expire(f"session:{session_id}", SESSION_TIMEOUT_SECONDS)
-
- response.set_cookie(key="session_id", value=session_id, httponly=True)
- return {"message": "Logged in successfully", "session_id": session_id}
-
-@app.get("/profile/")
-async def get_profile(session_id: str = Cookie(None)):
- if not session_id:
- raise HTTPException(status_code=403, detail="No session cookie found")
-
- session_data = redis.hgetall(f"session:{session_id}")
- if not session_data:
- response = Response()
- response.delete_cookie(key="session_id") # Clear the expired cookie
- raise HTTPException(status_code=404, detail="Session expired")
-
- # Update the session expiration time (sliding expiration)
- redis.expire(f"session:{session_id}", SESSION_TIMEOUT_SECONDS)
-
- return {"session_id": session_id, "session_data": session_data}
-
-@app.post("/logout/")
-async def logout(response: Response, session_id: str = Cookie(None)):
- if session_id:
- redis.delete(f"session:{session_id}")
- response.delete_cookie(key="session_id")
- return {"message": "Logged out successfully"}
-```
-
-Let's test the implementation using the following script:
-
-```python test_script.py
-import requests
-
-base_url = "http://127.0.0.1:8000"
-
-# Test login
-response = requests.post(f"{base_url}/login/", json={"username": "abdullah"})
-print("Login Response:", response.json())
-
-# In the browser, you don't need to set cookies manually. The browser will handle it automatically.
-session_cookie = response.cookies.get("session_id")
-
-# Test profile
-profile_response = requests.get(f"{base_url}/profile/", cookies={"session_id": session_cookie})
-print("Access Profile Response:", profile_response.json())
-
-# Test logout
-logout_response = requests.post(f"{base_url}/logout/", cookies={"session_id": session_cookie})
-print("Logout Response:", logout_response.json())
-
-# Test profile after logout
-profile_after_logout_response = requests.get(f"{base_url}/profile/", cookies={"session_id": session_cookie})
-print("Access Profile After Logout Response:", profile_after_logout_response.text)
-```
-
-***
-
-### **Code Explanation**
-
-1. **`/login/` Endpoint:**
- * Generates a unique session ID using `uuid.uuid4()`.
- * Stores the session data in Redis using the session ID as the key.
- * Sets a cookie named `session_id` with the generated session ID.
- * Returns a success message along with the session ID.
-
-2. **`/profile/` Endpoint:**
- * Retrieves the session ID from the cookie.
- * Fetches the session data from Redis using the session ID.
- * Updates the session expiration time.
- * Returns the session ID and session data.
-
-3. **`/logout/` Endpoint:**
- * Deletes the session data from Redis using the session ID.
- * Clears the `session_id` cookie.
-
-***
-
-### **Run the Application**
-
-1. Start the FastAPI server:
- ```bash
- uvicorn main:app --reload
- ```
-
-2. Run the test script:
- ```bash
- python test_script.py
- ```
-
-Here's what you should expect:
-```plaintext
-Login Response: {'message': 'Logged in successfully', 'session_id': '68223c50-ede4-48eb-9d26-4a4dd735c10d'}
-Access Profile Response: {'session_id': '68223c50-ede4-48eb-9d26-4a4dd735c10d', 'session_data': {'user': 'abdullah', 'status': 'active'}}
-Logout Response: {'message': 'Logged out successfully'}
-Access Profile After Logout Response: {"detail":"Session not found or expired"}
-```
-
-***
-
-## **Conclusion**
-
-By combining FastAPI, cookies, and Upstash Redis, we’ve created a reliable session management system. With Redis’s speed and built-in expiration features, this approach ensures secure and efficient handling of user sessions.
-
-To learn more about Upstash Redis, visit the [Upstash Redis Documentation](/docs/redis).
-
-# Building a URL Shortener with Redis
-Source: https://upstash.com/docs/redis/tutorials/python_url_shortener
-
-### Introduction
-
-In this tutorial, we’ll build a simple URL shortener using Redis and Python. The short URL service will generate a random short code for each URL, store it in Redis, and allow users to retrieve the original URL using the short code. We’ll also implement an expiration time for each shortened URL, making it expire after a specified period.
-
-### Environment Setup
-
-First, install the necessary dependencies, including Upstash Redis and `python-dotenv` for environment variables:
-
-```shell
-pip install upstash-redis
-```
-
-### Database Setup
-
-Create a Redis database using the [Upstash Console](https://console.upstash.com) or [Upstash CLI](https://github.com/upstash/cli), and export the `UPSTASH_REDIS_REST_URL` and `UPSTASH_REST_TOKEN` to your environment:
-
-```shell
-export UPSTASH_REDIS_REST_URL=
-export UPSTASH_REDIS_REST_TOKEN=
-```
-
-You can also use `python-dotenv` to load environment variables from a `.env` file:
-
-```text .env
-UPSTASH_REDIS_REST_URL=
-UPSTASH_REDIS_REST_TOKEN=
-```
-
-### Application Setup
-
-In this example, we will build a URL shortener where each short URL will be stored in Redis with an expiration time.
-
-Create `url_shortener.py`:
-
-```py url_shortener.py
-import string
-import random
-from upstash_redis import Redis
-from dotenv import load_dotenv
-import os
-
-# Load environment variables
-load_dotenv()
-
-# Redis connection
-redis = Redis.from_env()
-
-# Characters to generate the short URL from
-CHARS = string.ascii_letters + string.digits
-BASE_URL = "https://short.url/"
-
-# Function to generate a random string for the short URL
-def generate_short_code(length=6):
- return ''.join(random.choices(CHARS, k=length))
-
-# Function to shorten the URL with an expiration time
-def shorten_url(url, expiration=3600):
- # Generate a random short code
- short_code = generate_short_code()
- # Save the short code in Redis
- redis.set(short_code, url, ex=expiration)
- return BASE_URL + short_code
-
-# Function to get the original URL from the short URL
-def get_original_url(short_code):
- return redis.get(short_code)
-
-# Example usage
-if __name__ == "__main__":
- original_url = "https://example.com/my-very-long-url"
-
- # Shorten the URL
- short_url = shorten_url(original_url, expiration=600)
- print(f"Shortened URL: {short_url}")
-
- # Get the original URL
- original_url = get_original_url(short_url.split("/")[-1])
-
- if original_url:
- print(f"Original URL: {original_url}")
- else:
- print("Short URL expired or not found")
-```
-
-### Running the Application
-
-Simply run the Python script:
-
-```shell
-python url_shortener.py
-```
-
-This script will shorten a long URL, store the mapping in Redis, and print the shortened URL. It will then attempt to retrieve the original URL using the short code.
-
-Here is an example output:
-
-```shell
-Shortened URL: https://short.url/0lSLFI
-Original URL: https://example.com/my-very-long-url
-```
-
-To monitor your data in Redis, you can use the [Upstash Console](https://console.upstash.com) and check out the Data Browser tab.
-
-### How to Use the URL Shortener for Web Applications
-
-1. Extract the short code from the shortened URL (e.g., `0lSLFI`).
-
-2. Look up the original URL in Redis using that code.
-
-3. Redirect the user to the original URL.
-
-### Code Breakdown
-
-1. **Random Short Code Generation**: The `generate_short_code` function creates a random string of characters (letters and digits) that will serve as the short code for the URL.
-
-2. **Storing in Redis**: The `shorten_url` function takes the original URL and stores it in Redis using the randomly generated short code as the key. The `ex` parameter sets an expiration time (in seconds) for how long the shortened URL will be valid.
-
-3. **Retrieving the Original URL**: The `get_original_url` function takes the short code and looks it up in Redis to retrieve the original URL. If the short code doesn't exist (due to expiration or other reasons), it returns `None`.
-
-4. **Expiration Handling**: If the short code has expired, Redis automatically removes the entry, and the script will print a message indicating that the URL has expired or cannot be found.
-
-# Serverless Python API with Redis
-Source: https://upstash.com/docs/redis/tutorials/pythonapi
-
-
- You can find the project source code on GitHub.
-
-
-This tutorial shows how to build a serverless API for Page View Counter with
-Python and Redis. The API will the count page views and show it in JSON format.
-
-### Prerequisites
-
-* Complete all steps in [Getting started with the AWS CDK](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html)
-
-### Project Setup
-
-Create and navigate to a directory named `counter-cdk`. CDK CLI uses this directory name to name things in your CDK code, so if you decide to use a different name, don't forget to make the appropriate changes when applying this tutorial.
-
-```shell
-mkdir counter-cdk && cd counter-cdk
-```
-
-Initialize a new CDK project.
-
-```shell
-cdk init app --language typescript
-```
-
-### Counter Function Setup
-
-Create a folder named `api` under `lib`
-
-```shell
-mkdir lib/api
-```
-
-Create `/lib/api/requirements.txt`
-
-```txt /lib/api/requirements.txt
-upstash-redis
-```
-
-Create `/lib/api/index.py`
-
-```py /lib/api/index.py
-from upstash_redis import Redis
-
-redis = Redis.from_env()
-
-def handler(event, context):
- count = redis.incr('counter')
- return {
- 'statusCode': 200,
- 'body': f'Counter: {count}'
- }
-```
-
-### Counter Stack Setup
-
-Update `/lib/counter-cdk-stack.ts`
-
-```ts /lib/counter-cdk-stack.ts
-import * as cdk from 'aws-cdk-lib';
-import { Construct } from 'constructs';
-import * as lambda from 'aws-cdk-lib/aws-lambda';
-import * as path from 'path';
-
-export class CounterCdkStack extends cdk.Stack {
- constructor(scope: Construct, id: string, props?: cdk.StackProps) {
- super(scope, id, props);
-
- const counterFunction = new lambda.Function(this, 'CounterFunction', {
- code: lambda.Code.fromAsset(path.join(__dirname, 'api'), {
- bundling: {
- image: lambda.Runtime.PYTHON_3_9.bundlingImage,
- command: [
- 'bash', '-c',
- 'pip install -r requirements.txt -t /asset-output && cp -au . /asset-output'
- ],
- },
- }),
- runtime: lambda.Runtime.PYTHON_3_9,
- handler: 'index.handler',
- environment: {
- UPSTASH_REDIS_REST_URL: process.env.UPSTASH_REDIS_REST_URL || '',
- UPSTASH_REDIS_REST_TOKEN: process.env.UPSTASH_REDIS_REST_TOKEN || '',
- },
- });
-
- const counterFunctionUrl = counterFunction.addFunctionUrl({
- authType: lambda.FunctionUrlAuthType.NONE,
- });
-
- new cdk.CfnOutput(this, "counterFunctionUrlOutput", {
- value: counterFunctionUrl.url,
- })
- }
-}
-```
-
-### Database Setup
-
-Create a Redis database using [Upstash Console](https://console.upstash.com) or [Upstash CLI](https://github.com/upstash/cli) and export `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` to your environment.
-
-```shell
-export UPSTASH_REDIS_REST_URL=
-export UPSTASH_REDIS_REST_TOKEN=
-```
-
-### Deploy
-
-Run in the top folder:
-
-```shell
-cdk synth
-cdk bootstrap
-cdk deploy
-```
-
-Visit the output url.
-
-# AWS Lambda Rate Limiting with Serverless Redis
-Source: https://upstash.com/docs/redis/tutorials/rate-limiting
-
-In this tutorial, we will deploy a AWS Lambda function ratelimited based on the client's IP address using `@upstash/ratelimit` and Serverless Framework.
-
-### Prerequisites
-
-1. Install the Serverless Framework with `npm i serverless -g`
-
-### Project Setup
-
-Create a Serverless Framework application with the following options:
-
-```shell
-➜ tutorials > ✗ serverless
-Serverless ϟ Framework
-
-Welcome to Serverless Framework V.4
-
-Create a new project by selecting a Template to generate scaffolding for a specific use-case.
-
-✔ Select A Template: · AWS / Node.js / HTTP API
-
-✔ Name Your Project: · ratelimit-serverless
-
-✔ Template Downloaded
-
-✔ Create Or Select An Existing App: · Create A New App
-
-✔ Name Your New App: · ratelimit-serverless
-
-Your new Service "ratelimit-serverless" is ready. Here are next steps:
-
-• Open Service Directory: cd ratelimit-serverless
-• Install Dependencies: npm install (or use another package manager)
-• Deploy Your Service: serverless deploy
-```
-
-```shell
-cd ratelimit-serverless
-```
-
-Create `package.json` with `@upstash/ratelimit` as a dependency:
-
-```json package.json
-{
- "dependencies": {
- "@upstash/ratelimit": "latest",
- "@upstash/redis": "latest"
- }
- }
-```
-
-Install the dependencies:
-
-```shell
-npm install
-```
-
-### Database Setup
-
-Create a Redis database using [Upstash Console](https://console.upstash.com) or [Upstash CLI](https://github.com/upstash/cli) and copy the `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` into your `.env` file.
-
-```shell .env
-UPSTASH_REDIS_REST_URL=
-UPSTASH_REDIS_REST_TOKEN=
-```
-
-### Ratelimited Function Setup
-
-Update `handler.js`:
-
-```js handler.js
-const { Ratelimit } = require("@upstash/ratelimit");
-const { Redis } = require("@upstash/redis");
-
-const ratelimit = new Ratelimit({
- redis: Redis.fromEnv(),
- limiter: Ratelimit.slidingWindow(10, "10 s"),
- prefix: "@upstash/ratelimit",
- analytics: true,
-});
-
-exports.ratelimit = async (event) => {
- const identifier = event.requestContext.http.sourceIP;
- const { success, limit, remaining, pending } = await ratelimit.limit(
- identifier
- );
- const response = {
- success: success,
- limit: limit,
- remaining: remaining,
- };
-
- // pending is a promise for handling the analytics submission
- await pending;
-
- if (!success) {
- return {
- statusCode: 429,
- body: JSON.stringify(response),
- };
- }
- return {
- statusCode: 200,
- body: JSON.stringify(response),
- };
-};
-```
-
-Update `serverless.yml` to pass the environment variables:
-
-```yaml serverless.yml
-service: ratelimit-serverless
-
-provider:
- name: aws
- runtime: nodejs20.x
- environment:
- UPSTASH_REDIS_REST_URL: ${env:UPSTASH_REDIS_REST_URL}
- UPSTASH_REDIS_REST_TOKEN: ${env:UPSTASH_REDIS_REST_TOKEN}
-
-functions:
- ratelimit:
- handler: handler.ratelimit
- events:
- - httpApi:
- path: /
- method: get
-```
-
-### Developing
-Run the following command to start your dev session.
-```shell
-serverless dev
-```
-
-### Deployment
-Run the following command to deploy your service.
-```shell
-serverless deploy
-```
-
-Visit the output url.
-
-# Redis Queue: From FIFO Lists to a Job Queue
-Source: https://upstash.com/docs/redis/tutorials/redis_queue
-
-## What is a queue?
-
-A queue holds items in order: the first item added is the first one taken out
-(FIFO, or First In First Out), like a line of people waiting for service. In
-software, a queue lets one part of your system hand work to another so the producer
-doesn't have to wait for the work to finish.
-
-Redis works well for queues. Its [list](/docs/redis/sdks/ts/commands/list/lpush) and
-[sorted set](/docs/redis/sdks/ts/commands/zset/zadd) types map onto queue operations,
- and operations are atomic, so multiple producers and
-consumers can share a queue without stepping on each other. With
-[Upstash Redis](https://upstash.com/) you reach it over HTTP, so the same queue
-works from a serverless function or a long-running worker.
-
-This tutorial starts with a plain FIFO queue and builds up to a job queue that
-supports delays, priorities, and retries.
-
-### Database Setup
-
-Create a Redis database using the [Upstash Console](https://console.upstash.com),
-and add `UPSTASH_REDIS_REST_URL` and
-`UPSTASH_REDIS_REST_TOKEN` to your `.env` file:
-
-```bash
-UPSTASH_REDIS_REST_URL=your_upstash_redis_url
-UPSTASH_REDIS_REST_TOKEN=your_upstash_redis_token
-```
-
-### Installation
-
-```bash
-npm install @upstash/redis
-```
-
-Then create a client. `Redis.fromEnv()` reads the two variables above
-automatically:
-
-```ts
-import { Redis } from "@upstash/redis";
-
-const redis = Redis.fromEnv();
-```
-
-## A basic FIFO queue
-
-A Redis list is all you need for a simple queue. You push items onto one end and
-pop them off the other. The convention is to enqueue on the left with `LPUSH` and
-dequeue on the right with `RPOP`, so the oldest item is always the next one out.
-
-```ts
-// Producer — add tasks to the queue
-await redis.lpush("tasks", "send-welcome-email:alice");
-await redis.lpush("tasks", "send-welcome-email:bob");
-
-// Consumer — take the next task (returns "send-welcome-email:alice")
-const task = await redis.rpop("tasks");
-```
-
-| Operation | Command | Description |
-| --- | --- | --- |
-| Enqueue | `LPUSH queue task` | Add a task to the head of the list |
-| Dequeue | `RPOP queue` | Remove and return the oldest task |
-| Peek | `LRANGE queue -1 -1` | Look at the next task without removing it |
-| Length | `LLEN queue` | How many tasks are waiting |
-
-To peek at the next task without removing it, read the tail with `LRANGE`. This is
-useful for monitoring or for deciding whether to process:
-
-```ts
-const [next] = await redis.lrange("tasks", -1, -1);
-```
-
-This is enough for fire-and-forget work, but it has a flaw: if your consumer pops a
-task and then crashes before finishing it, that task is lost. The next sections fix
-that.
-
-## A reliable queue
-
-To avoid losing tasks, don't fully remove a task until it has been processed.
-Redis's `LMOVE` command (the modern replacement for `RPOPLPUSH`) atomically moves a
-task from the main queue to a "processing" list in a single step:
-
-```ts
-// Atomically move the next task from "tasks" to "processing"
-const task = await redis.lmove("tasks", "processing", "right", "left");
-
-if (task) {
- try {
- await handle(task);
- // Success: remove it from the processing list
- await redis.lrem("processing", 1, task);
- } catch (err) {
- // Failure: the task is still safe in "processing".
- // A recovery job can move stuck tasks back to "tasks" later.
- console.error("task failed, left in processing list", err);
- }
-}
-```
-
-Because the task stays in `processing` while it is being worked on, a crashed
-consumer doesn't cause data loss. A periodic recovery job can scan `processing` for
-tasks that have been stuck too long and move them back to `tasks`.
-
-## Consuming the queue
-
-A consumer reads from the queue in a loop. With a TCP Redis client you might use a
-blocking pop (`BRPOP`) to sleep until a task arrives, but `@upstash/redis` talks to
-Redis over HTTP, so it doesn't expose blocking commands. Instead, poll with `RPOP`
-and back off with a short sleep when the queue is empty:
-
-```ts
-async function consume() {
- while (true) {
- const task = await redis.rpop("tasks");
- if (task === null) {
- await new Promise((r) => setTimeout(r, 1000)); // queue empty, wait before retrying
- continue;
- }
- await handle(task);
- }
-}
-```
-
-
-Polling works well for a long-running worker, but in short-lived serverless
-functions you usually don't want to run a loop at all. For event-driven delivery,
-consider [Upstash QStash](/docs/qstash/overall/getstarted), which pushes messages to your
-HTTP endpoint instead of making you poll. We come back to this
-[at the end](#when-to-reach-for-qstash-instead).
-
-
-## Going further: a delayed, prioritized job queue with retries
-
-Plain lists are FIFO and immediate. Real job systems usually need a few more things:
-
-* Delays: run a job later, like a reminder in an hour, instead of right away.
-* Priorities: let urgent jobs go ahead of routine ones.
-* Retries: when a job keeps failing, retry it a few times and then set it aside
- rather than retrying forever.
-
-A sorted set handles all three. It keeps members ordered by a numeric score. If the
-score is the time a job becomes due, the member with the lowest score is always the
-next job to run, and jobs scheduled for the future sit untouched until their time
-comes.
-
-### The schedule: score = "run at" timestamp
-
-```ts
-type Job = { id: string; type: string; payload: unknown };
-type ScheduledJob = Job & { attempts?: number };
-
-// Enqueue a job to run after `delayMs` (0 = immediately).
-// `priority` shaves milliseconds off the score so higher-priority jobs
-// of the same due-time sort first.
-async function enqueue(job: ScheduledJob, delayMs = 0, priority = 0) {
- const runAt = Date.now() + delayMs - priority;
- await redis.zadd("jobs:scheduled", { score: runAt, member: JSON.stringify(job) });
-}
-
-// Routine job, runs now
-await enqueue({ id: "1", type: "email", payload: { to: "alice@example.com" } });
-
-// Reminder, runs in one hour
-await enqueue({ id: "2", type: "reminder", payload: { userId: 42 } }, 60 * 60 * 1000);
-
-// Urgent job — higher priority means it sorts ahead of same-time jobs
-await enqueue({ id: "3", type: "alert", payload: { level: "critical" } }, 0, 10_000);
-```
-
-### Claiming due jobs atomically
-
-A worker should only pick up jobs whose `runAt` is in the past, and when several
-workers run at once, no two should claim the same job. A Lua script handles this: it
-reads the due jobs and removes them in one step, so there is no race between the
-read and the remove.
-
-```ts
-// Returns up to `limit` jobs that are due, and removes them from the schedule
-// in the same atomic operation. `unpack` is available in Redis's Lua (5.1); on
-// newer Lua you'd use `table.unpack`. Keep `limit` modest so the unpacked
-// argument list stays well within Lua's stack limits.
-const CLAIM = `
- local due = redis.call('ZRANGEBYSCORE', KEYS[1], '-inf', ARGV[1], 'LIMIT', 0, ARGV[2])
- if #due > 0 then
- redis.call('ZREM', KEYS[1], unpack(due))
- end
- return due
-`;
-
-async function claimDueJobs(limit = 10): Promise {
- // The members were stored as JSON, and @upstash/redis deserializes JSON in
- // command results by default, so `eval` hands back parsed objects, not strings.
- return (await redis.eval(
- CLAIM,
- ["jobs:scheduled"],
- [Date.now().toString(), limit.toString()],
- )) as ScheduledJob[];
-}
-```
-
-### The worker: process, retry, or dead-letter
-
-When a job fails, we re-schedule it with an exponential back-off delay and increment
-its attempt count. Once it runs out of retries, it goes to a dead-letter queue: a
-separate list of jobs to look at later, rather than retrying them again.
-
-```ts
-const MAX_ATTEMPTS = 3;
-
-async function processJob(job: ScheduledJob) {
- const attempts = job.attempts ?? 0;
-
- try {
- await handle(job); // your business logic
- } catch (err) {
- if (attempts + 1 >= MAX_ATTEMPTS) {
- // Out of retries: set it aside for inspection. @upstash/redis serializes
- // the object to JSON for us, so we pass it directly.
- await redis.lpush("jobs:dead-letter", {
- job,
- error: String(err),
- failedAt: Date.now(),
- });
- } else {
- // Re-schedule with exponential back-off: 1s, 2s, 4s, ...
- const backoffMs = 1000 * 2 ** attempts;
- await enqueue({ ...job, attempts: attempts + 1 }, backoffMs);
- }
- }
-}
-
-// Worker loop
-async function runWorker() {
- while (true) {
- const jobs = await claimDueJobs();
- if (jobs.length === 0) {
- await new Promise((r) => setTimeout(r, 1000)); // nothing due, wait before retrying
- continue;
- }
- await Promise.all(jobs.map(processJob));
- }
-}
-
-runWorker();
-```
-
-This gives you a job queue that schedules work for later, runs urgent jobs first,
-retries failures with back-off, and moves jobs that keep failing to a dead-letter
-queue, all on a single sorted set.
-
-### Inspecting the dead-letter queue
-
-Failed jobs live in a list, so checking on them is straightforward. You can wire
-this into a dashboard or an alert:
-
-```ts
-type DeadLetter = { job: ScheduledJob; error: string; failedAt: number };
-
-const failedCount = await redis.llen("jobs:dead-letter");
-// lrange also deserializes JSON, so these come back as objects, not strings.
-const failed = await redis.lrange("jobs:dead-letter", 0, 9); // 10 most recent
-```
-
-## Wrapping up
-
-Redis covers queues at several levels, all in the same database:
-
-* Lists (`LPUSH` / `RPOP`) for simple FIFO queues, plus `LMOVE` for reliability and
- `BRPOP` for blocking consumers.
-* Sorted sets (`ZADD` / `ZRANGEBYSCORE`) for delayed and prioritized scheduling.
-* Lua scripts (`EVAL`) to claim work atomically across many concurrent workers.
-
-Because Upstash Redis is serverless and accessed over HTTP, you can produce jobs
-from serverless or edge functions and consume them from wherever your workers run.
-
-### When to reach for QStash instead
-
-The job queue above assumes you have a worker process polling Redis. In a fully
-serverless setup you often don't want to keep a worker running. That is where
-[Upstash QStash](/docs/qstash/overall/getstarted) comes in: instead of pulling jobs from
-a list, you publish a message and QStash pushes it to your HTTP endpoint, with
-retries, scheduling, and delays handled for you.
-
-```ts
-import { Client } from "@upstash/qstash";
-
-const qstash = new Client({ token: process.env.QSTASH_TOKEN! });
-
-// Deliver this job to your endpoint after a one-hour delay
-await qstash.publishJSON({
- url: "https://your-app.com/api/jobs/reminder",
- body: { userId: 42 },
- delay: 60 * 60, // seconds
-});
-```
-
-A rough rule of thumb: use the Redis patterns in this guide when you control the
-consumer and want full control over how jobs are stored and claimed. Reach for
-QStash when you'd rather not run a worker at all and just want jobs delivered to an
-endpoint. See the [QStash documentation](/docs/qstash/overall/getstarted) to get started.
-
-# Serverless Redisson
-Source: https://upstash.com/docs/redis/tutorials/redisson
-
-This tutorial shows how to use Upstash with Redisson client.
-
-See [code](https://github.com/upstash/examples/tree/master/examples/redisson)
-
-### `1` Create a Maven Project
-
-Create a maven project and copy the following dependency to your pom.xml
-
-```xml
-
- org.redisson
- redisson
- 3.15.4
-
-```
-
-### `2` Create a Redis (Upstash) Database
-
-Create a database as [getting started](../overall/getstarted)
-
-### `3` Code
-
-Create your java file and replace Redis password and URL below. If you enabled
-TLS, the endpoint should start with `rediss`
-
-```typescript
-public class Main {
-
- public static void main(String[] args) {
- Config config = new Config();
- config.useSingleServer().setPassword("YOUR_PASSWORD")
- // use "rediss://" for SSL connection
- .setAddress("YOUR_ENDPOINT");
- RedissonClient redisson = Redisson.create(config);
- RMap map = redisson.getMap("map");
- map.put("foo", "bar");
- System.out.println(map.get("foo"));
- }
-}
-```
-
-# Roadmap Voting App with Serverless Redis
-Source: https://upstash.com/docs/redis/tutorials/roadmapvotingapp
-
-
- We have developed an advanced version of Roadmap Voting App where users should
- log in to request or vote for a new feature. See it live version in [Upstash
- Roadmap](https://roadmap.upstash.com). See [the blog
- post](https://blog.upstash.com/roadmap-application) to learn about it. The
- below example allows users to request features anonymously.
-
-
-In this tutorial we will write a single page application which uses Redis as
-state store in a Next.js application.
-
-The example is a basic roadmap voting application where users enter and vote for
-feature requests. You can check the complete application in
-[Upstash Roadmap page](https://roadmap.upstash.com)
-
-### Deploy Yourself
-
-You can check the source code of the complete application
-[here](https://github.com/upstash/serverless-examples/tree/master/roadmap-voting-app).
-Thanks to [Upstash&Vercel integration](https://vercel.com/integrations/upstash),
-you can deploy the application yourself with zero cost/code by clicking below:
-[![Deploy with Vercel]()](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fupstash%2Fserverless-tutorials%2Ftree%2Fmaster%2Froadmap-voting-app&env=LOGO&envDescription=Enter%20URL%20for%20your%20project%2Fcompany%20logo&envLink=https%3A%2F%2Fdocs.upstash.com%2Fdocs%2Ftutorials%2Froadmap_voting_app&project-name=roadmap-voting&repo-name=roadmap-voting&demo-title=Roadmap%20Voting&demo-description=Roadmap%20Voting%20Page%20for%20Your%20Project&demo-url=https%3A%2F%2Froadmap.upstash.com&integration-ids=oac_V3R1GIpkoJorr6fqyiwdhl17)
-
-### Create Next.js Project
-
-We will use Next.js as web framework. So let's create a next.js app and install
-the redis client first.
-
-`npx create-next-app nextjs-with-redis`
-
-`npm install ioredis`
-
-### index.js
-
-Our application will be a single page. We will list the features with their
-order of votes. There will be 3 actions available for the page user:
-
-* The user will suggest a new feature.
-* The user will vote up an existing feature.
-* The user will enter their email to be notified of a release of any feature.
-
-The below are the parts that handles all those. If you want to check the full
-page see
-[here](https://github.com/upstash/serverless-tutorials/blob/master/roadmap-voting-app/pages/index.js)
-
-```javascript
-import Head from 'next/head'
-import { ToastContainer, toast } from 'react-toastify';
-import * as React from "react";
-
-class Home extends React.Component {
- ...
- refreshData() {
- fetch("api/list")
- .then(res => res.json())
- .then(
- (result) => {
- this.setState({
- isLoaded: true,
- items: result.body
- });
- this.inputNewFeature.current.value = "";
- },
- (error) => {
- this.setState({
- isLoaded: true,
- error
- });
- }
- )
- }
-
- vote(event, title) {
- const requestOptions = {
- method: 'POST',
- headers: {'Content-Type': 'application/json'},
- body: JSON.stringify({"title": title})
- };
- console.log(requestOptions);
- fetch('api/vote', requestOptions)
- .then(response => response.json()).then(data => {
- console.log(data)
- if(data.error) {
- toast.error(data.error, {hideProgressBar: true, autoClose: 3000});
- } else {
- this.refreshData()
- }
- })
- }
-
- handleNewFeature(event) {
- const requestOptions = {
- method: 'POST',
- headers: {'Content-Type': 'application/json'},
- body: JSON.stringify({"title": this.inputNewFeature.current.value})
- };
- fetch('api/create', requestOptions)
- .then(response => response.json()).then(data => {
- if(data.error) {
- toast.error(data.error, {hideProgressBar: true, autoClose: 5000});
- } else {
- toast.info("Your feature has been added to the list.", {hideProgressBar: true, autoClose: 3000});
- this.refreshData()
- }
- });
- event.preventDefault();
- }
-
- handleNewEmail(event) {
- const requestOptions = {
- method: 'POST',
- headers: {'Content-Type': 'application/json'},
- body: JSON.stringify({"email": this.inputEmail.current.value})
- };
- console.log(requestOptions);
- fetch('api/addemail', requestOptions)
- .then(response => response.json()).then(data => {
- if(data.error) {
- toast.error(data.error, {hideProgressBar: true, autoClose: 3000});
- } else {
- toast.info("Your email has been added to the list.", {hideProgressBar: true, autoClose: 3000});
- this.refreshData()
- }
- });
- event.preventDefault();
- }
-}
-export default Home;
-```
-
-### APIs
-
-With Next.js, you can write server-side APIs within your project. We will have 4
-apis:
-
-* list features
-* vote a feature
-* add a new feature
-* add email
-
-Now let's examine these API implementations:
-
-#### list.js
-
-The list API connects to the Redis and fetches feature requests ordered by their
-scores (votes) from the Sorted Set `roadmap`.
-
-
- This example uses ioredis, you can copy the connection string from the
- **Node** tab in the console.
-
-
-```javascript
-import { fixUrl } from "./utils";
-import Redis from "ioredis";
-
-module.exports = async (req, res) => {
- let redis = new Redis(fixUrl(process.env.REDIS_URL));
- let n = await redis.zrevrange("roadmap", 0, 100, "WITHSCORES");
- let result = [];
- for (let i = 0; i < n.length - 1; i += 2) {
- let item = {};
- item["title"] = n[i];
- item["score"] = n[i + 1];
- result.push(item);
- }
-
- redis.quit();
-
- res.json({
- body: result,
- });
-};
-```
-
-#### create.js
-
-This API connects to the Redis server and add a new element to the sorted set
-(roadmap) . We use "NX" flag together with ZADD, so a user will not be able to
-overwrite an existing feature request with the same title.
-
-```javascript
-import Redis from "ioredis";
-import { fixUrl } from "./utils";
-
-module.exports = async (req, res) => {
- let redis = new Redis(fixUrl(process.env.REDIS_URL));
- const body = req.body;
- const title = body["title"];
- if (!title) {
- redis.quit();
- res.json({
- error: "Feature can not be empty",
- });
- } else if (title.length < 70) {
- await redis.zadd("roadmap", "NX", 1, title);
- redis.quit();
- res.json({
- body: "success",
- });
- } else {
- redis.quit();
- res.json({
- error: "Max 70 characters please.",
- });
- }
-};
-```
-
-#### vote.js
-
-This API updates (increments) the score of the selected feature request. It also
-keeps the IP addresses of the user to prevent multiple votes on the same feature
-request.
-
-```javascript
-import Redis from "ioredis";
-import { fixUrl } from "./utils";
-
-module.exports = async (req, res) => {
- let redis = new Redis(fixUrl(process.env.REDIS_URL));
- const body = req.body;
- const title = body["title"];
- let ip = req.headers["x-forwarded-for"] || req.headers["Remote_Addr"] || "NA";
- let c = ip === "NA" ? 1 : await redis.sadd("s:" + title, ip);
- if (c === 0) {
- redis.quit();
- res.json({
- error: "You can not vote an item multiple times",
- });
- } else {
- let v = await redis.zincrby("roadmap", 1, title);
- redis.quit();
- res.json({
- body: v,
- });
- }
-};
-```
-
-#### addemail.js
-
-This API simply adds the user's email to the Redis Set. As the Set already
-ensures the uniqueness, we only need to check if the input is a valid email.
-
-```javascript
-import Redis from "ioredis";
-import { fixUrl } from "./utils";
-
-module.exports = async (req, res) => {
- let redis = new Redis(fixUrl(process.env.REDIS_URL));
-
- const body = req.body;
- const email = body["email"];
-
- redis.on("error", function (err) {
- throw err;
- });
-
- if (email && validateEmail(email)) {
- await redis.sadd("emails", email);
- redis.quit();
- res.json({
- body: "success",
- });
- } else {
- redis.quit();
- res.json({
- error: "Invalid email",
- });
- }
-};
-
-function validateEmail(email) {
- const re =
- /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
- return re.test(String(email).toLowerCase());
-}
-```
-
-#### css and utils
-
-[index.css](https://github.com/upstash/serverless-tutorials/blob/master/roadmap-voting-app/pages/index.css)
-helps page to look good and
-[utils.js](https://github.com/upstash/serverless-tutorials/blob/master/roadmap-voting-app/pages/api/utils.js)
-fixes common mistakes on Redis URL.
-
-### Notes:
-
-* If you deploy this application with Vercel; Vercel runs AWS Lambda functions
- to back the API implementations. For best performance choose the the same
- region for both Vercel functions and Upstash cluster.
-* You can access your database details via
- [Upstash Console](https://console.upstash.com)
-
-# Serverless API with Java and Redis
-Source: https://upstash.com/docs/redis/tutorials/serverless_java_redis
-
-In this tutorial, we will build a stateful serverless API using Java and Redis
-on AWS Lambda. The API will simply count the page views and return it as HTTP
-response.
-
-### Prerequisites
-
-1. Install the Serverless Framework installed with an AWS account set up.
-```shell
-npm i serverless@3.39.0 -g
-```
-2. Install JDK and not Java JRE. Set your JAVA_HOME.
-3. Install Apache Maven.
-4. Create a free Serverless Redis database from
- [Upstash](https://console.upstash.com) as described
- [here](/docs/redis/overall/getstarted).
-
-### Project Setup
-
-Create the project:
-
-```shell
-serverless create --template aws-java-maven --name counter-api -p aws-java-counter-api
-```
-```shell
-cd aws-java-counter-api
-```
-
-Add `jedis` as dependency to the `pom.xml`:
-
-```xml pom.xml
-...
-
- redis.clients
- jedis
- 3.6.0
-
-...
-```
-
-Update `serverless.yml` as below:
-
-```yaml serverless.yml
-service: counter-api
-
-frameworkVersion: '3'
-
-provider:
- name: aws
- runtime: java17
-
-package:
- artifact: target/hello-dev.jar
-
-functions:
- hello:
- handler: com.serverless.Handler
- events:
- - httpApi:
- path: /hello
- method: get
-```
-
-### Counter Function Setup
-
-Update `src/main/java/com/serverless/Handler.java` as below:
-
-```java src/main/java/com/serverless/Handler.java
-package com.serverless;
-
-import java.util.Map;
-
-import com.amazonaws.services.lambda.runtime.Context;
-import com.amazonaws.services.lambda.runtime.RequestHandler;
-import redis.clients.jedis.Jedis;
-
-public class Handler implements RequestHandler