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 redisThe 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 accessKV_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 ioredisDeploy 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:
- 1Uploads the function code
- 2Links the KV store and injects
KV_URL,KV_REST_API_URL, andKV_REST_API_TOKEN - 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 secondsor with the REST API:
POST /setex/key/600Common 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-franciscoThe 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.jsonDeploy 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/)