Skip to content

Best Practices ​

Follow these best practices to build robust, scalable, and efficient applications with DialogueDB.

Error Handling ​

Implement Comprehensive Error Handling ​

Always handle errors gracefully in production applications:

typescript
import { DialogueDB } from 'dialogue-db';

const db = new DialogueDB({ apiKey: process.env.DIALOGUE_DB_API_KEY });

async function saveMessageWithRetry(dialogueId: string, content: string, maxRetries = 3) {
  const dialogue = await db.getDialogue(dialogueId);

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const message = await dialogue.saveMessage({
        role: 'user',
        content
      });
      return message;
    } catch (error) {
      // Handle specific error types
      if (error.status === 401) {
        throw new Error('Authentication failed - check your API key');
      } else if (error.status === 404) {
        throw new Error(`Dialogue ${dialogueId} not found`);
      } else if (error.status === 429) {
        // Rate limited - wait progressively longer between retries
        if (attempt < maxRetries) {
          const delay = Math.pow(2, attempt) * 1000;
          await new Promise(resolve => setTimeout(resolve, delay));
          continue;
        }
        throw new Error('Rate limit exceeded - try again later');
      } else if (error.status >= 500) {
        // Server error - retry
        if (attempt < maxRetries) {
          await new Promise(resolve => setTimeout(resolve, 1000));
          continue;
        }
        throw new Error('Server error - please try again later');
      }

      // Unknown error
      throw error;
    }
  }
}

Common Error Scenarios ​

Error CodeMeaningAction
400Bad RequestValidate input before sending
401UnauthorizedCheck API key
404Not FoundVerify resource ID
429Rate LimitedWait and retry (progressively longer delays)
500Server ErrorRetry with backoff

Performance Optimization ​

Pagination Strategy ​

Always use pagination for large datasets:

typescript
async function getAllDialogues() {
  const allDialogues = [];
  let nextToken = null;

  do {
    const { items, next } = await db.listDialogues({
      limit: 20,
      next: nextToken
    });

    allDialogues.push(...items);
    nextToken = next;
  } while (nextToken);

  return allDialogues;
}

// Better: Process in chunks
async function processDialoguesInChunks(processor: (dialogues: any[]) => Promise<void>) {
  let nextToken = null;

  do {
    const { items, next } = await db.listDialogues({
      limit: 20,
      next: nextToken
    });

    // Process this batch
    await processor(items);

    nextToken = next;
  } while (nextToken);
}

Batch Message Creation ​

When adding multiple messages, consider batching:

typescript
// Avoid: Sequential creation
async function addMessagesSequential(dialogue: any, messages: any[]) {
  for (const msg of messages) {
    await dialogue.saveMessage(msg);
  }
}

// Better: Use the bulk method
async function addMessagesBulk(dialogue: any, messages: any[]) {
  await dialogue.saveMessages(messages);
}

// Best: Use dialogue creation with messages array
async function createDialogueWithHistory(messages: any[]) {
  return await db.createDialogue({
    messages: messages
  });
}

Caching Strategies ​

Cache frequently accessed dialogues:

typescript
class DialogueCache {
  private cache: Map<string, { dialogue: any; timestamp: number }> = new Map();
  private ttl: number = 5 * 60 * 1000; // 5 minutes

  async get(dialogueId: string): Promise<any> {
    const cached = this.cache.get(dialogueId);

    if (cached && Date.now() - cached.timestamp < this.ttl) {
      return cached.dialogue;
    }

    const dialogue = await db.getDialogue(dialogueId);

    this.cache.set(dialogueId, {
      dialogue,
      timestamp: Date.now()
    });

    return dialogue;
  }

  invalidate(dialogueId: string) {
    this.cache.delete(dialogueId);
  }
}

Rate Limiting ​

Implement Rate Limit Awareness ​

Track and respect rate limits:

typescript
class RateLimitedClient {
  private db: DialogueDB;
  private requestCount: number = 0;
  private windowStart: number = Date.now();
  private limit: number = 1000; // requests per minute

  constructor(apiKey: string) {
    this.db = new DialogueDB({ apiKey });
  }

  async request<T>(fn: () => Promise<T>): Promise<T> {
    // Reset window if minute has passed
    const now = Date.now();
    if (now - this.windowStart > 60000) {
      this.requestCount = 0;
      this.windowStart = now;
    }

    // Check if we're approaching limit
    if (this.requestCount >= this.limit) {
      const waitTime = 60000 - (now - this.windowStart);
      await new Promise(resolve => setTimeout(resolve, waitTime));
      this.requestCount = 0;
      this.windowStart = Date.now();
    }

    this.requestCount++;
    return await fn();
  }
}

// Usage
const rateLimitedClient = new RateLimitedClient(process.env.DIALOGUE_DB_API_KEY);

const dialogue = await rateLimitedClient.request(() =>
  db.createDialogue({ messages: [{ role: 'user', content: 'Hello' }] })
);

Data Management ​

Message Size Optimization ​

Keep messages concise when possible:

typescript
// Avoid: Storing entire documents
await dialogue.saveMessage({
  role: 'assistant',
  content: entireDocument // 50KB of text
});

// Better: Store summary with reference
await dialogue.saveMessage({
  role: 'assistant',
  content: 'Here is a summary of the document...',
  metadata: {
    documentId: 'doc_123',
    documentUrl: 'https://example.com/docs/123'
  }
});

Conversation Lifecycle Management ​

End conversations when appropriate:

typescript
async function manageConversationLifecycle(dialogueId: string) {
  const dialogue = await db.getDialogue(dialogueId);

  // End inactive conversations
  const lastMessage = dialogue.lastMessageCreated;
  const hoursSinceLastMessage = (Date.now() - new Date(lastMessage).getTime()) / (1000 * 60 * 60);

  if (hoursSinceLastMessage > 24) {
    await dialogue.end();
    console.log(`Ended inactive dialogue ${dialogueId}`);
  }
}

State Management ​

Keep state objects focused and minimal:

typescript
// Avoid: Bloated state
await dialogue.saveState({
  entireConversationHistory: messages, // Redundant
  userProfile: completeUserObject,     // Too large
  sessionData: everythingEver          // Unfocused
});

// Better: Focused state
await dialogue.saveState({
  currentIntent: 'book_appointment',
  step: 'date_selection',
  collectedData: {
    date: '2025-01-20',
    time: null
  }
});

Security ​

API Key Management ​

Never expose API keys in client-side code:

typescript
// NEVER DO THIS
const db = new DialogueDB({
  apiKey: 'sk_live_abc123...' // Exposed in browser!
});

// Use server-side proxy
// Backend (Express.js)
app.post('/api/chat', async (req, res) => {
  const db = new DialogueDB({
    apiKey: process.env.DIALOGUE_DB_API_KEY // Server-side only
  });

  const dialogue = await db.getDialogue(req.body.dialogueId);
  const result = await dialogue.saveMessage(req.body.message);

  res.json(result);
});

// Frontend
fetch('/api/chat', {
  method: 'POST',
  body: JSON.stringify({ dialogueId, message })
});

Input Validation ​

Always validate user input:

typescript
function validateMessage(content: string): boolean {
  // Check length
  if (content.length === 0 || content.length > 100000) {
    throw new Error('Message content must be between 1 and 100,000 characters');
  }

  // Check for malicious content
  if (containsMaliciousPatterns(content)) {
    throw new Error('Invalid message content');
  }

  return true;
}

async function createSafeMessage(dialogue: any, userInput: string) {
  validateMessage(userInput);

  return await dialogue.saveMessage({
    role: 'user',
    content: userInput
  });
}

Monitoring & Logging ​

Implement Request Logging ​

Track API usage for debugging and monitoring:

typescript
class LoggingClient {
  private db: DialogueDB;
  private logger: any;

  constructor(apiKey: string, logger: any) {
    this.db = new DialogueDB({ apiKey });
    this.logger = logger;
  }

  async saveMessage(dialogueId: string, message: any) {
    const startTime = Date.now();

    try {
      this.logger.info('Saving message', {
        dialogueId,
        role: message.role,
        contentLength: message.content.length
      });

      const dialogue = await this.db.getDialogue(dialogueId);
      const result = await dialogue.saveMessage(message);

      this.logger.info('Message saved successfully', {
        dialogueId,
        messageId: result.id,
        duration: Date.now() - startTime
      });

      return result;
    } catch (error) {
      this.logger.error('Failed to save message', {
        dialogueId,
        error: error.message,
        duration: Date.now() - startTime
      });

      throw error;
    }
  }
}

Track Metrics ​

Monitor key performance indicators:

typescript
class MetricsTracker {
  private metrics = {
    requestCount: 0,
    errorCount: 0,
    averageResponseTime: 0,
    rateLimitHits: 0
  };

  trackRequest(duration: number, success: boolean) {
    this.metrics.requestCount++;
    if (!success) this.metrics.errorCount++;

    // Update average response time
    this.metrics.averageResponseTime =
      (this.metrics.averageResponseTime * (this.metrics.requestCount - 1) + duration) /
      this.metrics.requestCount;
  }

  trackRateLimit() {
    this.metrics.rateLimitHits++;
  }

  getMetrics() {
    return {
      ...this.metrics,
      errorRate: this.metrics.errorCount / this.metrics.requestCount,
      successRate: (this.metrics.requestCount - this.metrics.errorCount) / this.metrics.requestCount
    };
  }
}

Testing ​

Mock DialogueDB in Tests ​

typescript
// __mocks__/dialogue-db.ts
export class DialogueDB {
  createDialogue = jest.fn();
  getDialogue = jest.fn();
  listDialogues = jest.fn();
}

// Mock Dialogue instance
export const mockDialogue = {
  saveMessage: jest.fn(),
  saveMessages: jest.fn(),
  loadMessages: jest.fn(),
  saveState: jest.fn(),
  save: jest.fn(),
};

// yourCode.test.ts
jest.mock('dialogue-db');

describe('Chat Handler', () => {
  it('should save message', async () => {
    const db = new DialogueDB({});
    db.getDialogue.mockResolvedValue(mockDialogue);
    mockDialogue.saveMessage.mockResolvedValue({
      id: 'msg_123',
      content: 'Hello'
    });

    const result = await handleUserMessage('dialogue_123', 'Hello');

    expect(db.getDialogue).toHaveBeenCalledWith('dialogue_123');
    expect(mockDialogue.saveMessage).toHaveBeenCalledWith(
      expect.objectContaining({ content: 'Hello' })
    );
  });
});

Checklist ​

Before deploying to production:

  • [ ] API keys stored securely (environment variables/secrets manager)
  • [ ] Error handling implemented with retries
  • [ ] Rate limiting awareness in place
  • [ ] Pagination used for all list operations
  • [ ] Input validation on all user-provided data
  • [ ] Logging and monitoring configured
  • [ ] Inactive conversations cleaned up regularly
  • [ ] Long conversations ended when complete
  • [ ] Tests cover error scenarios
  • [ ] Client-side code uses server proxy (never exposes API keys)

Next Steps ​