Skip to content

Commit c164d0f

Browse files
committed
feat: enhance caching strategy and API response handling
1 parent a0627a8 commit c164d0f

2 files changed

Lines changed: 114 additions & 46 deletions

File tree

README.md

Lines changed: 59 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ A simple REST API to get ARC Raiders game data. Need info about items, weapons,
88

99
- **Fast & Global**: Uses Cloudflare's global network - fast responses no matter where you are
1010
- **Just JSON**: Returns the raw data directly, no extra processing
11-
- **Cached**: Data is cached for 5 minutes to keep things speedy
11+
- **Smart Caching**: 1-hour cache with stale-while-revalidate for always-fast responses
1212
- **Secure**: HTTPS only, with standard security headers
1313
- **CORS Enabled**: Works from browsers, no cross-origin issues
1414
- **Auto-Updated**: Automatically finds new data files as they're added
@@ -38,40 +38,51 @@ These endpoints let you list everything or get a specific item:
3838

3939
#### Items
4040
```bash
41-
GET /v1/items # List all items (490 items)
42-
GET /v1/items/{item_id} # Get a specific item
41+
GET /v1/items # List all item IDs (490 items)
42+
GET /v1/items?full=true # Get ALL items with full data (1 request!)
43+
GET /v1/items/{item_id} # Get a specific item
4344
```
4445

45-
**Example:**
46+
**Examples:**
4647
```bash
48+
# Get just the list of IDs
49+
curl https://arcdata.mahcks.com/v1/items
50+
51+
# Get all items with complete data in one request
52+
curl https://arcdata.mahcks.com/v1/items?full=true
53+
54+
# Get a specific item
4755
curl https://arcdata.mahcks.com/v1/items/anvil_i
4856
```
4957

5058
#### Hideout Modules
5159
```bash
52-
GET /v1/hideout # List all hideout modules (9 modules)
60+
GET /v1/hideout # List all hideout module IDs
61+
GET /v1/hideout?full=true # Get ALL modules with full data
5362
GET /v1/hideout/{module_id} # Get a specific module
5463
```
5564

5665
**Example:**
5766
```bash
58-
curl https://arcdata.mahcks.com/v1/hideout/weapon_bench
67+
curl https://arcdata.mahcks.com/v1/hideout?full=true
5968
```
6069

6170
#### Quests
6271
```bash
63-
GET /v1/quests # List all quests (72 quests)
64-
GET /v1/quests/{quest_id} # Get a specific quest
72+
GET /v1/quests # List all quest IDs
73+
GET /v1/quests?full=true # Get ALL quests with full data
74+
GET /v1/quests/{quest_id} # Get a specific quest
6575
```
6676

6777
**Example:**
6878
```bash
69-
curl https://arcdata.mahcks.com/v1/quests/power_out
79+
curl https://arcdata.mahcks.com/v1/quests?full=true
7080
```
7181

7282
#### Map Events
7383
```bash
74-
GET /v1/map-events # List all map events
84+
GET /v1/map-events # List all map event IDs
85+
GET /v1/map-events?full=true # Get ALL events with full data
7586
GET /v1/map-events/{event_id} # Get a specific event
7687
```
7788

@@ -107,18 +118,48 @@ Returns the complete JSON data for whatever you requested.
107118
}
108119
```
109120

121+
## Performance Tips
122+
123+
### Use `?full=true` to avoid multiple requests
124+
125+
Instead of making hundreds of requests:
126+
```javascript
127+
// ❌ BAD - Makes 491 requests (1 for list + 490 for each item)
128+
const list = await fetch('/v1/items').then(r => r.json());
129+
const items = await Promise.all(
130+
list.items.map(item => fetch(item.url).then(r => r.json()))
131+
);
132+
```
133+
134+
Use the `?full=true` parameter:
135+
```javascript
136+
// ✅ GOOD - Makes 1 request total
137+
const { items } = await fetch('/v1/items?full=true').then(r => r.json());
138+
```
139+
140+
**Performance difference:**
141+
- Without `?full=true`: **491 requests** (~5-10 seconds)
142+
- With `?full=true`: **1 request** (~500ms)
143+
110144
## How Caching Works
111145

112-
The API caches data for 5 minutes to make things faster:
146+
The API uses smart caching with stale-while-revalidate:
113147

114148
1. **First Request**: Gets data from GitHub and caches it
115-
2. **Next Requests**: Served from cache (super fast)
116-
3. **After 5 Minutes**: Cache expires, next request gets fresh data from GitHub
117-
118-
This means:
119-
- 🚀 Really fast responses (data is cached close to you)
120-
- 🔄 Fresh data (never more than 5 minutes old)
121-
- 💰 Efficient (doesn't hammer GitHub's API)
149+
2. **Next Hour**: Served from cache (instant)
150+
3. **After 1 Hour**: Serves cached data immediately while fetching fresh data in the background
151+
4. **Stale Tolerance**: Will serve cached data up to 24 hours old if GitHub is slow/unavailable
152+
153+
**What this means:**
154+
- 🚀 Always instant responses (no waiting for GitHub)
155+
- 🔄 Fresh data (usually within 1 hour)
156+
- 💪 Resilient (works even if GitHub is down)
157+
- 💰 Efficient (minimal GitHub API usage)
158+
159+
**Cache Duration:**
160+
- Fresh: 1 hour
161+
- Stale-while-revalidate: 24 hours
162+
- The `?full=true` response is also cached separately
122163

123164
## Security
124165

src/index.js

Lines changed: 55 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
*/
55

66
const GITHUB_BASE = 'https://raw.githubusercontent.com/RaidTheory/arcraiders-data/main';
7-
const CACHE_TTL = 300; // 5 minutes
7+
const CACHE_TTL = 3600; // 1 hour - data doesn't change often
8+
const STALE_WHILE_REVALIDATE = 86400; // 24 hours - serve stale data while fetching fresh
89

910
// Define valid data types and their paths
1011
// For directory-based collections (multiple JSON files in folders)
@@ -30,12 +31,6 @@ export default {
3031
const url = new URL(request.url);
3132
const path = url.pathname;
3233

33-
// Enforce HTTPS - redirect HTTP to HTTPS
34-
if (url.protocol === 'http:') {
35-
const httpsUrl = url.toString().replace('http://', 'https://');
36-
return Response.redirect(httpsUrl, 301);
37-
}
38-
3934
// Handle CORS preflight
4035
if (request.method === 'OPTIONS') {
4136
return handleCors();
@@ -55,7 +50,9 @@ export default {
5550

5651
// Otherwise try as a collection list
5752
if (COLLECTION_TYPES[type]) {
58-
return await handleList(type, env, ctx);
53+
// Check if user wants full data via ?full=true
54+
const full = url.searchParams.get('full') === 'true';
55+
return await handleList(type, env, ctx, full);
5956
}
6057

6158
return jsonResponse({ error: `Unknown data type: ${type}` }, 404);
@@ -133,7 +130,7 @@ async function handleSingleFile(type, env, ctx) {
133130
status: 200,
134131
headers: {
135132
'Content-Type': 'application/json',
136-
'Cache-Control': `public, max-age=${CACHE_TTL}`,
133+
'Cache-Control': `public, max-age=${CACHE_TTL}, stale-while-revalidate=${STALE_WHILE_REVALIDATE}`,
137134
},
138135
});
139136

@@ -179,7 +176,7 @@ async function handleGetItem(type, id, env, ctx) {
179176
status: 200,
180177
headers: {
181178
'Content-Type': 'application/json',
182-
'Cache-Control': `public, max-age=${CACHE_TTL}`,
179+
'Cache-Control': `public, max-age=${CACHE_TTL}, stale-while-revalidate=${STALE_WHILE_REVALIDATE}`,
183180
},
184181
});
185182

@@ -193,22 +190,24 @@ async function handleGetItem(type, id, env, ctx) {
193190
/**
194191
* List all items of a type
195192
* Uses GitHub API to dynamically list directory contents
193+
* @param {boolean} full - If true, fetch and return full data for all items
196194
*/
197-
async function handleList(type, env, ctx) {
195+
async function handleList(type, env, ctx, full = false) {
198196
if (!COLLECTION_TYPES[type]) {
199197
return jsonResponse({ error: `Unknown collection type: ${type}` }, 404);
200198
}
201199

202200
const dirPath = COLLECTION_TYPES[type];
203201
const githubApiUrl = `https://api.github.com/repos/RaidTheory/arcraiders-data/contents/${dirPath}`;
204202

205-
// Check cache first
203+
// Different cache key for full vs list-only
204+
const cacheKeySuffix = full ? '?full=true' : '';
206205
const cache = caches.default;
207-
const cacheKey = new Request(githubApiUrl);
206+
const cacheKey = new Request(githubApiUrl + cacheKeySuffix);
208207
let response = await cache.match(cacheKey);
209208

210209
if (!response) {
211-
// Fetch from GitHub API
210+
// Fetch from GitHub API to get file list
212211
const githubResponse = await fetch(githubApiUrl, {
213212
headers: {
214213
'User-Agent': 'ArcRaiders-API/1.0',
@@ -222,24 +221,52 @@ async function handleList(type, env, ctx) {
222221

223222
const files = await githubResponse.json();
224223

225-
// Filter to only .json files and extract IDs
226-
const items = files
224+
// Filter to only .json files
225+
const jsonFiles = files
227226
.filter((f) => f.type === 'file' && f.name.endsWith('.json') && !f.name.startsWith('_'))
228-
.map((f) => ({
229-
id: f.name.replace('.json', ''),
230-
url: `/v1/${type}/${f.name.replace('.json', '')}`,
231-
}))
232-
.sort((a, b) => a.id.localeCompare(b.id));
233-
234-
const data = {
235-
type,
236-
count: items.length,
237-
items,
238-
};
227+
.map((f) => f.name.replace('.json', ''))
228+
.sort((a, b) => a.localeCompare(b));
229+
230+
let data;
231+
232+
if (full) {
233+
// Fetch all item data in parallel
234+
const itemPromises = jsonFiles.map(async (id) => {
235+
const itemUrl = `${GITHUB_BASE}/${dirPath}/${id}.json`;
236+
const itemResponse = await fetch(itemUrl, {
237+
headers: { 'User-Agent': 'ArcRaiders-API/1.0' },
238+
});
239+
240+
if (itemResponse.ok) {
241+
return await itemResponse.json();
242+
}
243+
return null;
244+
});
245+
246+
const itemsData = await Promise.all(itemPromises);
247+
248+
data = {
249+
type,
250+
count: itemsData.filter(Boolean).length,
251+
items: itemsData.filter(Boolean),
252+
};
253+
} else {
254+
// Just return IDs and URLs (original behavior)
255+
const items = jsonFiles.map((id) => ({
256+
id,
257+
url: `/v1/${type}/${id}`,
258+
}));
259+
260+
data = {
261+
type,
262+
count: items.length,
263+
items,
264+
};
265+
}
239266

240267
// Create cacheable response
241268
response = jsonResponse(data);
242-
response.headers.set('Cache-Control', `public, max-age=${CACHE_TTL}`);
269+
response.headers.set('Cache-Control', `public, max-age=${CACHE_TTL}, stale-while-revalidate=${STALE_WHILE_REVALIDATE}`);
243270

244271
// Store in cache (non-blocking)
245272
ctx.waitUntil(cache.put(cacheKey, response.clone()));

0 commit comments

Comments
 (0)