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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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-service

Usage

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

  1. Authorize: Navigate to http://localhost:3000/oauth/authorize
  2. Callback: User completes OAuth and is redirected to /oauth/callback
  3. 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 flow
  • GET /oauth/callback - OAuth callback handler

Content Endpoints

All require x-api-key header:

  • ALL /cma/:spaceId/:environmentId/* - Content Management API proxy
  • ALL /cda/:spaceId/:environmentId/entries - Content Delivery API entries
  • ALL /cda/:spaceId/:environmentId/assets - Content Delivery API assets
  • POST /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/entries

Error 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 observability
  • express - Web framework
  • express-rate-limit - Rate limiting

License

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