@bernierllc/contentful-client-core
v1.2.0
Published
Shared HTTP client for Contentful APIs with retry logic and rate limiting
Downloads
351
Readme
@bernierllc/contentful-client-core
Shared HTTP client for Contentful APIs with retry logic and rate limiting.
Features
- 🔄 Automatic Retry - Exponential backoff for 429 and 5xx errors
- ⚡ Rate Limiting - Token bucket algorithm (7 req/sec default, configurable)
- 🔒 Authentication - Bearer token injection for all requests
- 🛡️ Error Sanitization - Normalized error responses with sensitive data removed
- 📊 Logging & Metrics - Request/response logging with
@bernierllc/logger - ⏱️ Timeout Handling - Configurable request timeouts (30s default)
- 🎯 Multi-API Support - CMA, CDA, and GraphQL base URLs
Installation
npm install @bernierllc/contentful-client-coreUsage
Basic Client
import { ContentfulClientCore } from '@bernierllc/contentful-client-core';
const client = new ContentfulClientCore({
accessToken: 'your-contentful-token',
spaceId: 'your-space-id',
apiType: 'cma', // or 'cda', 'graphql'
environmentId: 'master' // optional, defaults to 'master'
});
// GET request
const entries = await client.get('/entries');
// POST request
const newEntry = await client.post('/entries', {
fields: {
title: { 'en-US': 'My Entry' }
}
});
// PUT request
const updated = await client.put('/entries/entry-id', data);
// PATCH request
const patched = await client.patch('/entries/entry-id', partialData);
// DELETE request
await client.delete('/entries/entry-id');Configuration
const client = new ContentfulClientCore({
accessToken: 'your-token',
spaceId: 'your-space',
apiType: 'cma',
// Optional configurations
environmentId: 'staging',
host: 'https://custom.contentful.com', // Override base URL
timeout: 30000, // Request timeout in ms (default: 30000)
maxRetries: 3, // Max retry attempts (default: 3)
retryOnError: true, // Enable retry logic (default: true)
// Rate limiting
rateLimit: {
requestsPerSecond: 7, // Default: 7
burstSize: 10 // Token bucket burst size (default: 10)
},
// Custom logger
logger: customLogger
});API Types
The client supports three Contentful API types:
Content Management API (CMA)
const cmaClient = new ContentfulClientCore({
accessToken: 'cma-token',
spaceId: 'space-id',
apiType: 'cma'
});
// Base URL: https://api.contentful.com/spaces/{spaceId}/environments/{environmentId}Content Delivery API (CDA)
const cdaClient = new ContentfulClientCore({
accessToken: 'cda-token',
spaceId: 'space-id',
apiType: 'cda'
});
// Base URL: https://cdn.contentful.com/spaces/{spaceId}/environments/{environmentId}GraphQL API
const graphqlClient = new ContentfulClientCore({
accessToken: 'graphql-token',
spaceId: 'space-id',
apiType: 'graphql'
});
// Base URL: https://graphql.contentful.com/content/v1/spaces/{spaceId}/environments/{environmentId}Retry Logic
The client automatically retries on:
- 429 Too Many Requests - Respects
Retry-AfterandX-Contentful-RateLimit-Resetheaders - 5xx Server Errors - Exponential backoff with jitter
Does NOT retry on:
- 4xx client errors (except 429)
- Network errors
// Customize retry behavior
const client = new ContentfulClientCore({
accessToken: 'token',
spaceId: 'space-id',
apiType: 'cma',
maxRetries: 5, // Increase max retries
retryOnError: false // Disable retries
});Rate Limiting
Uses token bucket algorithm to enforce rate limits:
const client = new ContentfulClientCore({
accessToken: 'token',
spaceId: 'space-id',
apiType: 'cma',
rateLimit: {
requestsPerSecond: 10, // Allow 10 requests per second
burstSize: 20 // Allow bursts up to 20 requests
}
});Contentful Default: 7 requests/second with burst size of 10.
Error Handling
Errors are sanitized and normalized:
try {
await client.get('/entries/invalid-id');
} catch (error) {
console.error(error.message); // Error message
console.error(error.status); // HTTP status code
console.error(error.statusText); // HTTP status text
console.error(error.errorId); // Contentful error ID
console.error(error.details); // Additional error details
console.error(error.requestId); // Contentful request ID
console.error(error.retryable); // Whether error is retryable
}Custom Request
For advanced use cases, use the request method:
const result = await client.request({
method: 'GET',
url: '/entries',
params: { limit: 10, skip: 0 },
headers: { 'X-Custom-Header': 'value' }
});Architecture
Rate Limiter
The RateLimiter class implements a token bucket algorithm:
import { RateLimiter } from '@bernierllc/contentful-client-core';
const limiter = new RateLimiter({
requestsPerSecond: 7,
burstSize: 10
});
await limiter.acquire(); // Consumes one tokenDependencies
@bernierllc/contentful-types- Type definitions@bernierllc/retry-policy- Retry algorithms@bernierllc/retry-state- Retry state management@bernierllc/retry-metrics- Retry metrics@bernierllc/logger- Loggingaxios- HTTP client
Integration
This is a core package that provides foundational HTTP client functionality. It integrates gracefully with:
@bernierllc/logger- Automatic request/response logging@bernierllc/retry-policy- Exponential backoff retry logic@bernierllc/neverhub-adapter- Optional (NeverHub integration not required for core packages)
With CMA Client
import { ContentfulClientCore } from '@bernierllc/contentful-client-core';
const coreClient = new ContentfulClientCore({
accessToken: 'cma-token',
spaceId: 'space-id',
apiType: 'cma'
});
// Use in CMA wrapper
const entries = await coreClient.get('/entries');With CDA Client
const cdaClient = new ContentfulClientCore({
accessToken: 'cda-token',
spaceId: 'space-id',
apiType: 'cda'
});
const entries = await cdaClient.get('/entries');Security
This package implements several security best practices:
- Token Sanitization - Access tokens are redacted from logs and error messages
- Error Sanitization - Sensitive data removed from error responses
- No Hardcoded Secrets - All credentials passed via configuration
- Dependency Auditing - Regular security audits of dependencies
- Strict TypeScript - Type safety prevents common security issues
Environment Variables: This package does not use environment variables directly. Credentials must be passed explicitly via constructor options.
Testing
npm test
npm run test:coverageMock Example
import nock from 'nock';
import { ContentfulClientCore } from '@bernierllc/contentful-client-core';
describe('My Test', () => {
it('should fetch entries', async () => {
const client = new ContentfulClientCore({
accessToken: 'test-token',
spaceId: 'test-space',
apiType: 'cma',
rateLimit: { requestsPerSecond: 100 } // High limit for tests
});
nock('https://api.contentful.com')
.get('/spaces/test-space/environments/master/entries')
.reply(200, { items: [] });
const result = await client.get('/entries');
expect(result).toEqual({ items: [] });
});
});License
Copyright (c) 2025 Bernier LLC. See LICENSE for details.
