Back to Blog
Tutorial11 min read2026-08-13

Building an Edge Function API Gateway with the KV Store

Deploy a Node.js edge function that caches API responses in the managed Redis KV store — sub-10ms reads, automatic KV_URL injection, and HTTP-based access.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

Edge functions are stateless by design: they boot, handle a request, return a response, and shut down. This makes them fast to start and cheap to scale, but it also means they cannot store data between invocations. If you need to cache an expensive API response, track rate limits, or store session tokens, you need external state — and the fastest external state is an in-memory key-value store.

PandaStack's managed Redis KV store gives edge functions sub-10ms read latency and automatic connection handling. Link the KV store to a function, and the platform injects KV_URL, KV_REST_API_URL, and KV_REST_API_TOKEN. The function connects via a native Redis client or the HTTP REST API, reads cached data, and returns it without hitting the slow upstream service.

Why edge functions need a KV store instead of a database

SQL databases are optimized for relational queries, joins, and transactions. They are too slow for edge functions, which need to respond in under 100ms. A PostgreSQL query over TLS takes 20–50ms even when the query is indexed and returns a single row. That latency is unacceptable when the function's entire execution budget is 50ms.

Redis is an in-memory key-value store. Reads take 1–5ms, writes take 2–10ms, and there is no schema to manage. You store JSON strings by key, retrieve them by key, and delete them when they expire. This is exactly the access pattern caching needs.

Linking a KV store to an edge function gives you Redis without managing a server, configuring persistence, or handling connection pooling. The platform injects the credentials, and your function talks to Redis as if it were a local service.

Create a managed Redis KV store

Provision a KV store via the CLI:

panda databases create \
  --name api-cache \
  --engine redis

The platform creates a managed Redis instance. Unlike PostgreSQL or MySQL, Redis KV stores do not have configurable CPU or disk — they are optimized for in-memory workloads and auto-scale within the plan limits.

The command returns connection details:

  • KV_URL: native Redis connection string (redis://...)
  • KV_REST_API_URL: HTTP endpoint for REST-based access
  • KV_REST_API_TOKEN: bearer token for the REST API

The REST API is useful when the function environment does not support native Redis clients or you want to avoid connection pooling overhead.

Deploy a Node.js edge function with the KV store linked

Edge functions on PandaStack use the nodejs or python runtime. Create a Node.js function that caches API responses:

// index.js
const Redis = require('ioredis');

const redis = new Redis(process.env.KV_URL, {
  tls: { rejectUnauthorized: false },
});

exports.handler = async (event) => {
  const cacheKey = 'weather:san-francisco';

  // Check cache
  const cached = await redis.get(cacheKey);
  if (cached) {
    return {
      statusCode: 200,
      body: JSON.stringify({ source: 'cache', data: JSON.parse(cached) }),
    };
  }

  // Fetch from upstream API
  const response = await fetch('https://api.weather.example/forecast/san-francisco');
  const data = await response.json();

  // Cache for 10 minutes
  await redis.setex(cacheKey, 600, JSON.stringify(data));

  return {
    statusCode: 200,
    body: JSON.stringify({ source: 'upstream', data }),
  };
};

Install the Redis client:

npm install ioredis

Deploy the function via the API:

curl -X POST https://api.pandastack.io/v1/functions \
  -H "Authorization: Bearer $PANDASTACK_TOKEN" \
  -F "name=weather-cache" \
  -F "runtime=nodejs" \
  -F "code=@index.js" \
  -F "linkKV=api-cache"

The platform:

  1. 1Uploads the function code
  2. 2Links the KV store and injects KV_URL, KV_REST_API_URL, and KV_REST_API_TOKEN
  3. 3Assigns an invoke endpoint: https://functions.pandastack.app/invoke/{function-id}

The first request fetches data from the upstream API, caches it in Redis for 10 minutes, and returns it. Subsequent requests within the TTL read from the cache and skip the upstream call. Cache hits return in under 50ms; cache misses take as long as the upstream API.

Use the HTTP REST API when native clients are unavailable

If your edge function does not have a Redis client or you want to avoid connection pooling, use the HTTP REST API. The platform provides an Upstash-compatible REST façade over Redis:

// index.js (REST API version)
exports.handler = async (event) => {
  const cacheKey = 'weather:san-francisco';

  // Check cache
  const getResponse = await fetch(
    `${process.env.KV_REST_API_URL}/get/${cacheKey}`,
    { headers: { Authorization: `Bearer ${process.env.KV_REST_API_TOKEN}` } }
  );
  const cached = await getResponse.json();

  if (cached.result) {
    return {
      statusCode: 200,
      body: JSON.stringify({ source: 'cache', data: JSON.parse(cached.result) }),
    };
  }

  // Fetch from upstream API
  const response = await fetch('https://api.weather.example/forecast/san-francisco');
  const data = await response.json();

  // Cache for 10 minutes
  await fetch(
    `${process.env.KV_REST_API_URL}/setex/${cacheKey}/600`,
    {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.KV_REST_API_TOKEN}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(data),
    }
  );

  return {
    statusCode: 200,
    body: JSON.stringify({ source: 'upstream', data }),
  };
};

The REST API adds 5–10ms of latency compared to native Redis clients, but it eliminates the need for connection pooling and TLS handshake overhead. For edge functions that run infrequently, the REST API is simpler.

Set cache expiration to avoid stale data

Redis keys without a TTL live forever and consume memory until manually deleted. Always set an expiration when caching:

await redis.setex('key', 600, 'value'); // Expires in 600 seconds

or with the REST API:

POST /setex/key/600

Common TTL values:

  • API responses: 5–15 minutes
  • Rate limit counters: 1 hour
  • Session tokens: 24 hours
  • Feature flags: 10 minutes

If the upstream data changes frequently, use a shorter TTL. If it is mostly static, use a longer TTL to reduce upstream traffic.

Handle cache invalidation when upstream data updates

The cache returns stale data until the TTL expires. If the upstream API updates the forecast and you need the cache to reflect it immediately, delete the key:

await redis.del('weather:san-francisco');

or via REST:

DELETE /del/weather:san-francisco

The next request misses the cache, fetches fresh data from upstream, and repopulates the cache with the new TTL.

For advanced invalidation patterns, you can use Redis pub/sub to notify multiple functions when a key changes, or implement cache tags where related keys are grouped and invalidated together.

Use the KV store for rate limiting edge function invocations

Edge functions are billed per invocation and execution time. If someone abuses your public function endpoint by sending thousands of requests per second, your bill spikes and the upstream API might block you. Rate limiting with the KV store stops this:

const Redis = require('ioredis');
const redis = new Redis(process.env.KV_URL, {
  tls: { rejectUnauthorized: false },
});

exports.handler = async (event) => {
  const ip = event.headers['x-forwarded-for'] || event.requestContext.identity.sourceIp;
  const key = `ratelimit:${ip}`;

  const count = await redis.incr(key);
  if (count === 1) {
    await redis.expire(key, 60); // 60 requests per minute
  }

  if (count > 60) {
    return {
      statusCode: 429,
      body: JSON.stringify({ error: 'Rate limit exceeded' }),
    };
  }

  // Process request
  return { statusCode: 200, body: 'OK' };
};

The function increments a counter for the client's IP address. If the counter exceeds 60 within a 60-second window, the request is rejected. Legitimate users stay under the limit; abusive clients get throttled.

Monitor KV store usage and connection errors

The KV store has a connection limit. If your edge function leaks connections by not closing them, you will hit the limit and start seeing ECONNREFUSED errors. The ioredis client reuses connections automatically, but you should still close the client when the function exits:

process.on('beforeExit', () => {
  redis.quit();
});

For REST API access, there are no persistent connections — each request is independent, so connection leaks are impossible.

Deploy from a Git repo with a package.json

If your edge function has dependencies, package them with npm install before deploying. Create a repo with:

my-function/
├── index.js
└── package.json

Deploy via the API with the repo URL:

curl -X POST https://api.pandastack.io/v1/functions \
  -H "Authorization: Bearer $PANDASTACK_TOKEN" \
  -F "name=weather-cache" \
  -F "runtime=nodejs" \
  -F "repositoryName=yourname/my-function" \
  -F "branch=main" \
  -F "linkKV=api-cache"

The platform clones the repo, runs npm install, bundles the function with dependencies, and deploys it. Future commits trigger redeployments if autoDeploy is enabled.

References

  • [Redis documentation](https://redis.io/docs/)
  • [ioredis client guide](https://github.com/redis/ioredis)
  • [PandaStack KV store docs](https://docs.pandastack.io/kv/)

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Tutorial

Browse all Tutorial articles →

See also