Cache helper
Cache helper lets you build a more performant app by reducing the number of server side calls for the same data. You can create a short-term cache that stores JSON objects in your Devvit app for a limited amount of time. This is valuable when you have many clients trying to get the same data, for example a stock ticker value or a sports score.
Under the covers, it's Redis plus a local in-memory write-through cache. This provides a pattern for fetching data without involving a scheduler and allows small time-to-live (TTL, ~1 second). Cache helper lets the app make one request for the data, save the response, and provide this response to all users requesting the same data.
Do not cache sensitive information. Cache helper randomly selects one user to make the real request and saves the response to the cache for others to use. You should only use cache helper for non-personalized fetches, since the same response is available to all users.
Usage
- Devvit Web
- Devvit Blocks / Mod Tools
You can import cache helper from @devvit/web/server in your server source files. The cache helper is not available client-side, so you will see an error if you try to import it in client source files.
import { cache } from '@devvit/web/server';
You need to enable Redis in order to use Cache helper.
Devvit.configure({
redis: true,
// other capabilities
});
Parameters
The cache takes a key and a TTL regardless of whether you are using Devvit Web or Devvit Blocks. The only difference is that the TTL is in seconds for Devvit Web and milliseconds for Devvit Blocks / Mod Tools.
- Devvit Web
- Devvit Blocks / Mod Tools
| Parameters | Description |
|---|---|
key | This is a string that identifies a cached response. Instead of making a real request, the app gets the cached response with the key you provide. Make sure to use different keys for different data. For example, if you’re saving post-specific data, add the postId to the cache key, like this: post_data_${postId}). |
ttl | Time to live is the number of seconds the cached response is expected to be relevant. Once the cached response expires, it will be voided and a real request is made to populate the cache again. You can treat it as a threshold, where ttl of 30 would mean that a request is done no more than once per 30 seconds. |
| Parameters | Description |
|---|---|
key | This is a string that identifies a cached response. Instead of making a real request, the app gets the cached response with the key you provide. Make sure to use different keys for different data. For example, if you’re saving post-specific data, add the postId to the cache key, like this: post_data_${postId}). |
ttl | Time to live is the number of milliseconds the cached response is expected to be relevant. Once the cached response expires, it will be voided and a real request is made to populate the cache again. You can treat it as a threshold, where ttl of 30000 would mean that a request is done no more than once per 30 seconds. |
Example
Here’s a way to set up in-app caching instead of using scheduler or interval to fetch.
- Devvit Web
- Devvit Blocks / Mod Tools
- Hono
- Express
import { Hono } from 'hono';
import { cache, context, createServer, getServerPort, reddit } from '@devvit/web/server';
type SubredditResponse = {
type: 'subreddit';
subreddit: string;
};
type SubredditErrorResponse = {
status: 'error';
message: string;
};
const app = new Hono();
app.get('/api/subreddit', async (c) => {
const { postId } = context;
if (!postId) {
console.error('API Subreddit Error: postId not found in devvit context');
return c.json<SubredditErrorResponse>(
{
status: 'error',
message: 'postId is required but missing from context',
},
400
);
}
try {
const subredditName = await cache(
async () => {
const subreddit = await reddit.getCurrentSubreddit();
if (!subreddit) {
throw new Error('Subreddit is required but missing from context');
}
return subreddit.name;
},
{
key: 'current_subreddit',
ttl: 24 * 60 * 60, // expire after one day.
}
);
console.log(`Current subreddit: ${subredditName}`);
return c.json<SubredditResponse>({
type: 'subreddit',
subreddit: subredditName,
});
} catch (error) {
console.error(`API Subreddit Error for post ${postId}:`, error);
let errorMessage = 'Unknown error during subreddit retrieval';
if (error instanceof Error) {
errorMessage = `Subreddit retrieval failed: ${error.message}`;
}
return c.json<SubredditErrorResponse>({ status: 'error', message: errorMessage }, 400);
}
});
const server = createServer(app);
server.on('error', (err) => console.error(`server error; ${err.stack}`));
server.listen(getServerPort());
import express from "express";
import {
cache,
createServer,
context,
getServerPort,
reddit,
} from "@devvit/web/server";
type SubredditResponse = {
type: "subreddit";
subreddit: string;
};
type SubredditErrorResponse = {
status: "error";
message: string;
};
const app = express();
// Middleware for JSON body parsing
app.use(express.json());
// Middleware for URL-encoded body parsing
app.use(express.urlencoded({ extended: true }));
// Middleware for plain text body parsing
app.use(express.text());
const router = express.Router();
router.get<string, never, SubredditResponse | SubredditErrorResponse, never>(
"/api/subreddit",
async (_req, res): Promise<void> => {
const { postId } = context;
if (!postId) {
console.error("API Subreddit Error: postId not found in devvit context");
res.status(400).json({
status: "error",
message: "postId is required but missing from context",
});
return;
}
try {
const subredditName = await cache(
async () => {
const subreddit = await reddit.getCurrentSubreddit();
if (!subreddit) {
throw new Error("Subreddit is required but missing from context");
}
return subreddit.name;
},
{
key: `current_subreddit`,
ttl: 24 * 60 * 60 // expire after one day.
}
);
console.log(`Current subreddit: ${subredditName}`);
res.json({
type: "subreddit",
subreddit: subredditName,
});
} catch (error) {
console.error(`API Subreddit Error for post ${postId}:`, error);
let errorMessage = "Unknown error during subreddit retrieval";
if (error instanceof Error) {
errorMessage = `Subreddit retrieval failed: ${error.message}`;
}
res.status(400).json({ status: "error", message: errorMessage });
}
}
);
app.use(router);
const server = createServer(app);
server.on("error", (err) => console.error(`server error; ${err.stack}`));
server.listen(getServerPort());
import { Devvit, useState } from '@devvit/public-api';
Devvit.configure({
redis: true,
http: true, // to use `fetch()`
// other capabilities
});
Devvit.addCustomPostType({
name: 'Name',
render: (context) => {
const [data, setData] = useState({});
async function getData() {
const result = await context.cache(
async () => {
const response = await fetch('https://example.com');
if (!response.ok) {
throw Error(`HTTP error ${response.status}: ${response.statusText}`);
}
return await response.json();
},
{
key: context.userId!,
ttl: 10_000, // millis
}
);
setData(result);
}
return (
<blocks>
<button
onPress={() => {
getData();
}}
>
Load data
</button>
<text>{data.something}</text>
</blocks>
);
},
});
export default Devvit;