Skip to main content
This guide covers proven integration patterns and architectural approaches for building robust, scalable integrations with the Nudj platform, from simple API integrations to complex enterprise architectures.
The code examples in this guide demonstrate integration patterns using pseudocode. For actual API implementation, use direct HTTP requests to the Nudj API endpoints documented in our API Reference.

Core Integration Patterns

Request-Response Pattern

Synchronous API Integration
// Simple synchronous pattern for real-time operations
class UserService {
  constructor(private nudjClient: NudjClient) {}
  
  async createUser(userData: CreateUserRequest): Promise<User> {
    try {
      // Create user in your system
      const localUser = await this.db.users.create(userData);
      
      // Sync to Nudj platform
      const nudjUser = await this.nudjClient.users.create({
        email: localUser.email,
        username: localUser.username,
        externalUserId: localUser.id
      });
      
      // Update local record with Nudj ID
      await this.db.users.update(localUser.id, {
        nudjUserId: nudjUser.id
      });
      
      return localUser;
    } catch (error) {
      // Handle rollback if needed
      await this.handleUserCreationError(error);
      throw error;
    }
  }
}
Best Use Cases:
  • Real-time user operations
  • Interactive challenge completion
  • Immediate reward redemption
  • Administrative operations

Event-Driven Pattern

Asynchronous Event Processing
// Event-driven pattern for scalable, decoupled operations
class EngagementEventHandler {
  constructor(
    private eventBus: EventBus,
    private nudjClient: NudjClient
  ) {}
  
  async handleUserRegistration(event: UserRegisteredEvent) {
    // Process in background
    await this.eventBus.publish('user.sync.requested', {
      userId: event.userId,
      timestamp: event.timestamp
    });
  }
  
  async syncUserToNudj(event: UserSyncRequestedEvent) {
    const user = await this.db.users.findById(event.userId);
    
    await this.nudjClient.users.create({
      email: user.email,
      username: user.username,
      externalUserId: user.id
    });
    
    await this.eventBus.publish('user.synced', {
      userId: event.userId,
      nudjUserId: user.nudjUserId
    });
  }
}
Best Use Cases:
  • High-volume user operations
  • Background data synchronization
  • Complex workflow orchestration
  • System decoupling

Batch Processing Pattern

Bulk Data Operations
// Batch processing for efficient bulk operations
class BatchSyncService {
  constructor(private nudjClient: NudjClient) {}
  
  async syncUsersBatch(userIds: string[], batchSize = 100) {
    const batches = this.chunk(userIds, batchSize);
    
    for (const batch of batches) {
      await this.processBatch(batch);
      await this.delay(1000); // Rate limiting
    }
  }
  
  private async processBatch(userIds: string[]) {
    const users = await this.db.users.findByIds(userIds);
    
    const nudjUsers = users.map(user => ({
      email: user.email,
      username: user.username,
      externalUserId: user.id
    }));
    
    try {
      const results = await this.nudjClient.users.bulkCreate(nudjUsers);
      await this.updateLocalRecords(users, results);
    } catch (error) {
      await this.handleBatchError(userIds, error);
    }
  }
}
Best Use Cases:
  • Initial data migration
  • Daily/periodic synchronization
  • Bulk user imports
  • Data cleansing operations

Authentication Patterns

API Key Management

Secure API Key Handling
class SecureNudjClient {
  private apiKey: string;
  private keyRotationSchedule: NodeJS.Timer;
  
  constructor(config: ClientConfig) {
    this.apiKey = this.loadApiKey();
    this.setupKeyRotation();
  }
  
  private loadApiKey(): string {
    // Load from secure vault/key management system
    return process.env.NUDJ_API_KEY || this.vault.getSecret('nudj-api-key');
  }
  
  private setupKeyRotation() {
    // Rotate API keys periodically
    this.keyRotationSchedule = setInterval(async () => {
      await this.rotateApiKey();
    }, 30 * 24 * 60 * 60 * 1000); // 30 days
  }
  
  private async rotateApiKey() {
    const newKey = await this.nudjClient.auth.rotateApiKey();
    await this.vault.setSecret('nudj-api-key', newKey);
    this.apiKey = newKey;
  }
}

Token-Based Authentication

JWT Token Management
import jwt
from datetime import datetime, timedelta
from nudj import NudjClient

class TokenManager:
    def __init__(self, client_id: str, client_secret: str):
        self.client_id = client_id
        self.client_secret = client_secret
        self.token = None
        self.expires_at = None
    
    async def get_valid_token(self) -> str:
        if self.token and self.expires_at > datetime.utcnow():
            return self.token
        
        return await self.refresh_token()
    
    async def refresh_token(self) -> str:
        response = await self.nudj_client.auth.get_token({
            'client_id': self.client_id,
            'client_secret': self.client_secret,
            'grant_type': 'client_credentials'
        })
        
        self.token = response.access_token
        self.expires_at = datetime.utcnow() + timedelta(
            seconds=response.expires_in - 300  # 5-minute buffer
        )
        
        return self.token

Data Synchronization Patterns

Bidirectional Sync

Two-Way Data Synchronization
class BidirectionalSyncService {
  constructor(
    private nudjClient: NudjClient,
    private localDb: Database
  ) {}
  
  async syncUserData(userId: string) {
    const [localUser, nudjUser] = await Promise.all([
      this.localDb.users.findById(userId),
      this.nudjClient.users.get(userId)
    ]);
    
    // Determine which record is more recent
    const localUpdated = new Date(localUser.updatedAt);
    const nudjUpdated = new Date(nudjUser.updatedAt);
    
    if (localUpdated > nudjUpdated) {
      // Local is newer, sync to Nudj
      await this.syncToNudj(localUser);
    } else if (nudjUpdated > localUpdated) {
      // Nudj is newer, sync to local
      await this.syncToLocal(nudjUser);
    }
    // If equal, no sync needed
  }
  
  private async syncToNudj(localUser: LocalUser) {
    await this.nudjClient.users.update(localUser.nudjUserId, {
      displayName: localUser.displayName,
      bio: localUser.bio,
      profilePictureUrl: localUser.avatarUrl
    });
  }
  
  private async syncToLocal(nudjUser: NudjUser) {
    await this.localDb.users.update(nudjUser.externalUserId, {
      displayName: nudjUser.displayName,
      bio: nudjUser.bio,
      avatarUrl: nudjUser.profilePictureUrl,
      updatedAt: nudjUser.updatedAt
    });
  }
}

Change Data Capture

CDC-Based Synchronization
class ChangeDataCaptureSync:
    def __init__(self, nudj_client: NudjClient):
        self.nudj_client = nudj_client
        self.last_sync_timestamp = self.get_last_sync_timestamp()
    
    async def sync_changes(self):
        # Get changes since last sync
        changes = await self.get_local_changes(self.last_sync_timestamp)
        
        for change in changes:
            await self.process_change(change)
        
        # Update sync timestamp
        self.update_last_sync_timestamp(datetime.utcnow())
    
    async def process_change(self, change: DataChange):
        if change.operation == 'INSERT':
            await self.handle_create(change)
        elif change.operation == 'UPDATE':
            await self.handle_update(change)
        elif change.operation == 'DELETE':
            await self.handle_delete(change)
    
    async def handle_create(self, change: DataChange):
        if change.table == 'users':
            await self.nudj_client.users.create(change.new_values)
        elif change.table == 'challenges':
            await self.nudj_client.challenges.create(change.new_values)

Error Handling Patterns

Circuit Breaker Pattern

Resilient API Integration
class CircuitBreaker {
  private failures = 0;
  private lastFailureTime = 0;
  private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';
  
  constructor(
    private threshold = 5,
    private timeout = 60000,
    private retryTimeout = 30000
  ) {}
  
  async execute<T>(operation: () => Promise<T>): Promise<T> {
    if (this.state === 'OPEN') {
      if (Date.now() - this.lastFailureTime < this.timeout) {
        throw new Error('Circuit breaker is OPEN');
      }
      this.state = 'HALF_OPEN';
    }
    
    try {
      const result = await operation();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }
  
  private onSuccess() {
    this.failures = 0;
    this.state = 'CLOSED';
  }
  
  private onFailure() {
    this.failures++;
    this.lastFailureTime = Date.now();
    
    if (this.failures >= this.threshold) {
      this.state = 'OPEN';
    }
  }
}

// Usage
const circuitBreaker = new CircuitBreaker();
const nudjClientWithCircuitBreaker = {
  users: {
    get: (id: string) => circuitBreaker.execute(() => nudjClient.users.get(id))
  }
};

Retry with Exponential Backoff

Intelligent Retry Logic
import asyncio
import random
from typing import Callable, TypeVar, Any

T = TypeVar('T')

class RetryWithBackoff:
    def __init__(
        self,
        max_retries: int = 3,
        base_delay: float = 1.0,
        max_delay: float = 60.0,
        jitter: bool = True
    ):
        self.max_retries = max_retries
        self.base_delay = base_delay
        self.max_delay = max_delay
        self.jitter = jitter
    
    async def execute(
        self,
        operation: Callable[[], Awaitable[T]],
        retryable_exceptions: tuple = (Exception,)
    ) -> T:
        last_exception = None
        
        for attempt in range(self.max_retries + 1):
            try:
                return await operation()
            except retryable_exceptions as e:
                last_exception = e
                
                if attempt == self.max_retries:
                    break
                
                delay = min(
                    self.base_delay * (2 ** attempt),
                    self.max_delay
                )
                
                if self.jitter:
                    delay *= (0.5 + random.random() * 0.5)
                
                await asyncio.sleep(delay)
        
        raise last_exception

# Usage
retry_handler = RetryWithBackoff()

async def get_user_with_retry(user_id: str):
    return await retry_handler.execute(
        lambda: nudj_client.users.get(user_id),
        retryable_exceptions=(ConnectionError, TimeoutError)
    )

Caching Patterns

Multi-Level Caching

Layered Caching Strategy
interface CacheLayer {
  get(key: string): Promise<any>;
  set(key: string, value: any, ttl?: number): Promise<void>;
  delete(key: string): Promise<void>;
}

class MultiLevelCache {
  constructor(
    private l1Cache: CacheLayer, // Memory cache
    private l2Cache: CacheLayer, // Redis cache
    private l3Cache: CacheLayer  // Database cache
  ) {}
  
  async get(key: string): Promise<any> {
    // Try L1 cache first
    let value = await this.l1Cache.get(key);
    if (value) return value;
    
    // Try L2 cache
    value = await this.l2Cache.get(key);
    if (value) {
      await this.l1Cache.set(key, value, 300); // 5 min TTL
      return value;
    }
    
    // Try L3 cache
    value = await this.l3Cache.get(key);
    if (value) {
      await this.l2Cache.set(key, value, 3600); // 1 hour TTL
      await this.l1Cache.set(key, value, 300);
      return value;
    }
    
    return null;
  }
  
  async set(key: string, value: any) {
    await Promise.all([
      this.l1Cache.set(key, value, 300),
      this.l2Cache.set(key, value, 3600),
      this.l3Cache.set(key, value, 86400) // 24 hours
    ]);
  }
}

class CachedNudjClient {
  constructor(
    private nudjClient: NudjClient,
    private cache: MultiLevelCache
  ) {}
  
  async getUser(userId: string): Promise<User> {
    const cacheKey = `user:${userId}`;
    
    let user = await this.cache.get(cacheKey);
    if (user) return user;
    
    user = await this.nudjClient.users.get(userId);
    await this.cache.set(cacheKey, user);
    
    return user;
  }
}

Cache Invalidation

Smart Cache Invalidation
class CacheInvalidationService:
    def __init__(self, cache: CacheLayer, nudj_client: NudjClient):
        self.cache = cache
        self.nudj_client = nudj_client
        self.setup_webhooks()
    
    def setup_webhooks(self):
        # Listen for user update events
        self.nudj_client.webhooks.subscribe(
            'user.updated',
            self.handle_user_updated
        )
    
    async def handle_user_updated(self, event: WebhookEvent):
        user_id = event.data.user_id
        
        # Invalidate all caches for this user
        await self.invalidate_user_cache(user_id)
    
    async def invalidate_user_cache(self, user_id: str):
        cache_keys = [
            f'user:{user_id}',
            f'user:profile:{user_id}',
            f'user:challenges:{user_id}',
            f'user:rewards:{user_id}'
        ]
        
        for key in cache_keys:
            await self.cache.delete(key)

Monitoring & Observability Patterns

Comprehensive Logging

Structured Logging with Correlation IDs
class ObservableNudjClient {
  constructor(
    private nudjClient: NudjClient,
    private logger: Logger,
    private metrics: MetricsCollector
  ) {}
  
  async getUser(userId: string, correlationId?: string): Promise<User> {
    const startTime = Date.now();
    const requestId = correlationId || generateRequestId();
    
    this.logger.info('Nudj API request started', {
      operation: 'getUser',
      userId,
      requestId,
      timestamp: new Date().toISOString()
    });
    
    try {
      const user = await this.nudjClient.users.get(userId);
      
      const duration = Date.now() - startTime;
      
      this.logger.info('Nudj API request completed', {
        operation: 'getUser',
        userId,
        requestId,
        duration,
        success: true
      });
      
      this.metrics.increment('nudj.api.requests', {
        operation: 'getUser',
        status: 'success'
      });
      
      this.metrics.histogram('nudj.api.duration', duration, {
        operation: 'getUser'
      });
      
      return user;
    } catch (error) {
      const duration = Date.now() - startTime;
      
      this.logger.error('Nudj API request failed', {
        operation: 'getUser',
        userId,
        requestId,
        duration,
        error: error.message,
        stack: error.stack
      });
      
      this.metrics.increment('nudj.api.requests', {
        operation: 'getUser',
        status: 'error'
      });
      
      throw error;
    }
  }
}

Health Check Pattern

Service Health Monitoring
from typing import Dict, Any
from datetime import datetime, timedelta

class HealthCheckService:
    def __init__(self, nudj_client: NudjClient):
        self.nudj_client = nudj_client
        self.last_successful_call = None
        self.consecutive_failures = 0
    
    async def check_health(self) -> Dict[str, Any]:
        health_status = {
            'status': 'healthy',
            'timestamp': datetime.utcnow().isoformat(),
            'checks': {}
        }
        
        # Check API connectivity
        api_health = await self.check_api_connectivity()
        health_status['checks']['nudj_api'] = api_health
        
        # Check authentication
        auth_health = await self.check_authentication()
        health_status['checks']['authentication'] = auth_health
        
        # Overall status
        all_healthy = all(
            check['status'] == 'healthy' 
            for check in health_status['checks'].values()
        )
        
        health_status['status'] = 'healthy' if all_healthy else 'unhealthy'
        
        return health_status
    
    async def check_api_connectivity(self) -> Dict[str, Any]:
        try:
            start_time = datetime.utcnow()
            await self.nudj_client.health_check()
            duration = (datetime.utcnow() - start_time).total_seconds()
            
            self.last_successful_call = datetime.utcnow()
            self.consecutive_failures = 0
            
            return {
                'status': 'healthy',
                'response_time_ms': duration * 1000,
                'last_success': self.last_successful_call.isoformat()
            }
        except Exception as e:
            self.consecutive_failures += 1
            
            return {
                'status': 'unhealthy',
                'error': str(e),
                'consecutive_failures': self.consecutive_failures,
                'last_success': self.last_successful_call.isoformat() if self.last_successful_call else None
            }

Scaling Patterns

Load Balancing

API Client Load Balancing
class LoadBalancedNudjClient {
  private clients: NudjClient[] = [];
  private currentIndex = 0;
  
  constructor(endpoints: string[], apiKey: string) {
    this.clients = endpoints.map(endpoint => 
      new NudjClient({ baseURL: endpoint, apiKey })
    );
  }
  
  private getNextClient(): NudjClient {
    const client = this.clients[this.currentIndex];
    this.currentIndex = (this.currentIndex + 1) % this.clients.length;
    return client;
  }
  
  async getUser(userId: string): Promise<User> {
    const maxAttempts = this.clients.length;
    let lastError: Error;
    
    for (let attempt = 0; attempt < maxAttempts; attempt++) {
      try {
        const client = this.getNextClient();
        return await client.users.get(userId);
      } catch (error) {
        lastError = error;
        console.warn(`Client attempt ${attempt + 1} failed:`, error.message);
      }
    }
    
    throw lastError;
  }
}

Rate Limiting

Client-Side Rate Limiting
import asyncio
from datetime import datetime, timedelta
from collections import deque

class RateLimiter:
    def __init__(self, max_requests: int, time_window: int):
        self.max_requests = max_requests
        self.time_window = time_window
        self.requests = deque()
        self.lock = asyncio.Lock()
    
    async def acquire(self):
        async with self.lock:
            now = datetime.utcnow()
            
            # Remove requests outside the time window
            while (self.requests and 
                   now - self.requests[0] > timedelta(seconds=self.time_window)):
                self.requests.popleft()
            
            # Check if we can make a request
            if len(self.requests) >= self.max_requests:
                # Calculate wait time
                oldest_request = self.requests[0]
                wait_time = (oldest_request + timedelta(seconds=self.time_window) - now).total_seconds()
                await asyncio.sleep(wait_time)
                return await self.acquire()
            
            # Record this request
            self.requests.append(now)

class RateLimitedNudjClient:
    def __init__(self, nudj_client: NudjClient, rate_limiter: RateLimiter):
        self.nudj_client = nudj_client
        self.rate_limiter = rate_limiter
    
    async def get_user(self, user_id: str):
        await self.rate_limiter.acquire()
        return await self.nudj_client.users.get(user_id)

Testing Patterns

Integration Testing

Comprehensive Integration Tests
describe('Nudj Integration', () => {
  let testClient: NudjClient;
  let testUser: User;
  
  beforeAll(async () => {
    testClient = new NudjClient({
      apiKey: process.env.NUDJ_TEST_API_KEY,
      baseURL: process.env.NUDJ_TEST_BASE_URL
    });
  });
  
  describe('User Management', () => {
    it('should create, update, and delete user', async () => {
      // Create user
      const userData = {
        email: 'test@example.com',
        username: 'testuser',
        displayName: 'Test User'
      };
      
      testUser = await testClient.users.create(userData);
      expect(testUser.email).toBe(userData.email);
      
      // Update user
      const updatedData = { displayName: 'Updated User' };
      const updatedUser = await testClient.users.update(testUser.id, updatedData);
      expect(updatedUser.displayName).toBe(updatedData.displayName);
      
      // Delete user
      await testClient.users.delete(testUser.id);
      
      // Verify deletion
      await expect(testClient.users.get(testUser.id))
        .rejects.toThrow('User not found');
    });
  });
  
  describe('Challenge Workflow', () => {
    it('should complete full challenge workflow', async () => {
      // Create challenge
      const challenge = await testClient.challenges.create({
        title: 'Test Challenge',
        communityId: 'test-community'
      });
      
      // User starts challenge
      await testClient.challenges.start(challenge.id, testUser.id);
      
      // User completes challenge
      const completion = await testClient.challenges.complete(
        challenge.id, 
        testUser.id,
        { score: 100 }
      );
      
      expect(completion.completed).toBe(true);
      expect(completion.score).toBe(100);
    });
  });
});

Mock Integration

Testing with Mocks
import pytest
from unittest.mock import AsyncMock, patch
from nudj import NudjClient

@pytest.fixture
def mock_nudj_client():
    client = AsyncMock(spec=NudjClient)
    
    # Mock user operations
    client.users.get.return_value = {
        'id': 'user-123',
        'email': 'test@example.com',
        'username': 'testuser'
    }
    
    client.users.create.return_value = {
        'id': 'user-123',
        'email': 'test@example.com',
        'username': 'testuser'
    }
    
    return client

async def test_user_service_with_mock(mock_nudj_client):
    service = UserService(mock_nudj_client)
    
    user = await service.get_user('user-123')
    
    assert user['email'] == 'test@example.com'
    mock_nudj_client.users.get.assert_called_once_with('user-123')

Best Practices Summary

Architecture Principles

Design Guidelines
  1. Idempotency: Design all operations to be safely retried
  2. Fault Tolerance: Handle failures gracefully with circuit breakers
  3. Observability: Implement comprehensive logging and monitoring
  4. Security: Use secure authentication and validate all inputs
  5. Performance: Implement caching and rate limiting appropriately

Integration Checklist

Pre-Production Checklist
  • Authentication configured securely
  • Error handling and retries implemented
  • Logging and monitoring in place
  • Rate limiting configured
  • Integration tests passing
  • Performance testing completed
  • Security review conducted
  • Documentation updated

Common Pitfalls

Avoid These Mistakes
  • Not implementing proper error handling
  • Missing idempotency considerations
  • Insufficient logging for debugging
  • Hard-coding API keys in source code
  • Not handling rate limits appropriately
  • Ignoring webhook signature verification
  • Missing correlation IDs for request tracing

Getting Started

Choose the integration pattern that best fits your use case:

Simple Integration

Start with the Request-Response pattern for straightforward, real-time operations.

Scalable Architecture

Use Event-Driven patterns for high-volume, resilient integrations.

Enterprise Setup

Implement comprehensive patterns with monitoring, caching, and fault tolerance.
Ready to implement these patterns? Check our API reference and webhook implementation guide for detailed implementation guidance.
I