@bernierllc/contentful-gateway-service
v1.2.0
Published
OAuth credential management and multi-space routing gateway for Contentful
Readme
@bernierllc/contentful-gateway-service
OAuth credential management and multi-space routing gateway for Contentful.
Features
- OAuth Credential Management: Automatic token storage and refresh
- Multi-Space Routing: Route requests to different Contentful spaces/environments
- API Key Authentication: Secure internal service access
- Rate Limiting: Configurable rate limits per client
- Client Pooling: Efficient client reuse for performance
- Request Logging: Comprehensive request tracking
- NeverHub Integration: Optional integration for observability
Installation
npm install @bernierllc/contentful-gateway-serviceUsage
Basic Setup
import {
ContentfulGatewayService,
InMemoryTokenStorage
} from '@bernierllc/contentful-gateway-service';
const config = {
port: 3000,
apiKeys: ['your-internal-api-key'],
oauthConfig: {
clientId: 'your-contentful-client-id',
clientSecret: 'your-contentful-client-secret',
redirectUri: 'http://localhost:3000/oauth/callback'
},
rateLimiting: {
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // 100 requests per window
}
};
// For development/testing - use InMemoryTokenStorage
const storage = new InMemoryTokenStorage();
// For production - implement your own persistent storage
// class PostgreSQLTokenStorage implements TokenStorage { ... }
// const storage = new PostgreSQLTokenStorage(dbConfig);
const gateway = new ContentfulGatewayService(config, storage);
await gateway.start();
console.log('Gateway running on port 3000');OAuth Flow
- Authorize: Navigate to
http://localhost:3000/oauth/authorize - Callback: User completes OAuth and is redirected to
/oauth/callback - Tokens Stored: Gateway automatically stores and manages tokens
Making Requests
All requests (except OAuth and health) require an API key:
// CMA Request
fetch('http://localhost:3000/cma/space123/master/entries', {
headers: {
'x-api-key': 'your-internal-api-key'
}
});
// CDA Request
fetch('http://localhost:3000/cda/space123/master/entries?content_type=blogPost', {
headers: {
'x-api-key': 'your-internal-api-key'
}
});
// GraphQL Request
fetch('http://localhost:3000/graphql/space123/master', {
method: 'POST',
headers: {
'x-api-key': 'your-internal-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
query: 'query { blogPostCollection { items { title } } }',
variables: { limit: 10 }
})
});Custom Token Storage
For production use, implement the TokenStorage interface:
import { TokenStorage, ContentfulOAuthTokens } from '@bernierllc/contentful-auth';
class PostgreSQLTokenStorage implements TokenStorage {
constructor(private db: DatabaseAdapter) {}
async store(key: string, tokens: ContentfulOAuthTokens): Promise<void> {
await this.db.query(
'INSERT INTO contentful_tokens (key, access_token, refresh_token, expires_at) VALUES ($1, $2, $3, $4) ON CONFLICT (key) DO UPDATE SET access_token = $2, refresh_token = $3, expires_at = $4',
[
key,
tokens.access_token,
tokens.refresh_token,
new Date(Date.now() + tokens.expires_in * 1000)
]
);
}
async retrieve(key: string): Promise<ContentfulOAuthTokens | null> {
const result = await this.db.query(
'SELECT access_token, refresh_token, expires_at FROM contentful_tokens WHERE key = $1',
[key]
);
if (result.rows.length === 0) return null;
const row = result.rows[0];
return {
access_token: row.access_token,
refresh_token: row.refresh_token,
expires_in: Math.floor((new Date(row.expires_at).getTime() - Date.now()) / 1000),
token_type: 'Bearer'
};
}
async delete(key: string): Promise<void> {
await this.db.query('DELETE FROM contentful_tokens WHERE key = $1', [key]);
}
}
// Use with gateway
const storage = new PostgreSQLTokenStorage(dbAdapter);
const gateway = new ContentfulGatewayService(config, storage);API Endpoints
OAuth Endpoints
GET /oauth/authorize- Start OAuth flowGET /oauth/callback- OAuth callback handler
Content Endpoints
All require x-api-key header:
ALL /cma/:spaceId/:environmentId/*- Content Management API proxyALL /cda/:spaceId/:environmentId/entries- Content Delivery API entriesALL /cda/:spaceId/:environmentId/assets- Content Delivery API assetsPOST /graphql/:spaceId/:environmentId- GraphQL API proxy
System Endpoints
GET /health- Health check (no auth required)
Configuration
interface GatewayConfig {
// Port to run the gateway on
port: number;
// API keys for authenticating internal services
apiKeys: string[];
// OAuth configuration for Contentful
oauthConfig: {
clientId: string;
clientSecret: string;
redirectUri: string;
};
// Optional rate limiting configuration
rateLimiting?: {
windowMs: number; // Time window in ms
max: number; // Max requests per window
};
}Client Pooling
The gateway automatically pools clients per space/environment combination for optimal performance:
- CMA clients are reused for the same space/environment
- CDA clients are reused for the same space/environment
- GraphQL clients are reused for the same space/environment
// These two requests will use the SAME CMA client
GET /cma/space123/master/entries
GET /cma/space123/master/assets
// This request will create a NEW CMA client
GET /cma/space456/master/entriesError Handling
All errors are returned as JSON:
{
"error": "Error message"
}Common status codes:
401- Unauthorized (missing or invalid API key)404- Not found (invalid endpoint)429- Too many requests (rate limit exceeded)500- Internal server error
Testing
The package includes InMemoryTokenStorage for testing:
import { InMemoryTokenStorage } from '@bernierllc/contentful-gateway-service';
const storage = new InMemoryTokenStorage();
// Store test tokens
await storage.store('test-org', {
access_token: 'test-token',
refresh_token: 'test-refresh',
expires_in: 3600,
token_type: 'Bearer'
});
// Clear all tokens after tests
await storage.clear();Dependencies
@bernierllc/contentful-auth- OAuth authentication@bernierllc/contentful-cma-client- Content Management API client@bernierllc/contentful-cda-client- Content Delivery API client@bernierllc/contentful-graphql-client- GraphQL API client@bernierllc/logger- Logging@bernierllc/neverhub-adapter- Optional observabilityexpress- Web frameworkexpress-rate-limit- Rate limiting
License
Copyright (c) 2025 Bernier LLC. See LICENSE for details.
