npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@bernierllc/contentful-cda-client

v1.0.4

Published

Content Delivery API (CDA) client wrapper with Sync API support for published content retrieval

Readme

@bernierllc/contentful-cda-client

Content Delivery API (CDA) client wrapper for published content retrieval and sync operations.

npm version License

Overview

Wrapper around the official contentful npm package for Content Delivery API access. Provides published content retrieval, Sync API for incremental updates, and CDN-backed queries with high rate limits.

Features

  • Published Content Access - Fetch entries and assets from CDN
  • Sync API Support - Initial sync + delta sync for incremental updates
  • Locale-Aware - Fetch content in specific languages
  • Include Depth - Control reference resolution depth
  • High Rate Limits - CDN-backed queries (55 requests/sec)
  • Type-Safe - Full TypeScript support with strict typing
  • Logger Integration - Built-in structured logging

What This Package Does NOT Do (MECE)

  • ❌ Draft/unpublished content (use @bernierllc/contentful-cma-client)
  • ❌ Content modification (use @bernierllc/contentful-cma-client)
  • ❌ GraphQL queries (use @bernierllc/contentful-graphql-client)

Installation

npm install @bernierllc/contentful-cda-client

Quick Start

import { ContentfulCDAClient } from '@bernierllc/contentful-cda-client';

// Initialize client
const client = new ContentfulCDAClient({
  space: 'your-space-id',
  accessToken: 'your-cda-token', // CDA token (not CMA token!)
  environment: 'master'
});

// Fetch single entry
const entry = await client.getEntry('entryId');

// Fetch entries with query
const posts = await client.getEntries({
  content_type: 'blogPost',
  limit: 10,
  order: '-sys.createdAt'
});

// Initial sync
const syncResult = await client.initialSync();
console.log(`Synced ${syncResult.entries.length} entries`);

// Delta sync (incremental updates)
const deltaResult = await client.deltaSync(syncResult.nextSyncToken);
console.log(`Updated ${deltaResult.entries.length} entries`);

API Reference

Constructor

new ContentfulCDAClient(config)

Creates a new CDA client instance.

interface ContentfulCDAConfig {
  space: string;              // Required: Contentful space ID
  accessToken: string;        // Required: CDA access token
  environment?: string;       // Optional: Environment (default: 'master')
  host?: string;              // Optional: API host (default: 'cdn.contentful.com')
  retryOnError?: boolean;     // Optional: Retry on error (default: true)
  timeout?: number;           // Optional: Request timeout (default: 30000ms)
}

Example:

const client = new ContentfulCDAClient({
  space: 'abc123xyz',
  accessToken: 'your-cda-token',
  environment: 'master',
  timeout: 60000
});

Entry Methods

getEntry<T>(entryId, locale?)

Retrieves a single published entry by ID.

interface BlogPost {
  title: string;
  body: string;
  author: ContentfulLink<'Entry'>;
}

const post = await client.getEntry<BlogPost>('5KsDBWseXY6QegucYAoacS');
console.log(post.fields.title);

// Fetch in specific locale
const spanishPost = await client.getEntry<BlogPost>('5KsDBWseXY6QegucYAoacS', 'es-ES');

getEntries<T>(query?)

Retrieves multiple published entries based on query parameters.

// Fetch all blog posts
const posts = await client.getEntries({
  content_type: 'blogPost'
});

// Fetch with pagination
const entries = await client.getEntries({
  content_type: 'article',
  limit: 10,
  skip: 20,
  order: '-sys.createdAt',
  include: 2 // Include depth for references
});

// Fetch in specific locale
const frenchArticles = await client.getEntries({
  content_type: 'article',
  locale: 'fr-FR'
});

// Filter by field values
const featuredPosts = await client.getEntries({
  content_type: 'blogPost',
  'fields.featured': true,
  limit: 5
});

Query Parameters:

  • content_type: Filter by content type ID
  • limit: Number of items to return (default: 100, max: 1000)
  • skip: Number of items to skip (for pagination)
  • order: Sort order (e.g., sys.createdAt, -sys.updatedAt)
  • include: Include depth for linked entries/assets (0-10)
  • locale: Fetch content in specific locale
  • fields.[fieldName]: Filter by field values
  • select: Select specific fields to return

Asset Methods

getAsset(assetId, locale?)

Retrieves a single published asset by ID.

const asset = await client.getAsset('1MvhB4KOyyCMIo0KeeqUaa');
console.log(asset.fields.file['en-US'].url); // Image URL
console.log(asset.fields.file['en-US'].details.size); // File size

getAssets(query?)

Retrieves multiple published assets based on query parameters.

// Fetch all images
const images = await client.getAssets({
  mimetype_group: 'image',
  limit: 20
});

// Fetch with pagination and sorting
const assets = await client.getAssets({
  limit: 50,
  skip: 0,
  order: '-sys.createdAt'
});

Sync API Methods

initialSync(options?)

Performs an initial sync to retrieve all published content. Returns a sync token for subsequent delta syncs.

// Sync all content
const syncResult = await client.initialSync();
console.log(`Synced ${syncResult.entries.length} entries`);
console.log(`Synced ${syncResult.assets.length} assets`);

// Store the sync token
const syncToken = syncResult.nextSyncToken;

// Sync only entries (not assets)
const entrySync = await client.initialSync({ type: 'Entry' });

// Sync specific content type
const blogSync = await client.initialSync({
  type: 'Entry',
  content_type: 'blogPost'
});

// Sync in specific locale
const frenchSync = await client.initialSync({ locale: 'fr-FR' });

Options:

interface ContentfulSyncOptions {
  type?: 'all' | 'Asset' | 'Entry' | 'Deletion' | 'DeletedAsset' | 'DeletedEntry';
  content_type?: string; // Filter by content type
  locale?: string;       // Filter by locale
  limit?: number;        // Items per page
}

deltaSync(nextSyncToken)

Performs a delta sync using a sync token from a previous sync. Returns only changes since the last sync.

// Perform delta sync
const deltaResult = await client.deltaSync(storedSyncToken);

// Process new/updated content
deltaResult.entries.forEach(entry => {
  console.log(`Updated entry: ${entry.sys.id}`);
});

deltaResult.assets.forEach(asset => {
  console.log(`Updated asset: ${asset.sys.id}`);
});

// Process deletions
deltaResult.deletedEntries.forEach(deleted => {
  console.log(`Deleted entry: ${deleted.sys.id}`);
});

deltaResult.deletedAssets.forEach(deleted => {
  console.log(`Deleted asset: ${deleted.sys.id}`);
});

// Store new token for next sync
storedSyncToken = deltaResult.nextSyncToken;

Sync Collection Response:

interface SyncCollection {
  entries: Entry[];           // New/updated entries
  assets: Asset[];            // New/updated assets
  deletedEntries: Entry[];    // Deleted entries
  deletedAssets: Asset[];     // Deleted assets
  nextSyncToken: string;      // Token for next delta sync
  nextPageUrl?: string;       // URL for pagination (if needed)
}

Utility Methods

getConfig()

Returns the current client configuration (without exposing the access token).

const config = client.getConfig();
console.log(config.space);       // 'your-space-id'
console.log(config.environment); // 'master'
// config.accessToken is NOT included for security

getRawClient()

Returns the underlying Contentful SDK client for advanced operations.

const rawClient = client.getRawClient();
// Use for operations not covered by the wrapper

Usage Examples

Building a Content Cache

import { ContentfulCDAClient } from '@bernierllc/contentful-cda-client';

class ContentCache {
  private client: ContentfulCDAClient;
  private syncToken: string | null = null;
  private cache: Map<string, any> = new Map();

  constructor(config: ContentfulCDAConfig) {
    this.client = new ContentfulCDAClient(config);
  }

  async initialize(): Promise<void> {
    console.log('Initializing content cache...');
    const syncResult = await this.client.initialSync();

    // Store all entries in cache
    syncResult.entries.forEach(entry => {
      this.cache.set(entry.sys.id, entry);
    });

    // Store sync token for updates
    this.syncToken = syncResult.nextSyncToken;
    console.log(`Cache initialized with ${syncResult.entries.length} entries`);
  }

  async update(): Promise<void> {
    if (!this.syncToken) {
      throw new Error('Cache not initialized');
    }

    console.log('Updating cache...');
    const deltaResult = await this.client.deltaSync(this.syncToken);

    // Update cache with changes
    deltaResult.entries.forEach(entry => {
      this.cache.set(entry.sys.id, entry);
    });

    // Remove deleted entries
    deltaResult.deletedEntries.forEach(deleted => {
      this.cache.delete(deleted.sys.id);
    });

    // Update sync token
    this.syncToken = deltaResult.nextSyncToken;
    console.log(`Cache updated: +${deltaResult.entries.length} entries, -${deltaResult.deletedEntries.length} deleted`);
  }

  get(id: string): any | undefined {
    return this.cache.get(id);
  }
}

// Usage
const cache = new ContentCache({
  space: 'your-space-id',
  accessToken: 'your-cda-token'
});

await cache.initialize();

// Update cache every 5 minutes
setInterval(() => cache.update(), 5 * 60 * 1000);

Fetching Localized Content

async function getLocalizedContent(contentType: string, locale: string) {
  const client = new ContentfulCDAClient({
    space: 'your-space-id',
    accessToken: 'your-cda-token'
  });

  const entries = await client.getEntries({
    content_type: contentType,
    locale: locale
  });

  return entries;
}

// Fetch blog posts in Spanish
const spanishPosts = await getLocalizedContent('blogPost', 'es-ES');

// Fetch products in German
const germanProducts = await getLocalizedContent('product', 'de-DE');

Paginated Content Fetching

async function fetchAllEntries(contentType: string) {
  const client = new ContentfulCDAClient({
    space: 'your-space-id',
    accessToken: 'your-cda-token'
  });

  let allEntries: any[] = [];
  let skip = 0;
  const limit = 100;
  let hasMore = true;

  while (hasMore) {
    const entries = await client.getEntries({
      content_type: contentType,
      limit: limit,
      skip: skip
    });

    allEntries = allEntries.concat(entries);
    skip += limit;
    hasMore = entries.length === limit;
  }

  return allEntries;
}

// Fetch all blog posts (handles pagination automatically)
const allPosts = await fetchAllEntries('blogPost');
console.log(`Fetched ${allPosts.length} total posts`);

Rate Limits

The Content Delivery API is served via CDN with high rate limits:

  • CDA: 55 requests/second (CDN-backed)
  • Preview API: 14 requests/second

For most use cases, CDA rate limits are generous. If you need higher throughput:

  1. Use the Sync API for bulk operations
  2. Implement client-side caching
  3. Use include parameter to fetch linked content in single request

Error Handling

try {
  const entry = await client.getEntry('invalid-id');
} catch (error) {
  if (error.sys?.id === 'NotFound') {
    console.error('Entry not found');
  } else if (error.sys?.id === 'RateLimitExceeded') {
    console.error('Rate limit exceeded, retry after:', error.sys.retryAfter);
  } else {
    console.error('Unknown error:', error);
  }
}

Testing

The package includes comprehensive test coverage (90%+):

npm test              # Run tests
npm run test:coverage # Run with coverage report
npm run test:watch    # Watch mode

Dependencies

  • @bernierllc/contentful-types - Shared Contentful type definitions
  • @bernierllc/contentful-client-core - Shared HTTP client utilities
  • @bernierllc/logger - Structured logging
  • contentful - Official Contentful SDK

Related Packages

Core Layer

  • @bernierllc/contentful-types - Shared type definitions
  • @bernierllc/contentful-client-core - HTTP client core
  • @bernierllc/contentful-cma-client - Management API client (draft content, modifications)
  • @bernierllc/contentful-graphql-client - GraphQL API client

Service Layer

  • @bernierllc/contentful-sync-service - Automated sync orchestration
  • @bernierllc/contentful-cache-service - Content caching service

License

Copyright (c) 2025 Bernier LLC. See LICENSE for details.

Support

For issues and questions, contact Bernier LLC support or file an issue in the repository.