Production-ready JavaScript/TypeScript wrapper for The Guardian Content API. Caching, rate limiting, pagination, validation.
npm install guardian-api-wrapperProduction-ready JavaScript/TypeScript wrapper for The Guardian Content API. Caching (LRU), rate limiting, pagination, validation, custom errors, and hooks.


- Caching — LRU cache (5 min TTL default), configurable or disabled.
- Rate limiting — Bottleneck (~12 req/min default for free tier), configurable or disabled.
- Pagination — searchAll(query, options) async generator for all pages.
- Validation — Dates (ISO), page/pageSize; throws GuardianValidationError.
- Custom errors — GuardianAuthError, GuardianRateLimitError, GuardianTimeoutError, GuardianAPIError.
- Hooks — onRequest, onResponse for logging or auth (no API key in logs).
- TypeScript — Strict types, generics, full JSDoc.
- Node 14+ — Uses native fetch (Node 18+); for Node 14–17 use a global fetch polyfill.
---
- Installation
- API key
- Quick start
- Configuration
- Usage
- API reference
- Errors
- TypeScript
- Scripts
- What to do next
- License & links
---
``bash`
npm install guardian-api-wrapper
Requirements: Node.js 14+ (native fetch in Node 18+; for 14–17 use a fetch polyfill).
---
Security: Never commit or log your API key. Use environment variables (e.g. process.env.GUARDIAN_API_KEY) and never pass the key to hooks or loggers.
1. Go to The Guardian Open Platform.
2. Register and create an application.
3. Copy your API key and pass it via config (see Configuration).
Rate limits: The Guardian API has usage limits. This library includes optional rate limiting (~12 req/min default); you can disable it or tune it.
---
`javascript
const { GuardianAPI } = require('guardian-api-wrapper');
const api = new GuardianAPI(process.env.GUARDIAN_API_KEY);
const data = await api.search('climate change', { pageSize: 5 });
console.log(data.response.results);
`
---
Constructor accepts a string (API key only) or a config object:
`javascript`
const api = new GuardianAPI({
apiKey: process.env.GUARDIAN_API_KEY,
baseUrl: 'https://content.guardianapis.com', // optional
timeout: 10_000, // ms, AbortController (default 10s)
cache: true, // LRU cache (default true)
cacheTtlMs: 5 60 1000, // 5 min default
cacheMaxSize: 100, // max entries
rateLimit: true, // bottleneck (default true)
rateLimitMinTimeMs: 5000, // ~12 req/min default
logger: myLogger, // optional (e.g. winston-compatible)
onRequest: (url, init) => {}, // optional hook (no key in url if you strip it)
onResponse: (url, data) => {}, // optional hook
});
To disable cache or rate limiting (e.g. in tests):
`javascript`
const api = new GuardianAPI({ apiKey: 'key', cache: false, rateLimit: false });
---
`javascript
const { GuardianAPI } = require('guardian-api-wrapper');
const api = new GuardianAPI(process.env.GUARDIAN_API_KEY);
const data = await api.search('climate change', { pageSize: 5 });
console.log(data.response.results);
`
`javascript
import GuardianAPI from 'guardian-api-wrapper';
const api = new GuardianAPI(process.env.GUARDIAN_API_KEY);
const data = await api.search('climate change', { pageSize: 5 });
console.log(data.response.results);
`
Responses are cached by URL (API key not stored in cache key). Second identical request returns cached result within TTL.
`javascript`
const api = new GuardianAPI({ apiKey: process.env.GUARDIAN_API_KEY });
await api.search('brexit'); // hits API
await api.search('brexit'); // from cache (if within TTL)
Async generator yields each page:
`javascript
const api = new GuardianAPI(process.env.GUARDIAN_API_KEY);
for await (const page of api.searchAll('brexit', { pageSize: 50 })) {
console.log(page.response.results.length);
console.log(page.response.currentPage, page.response.pages);
}
`
`javascript`
const api = new GuardianAPI({
apiKey: process.env.GUARDIAN_API_KEY,
onRequest: (url) => {
// log path only; do not log full url (contains api-key)
const path = new URL(url).pathname;
logger.info('Guardian API request', { path });
},
onResponse: (url, data) => {
const path = new URL(url).pathname;
logger.info('Guardian API response', { path, total: data?.response?.total });
},
});
---
All methods are async and return a Promise (except searchAll, which is an async generator).
| Method | Description |
|--------|-------------|
| search(query?, options?) | Search content. Options: page, pageSize (max 200), fromDate, toDate, tag, section, showFields, etc. |
| searchAll(query?, options?) | Async generator yielding each page of search results. |
| getContent(itemId, options?) | Get a single item by path (e.g. 'world/2023/oct/01/example'). |getTags(options?)
| | Fetch tags (options: type, q, page, pageSize). |getSingleTag(tagId, options?)
| | Fetch a single tag by ID (e.g. 'sport/rugbyunion'). |getSections(options?)
| | Fetch sections. |getEditions(options?)
| | Fetch editions. |getPillars(options?)
| | Fetch pillars. |
Search/list options use camelCase and are mapped to API kebab-case (e.g. pageSize → page-size). Dates must be YYYY-MM-DD. See The Guardian API documentation for full parameters.
---
The library throws custom errors (all extend GuardianError):
| Error | When |
|-------|------|
| GuardianAuthError | Invalid/missing API key (401). |
| GuardianRateLimitError | Rate limit exceeded (429). |
| GuardianTimeoutError | Request timed out (AbortController). |
| GuardianAPIError | Non-2xx, invalid JSON, or response.status === 'error'. |GuardianValidationError
| | Invalid input (e.g. bad date, pageSize > 200). |
Handle by type:
`javascript
const { GuardianAPI, GuardianAuthError, GuardianRateLimitError } = require('guardian-api-wrapper');
try {
const data = await api.search('test');
} catch (err) {
if (err instanceof GuardianAuthError) {
// invalid key
} else if (err instanceof GuardianRateLimitError) {
// rate limited; consider retry after err.retryAfter
}
}
`
---
Types are included. Use generics for responses:
`typescript
import GuardianAPI, {
type SearchOptions,
type GuardianSearchResponse,
type ResponseContent,
} from 'guardian-api-wrapper';
const api = new GuardianAPI(process.env.GUARDIAN_API_KEY!);
const opts: SearchOptions = { pageSize: 10, showFields: 'all' };
const data: GuardianSearchResponse = await api.search('news', opts);
const first: ResponseContent | undefined = data.response?.results?.[0];
`
---
| Command | Description |
|--------|-------------|
| npm run build | Compile TypeScript and bundle (tsc && rollup). |npm test
| | Build then run Jest (uses jest-fetch-mock). |npm run lint
| | ESLint on src/*/.ts. |
---
- [ ] Set repository.url and author in package.json.npm install && npm run build && npm test`.
- [ ] Run
- [ ] Use env var for API key in examples; never commit keys.
- [ ] Add CI (e.g. GitHub Actions) for build + test + lint.
- [ ] See CONTRIBUTING.md for extending the package.
---
- License: MIT. See LICENSE.
Guardian API: