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 token');
      } 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 token
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: 100,
      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: 100,
      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

Token Management

Never expose tokens 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 tokens 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 compacted or ended
  • [ ] Tests cover error scenarios
  • [ ] Client-side code uses server proxy (never exposes tokens)

Next Steps

Built with DialogueDB