Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.cocobase.buzz/llms.txt

Use this file to discover all available pages before exploring further.

Best Practices

Master the art of building secure, performant, and maintainable applications with CocoBase.

Security Best Practices

API Key Management

Never Hard-Code API Keys:
// Bad - hardcoded credentials
final config = CocobaseConfig(
  apiKey: "hardcoded-key",  // DON'T DO THIS
);

// Good - use environment variables
const apiKey = String.fromEnvironment('COCOBASE_API_KEY');
final config = CocobaseConfig(apiKey: apiKey);

// Good - load from secure storage
final prefs = await SharedPreferences.getInstance();
final apiKey = prefs.getString('cocobase_api_key');
final config = CocobaseConfig(apiKey: apiKey!);

Rotate API Keys Regularly:
  • Generate new API keys every 90 days
  • Revoke old keys after migration
  • Use different keys for development, staging, and production
  • Never commit API keys to version control
Add to .gitignore:
# .gitignore
.env
.env.local
config/secrets.json
**/secrets/

Authentication Security

Always Verify User Authentication:
def main():
    # Check if user is authenticated
    user = req.user

    if not user:
        return {"error": "Authentication required"}, 401

    # Proceed with authenticated operations
    return {"user_id": user.id}

Implement Role-Based Access Control:
def main():
    user = req.user

    if not user:
        return {"error": "Authentication required"}, 401

    # Check user roles
    if 'admin' not in user.roles:
        return {"error": "Forbidden - admin access required"}, 403

    # Perform admin operation
    result = perform_admin_action()
    return {"result": result}

Input Validation

Always Validate User Input:
def main():
    email = req.get('email', '').strip()
    password = req.get('password', '')

    # Validate email
    if not email or '@' not in email:
        return {"error": "Invalid email format"}, 400

    # Validate password strength
    if len(password) < 8:
        return {"error": "Password must be at least 8 characters"}, 400

    # Proceed with registration
    user = db.create_app_user(email, password)
    return {"user": user}

Sanitize User Input:
function sanitizeInput(input) {
  // Remove HTML tags
  return input.replace(/<[^>]*>/g, "");
}

function createPost(title, content) {
  const sanitizedTitle = sanitizeInput(title.trim());
  const sanitizedContent = sanitizeInput(content.trim());

  return db.createDocument("posts", {
    title: sanitizedTitle,
    content: sanitizedContent,
  });
}

Data Modeling Best Practices

Schema Design

Use Clear, Descriptive Field Names:
// Good - clear intent
{
  "author_id": "user-123",        // Obviously a user reference
  "product_ids": ["p1", "p2"],    // Obviously multiple products
  "is_published": true,           // Boolean clarity
}

// Bad - ambiguous names
{
  "related": "...",      // Related to what?
  "ids": ["..."],        // IDs of what?
  "flag": true,          // Flag for what?
}
Normalize Data Appropriately:
// Good - normalized structure
const user = {
  id: "user-123",
  name: "John Doe",
  company_id: "company-456", // Reference to company
};

const company = {
  id: "company-456",
  name: "TechCorp",
  address: "123 Main St",
};

// Bad - denormalized (duplicated data)
const user = {
  id: "user-123",
  name: "John Doe",
  company_name: "TechCorp", // Duplicated
  company_address: "123 Main St", // Duplicated
};
Use Appropriate Data Types:
# Good - appropriate types
document = {
    "price": 29.99,           # Number, not string
    "quantity": 5,            # Integer
    "is_available": True,     # Boolean, not string
    "tags": ["tech", "new"],  # Array
    "created_at": "2024-01-15T10:30:00Z"  # ISO 8601 timestamp
}

# Bad - wrong types
document = {
    "price": "29.99",         # String instead of number
    "quantity": "5",          # String instead of int
    "is_available": "true",   # String instead of boolean
    "tags": "tech,new",       # String instead of array
    "created_at": "01/15/2024"  # Non-standard date format
}

Relationship Management

Follow Naming Conventions:
// Single references use id suffix
{
  "author_id": "user-123",
  "category_id": "cat-456",
  "company_id": "comp-789"
}

// Multiple references use _ids suffix
{
  "tag_ids": ["tag-1", "tag-2"],
  "follower_ids": ["user-4", "user-5"],
  "product_ids": ["prod-1", "prod-2", "prod-3"]
}
Cache Relationship Counts:
# Store counts for quick access
user_data = {
    "followers_count": 1500,  # Update when changed
    "following_count": 300,
    "posts_count": 42
}

# Instead of counting every time
# followers = db.get_user_relationships(user_id, "followers")
# count = followers["total"]  # Expensive!

Efficient Queries

Use Specific Queries:
// Good - specific query
final query = QueryBuilder()
  .where('status', 'published')
  .where('author_id', userId)
  .limit(50);

final docs = await db.listDocuments("posts", queryBuilder: query);

// Bad - fetching everything then filtering
final all = await db.listDocuments("posts");
final filtered = all.where((doc) =>
  doc.data['status'] == 'published' &&
  doc.data['author_id'] == userId
).toList();
Always Set Limits:
# Good - limited results
posts = db.list_documents("posts",
    status="published",
    limit=20  # Prevent loading too much
)

# Bad - no limit (could return millions)
posts = db.list_documents("posts",
    status="published"
)
Use Pagination:
// Good - paginate results
async function getAllUsers() {
  const users = [];
  let page = 1;
  const perPage = 100;
  let hasMore = true;

  while (hasMore) {
    const result = await db.listDocuments("users", {
      filters: {
        limit: perPage,
        offset: (page - 1) * perPage,
      }
    });

    users.push(...result);
    hasMore = result.length === perPage;
    page++;
  }

  return users;
}

// Bad - fetch everything at once
const users = await db.listDocuments("users");
Select Only Needed Fields:
// Good - select specific fields
final docs = await db.listDocuments(
  "books",
  queryBuilder: QueryBuilder()
    .select('id')
    .select('title')
    .select('price')
    .limit(100),
);

// Bad - fetch all fields
final docs = await db.listDocuments("books");
Use Populate Wisely:
# Good - selective population
posts = db.query("posts",
    populate=["author"],  # Only what's needed
    limit=20
)

# Bad - populate everything
posts = db.query("posts",
    populate=["author", "category", "tags", "comments", "likes"],  # Too much
    limit=20
)

Error Handling Patterns

Comprehensive Error Handling

Future<void> performOperation() async {
  try {
    final result = await db.createDocument("posts", data);
    print('Success: ${result.id}');
  } on DioException catch (e) {
    if (e.response?.statusCode == 400) {
      print('Bad request - check your data');
    } else if (e.response?.statusCode == 401) {
      print('Unauthorized - login required');
      await handleSessionExpired();
    } else if (e.response?.statusCode == 403) {
      print('Forbidden - insufficient permissions');
    } else if (e.response?.statusCode == 404) {
      print('Not found');
    } else if (e.response?.statusCode == 429) {
      print('Rate limited - slow down');
      await Future.delayed(Duration(seconds: 5));
    } else {
      print('Error: ${e.message}');
    }
  } catch (e) {
    print('Unexpected error: $e');
  }
}

Retry Logic

Implement Exponential Backoff:
Future<T> withRetry<T>(
  Future<T> Function() operation, {
  int maxAttempts = 3,
}) async {
  for (int i = 0; i < maxAttempts; i++) {
    try {
      return await operation();
    } catch (e) {
      if (i == maxAttempts - 1) rethrow;

      // Exponential backoff: 2^i seconds
      await Future.delayed(Duration(seconds: 1 << i));
    }
  }
  throw Exception('Max retries exceeded');
}

// Use it
final books = await withRetry(() => db.listDocuments<Book>("books"));

Rate Limiting

Respect API Limits

Implement Client-Side Rate Limiting:
class RateLimiter {
  constructor(maxRequests = 100, windowMs = 60000) {
    this.requests = [];
    this.maxRequests = maxRequests;
    this.windowMs = windowMs;
  }

  async acquire() {
    const now = Date.now();

    // Remove old requests outside the window
    this.requests = this.requests.filter((time) => now - time < this.windowMs);

    // Check if we're at the limit
    if (this.requests.length >= this.maxRequests) {
      const oldestRequest = this.requests[0];
      const waitTime = this.windowMs - (now - oldestRequest);
      await sleep(waitTime);
      return this.acquire(); // Retry
    }

    this.requests.push(now);
  }
}

// Use it
const limiter = new RateLimiter(100, 60000); // 100 requests per minute

async function makeRequest() {
  await limiter.acquire();
  return db.listDocuments("posts");
}
Batch Requests When Possible:
# Good - batch operations
def import_books(books_data):
    batch_size = 100
    for i in range(0, len(books_data), batch_size):
        batch = books_data[i:i + batch_size]
        db.bulk_create_documents("books", batch)

# Bad - individual requests
def import_books(books_data):
    for book in books_data:
        db.create_document("books", book)  # Too many requests!

Production Checklist

Before deploying to production, verify:

Security

  • API keys are stored securely (environment variables, not hardcoded)
  • HTTPS is enabled for all API calls
  • User authentication is properly validated
  • Input validation is implemented
  • Sensitive data is encrypted
  • Rate limiting is configured
  • Error messages don’t leak sensitive information

Performance

  • Indexes are created on frequently queried fields
  • Pagination is implemented for large datasets
  • Query limits are set appropriately
  • Caching is implemented where appropriate
  • Only necessary fields are selected/populated
  • Batch operations are used for bulk actions

Code Quality

  • Error handling is comprehensive
  • Logging is implemented
  • Code follows naming conventions
  • Tests are written and passing
  • Documentation is up to date
  • Type safety is enforced (where applicable)

Data Management

  • Database schema is properly designed
  • Relationships are clearly defined
  • Backup strategy is in place
  • Data migration plan exists
  • Soft deletes are used where appropriate

Monitoring

  • Error tracking is set up (e.g., Sentry)
  • Performance monitoring is enabled
  • Logs are centralized and searchable
  • Alerts are configured for critical issues

Performance Tips

1. Use Connection Pooling

// Good - custom HTTP client with connection pooling
httpClient := &http.Client{
    Timeout: 30 * time.Second,
    Transport: &http.Transport{
        MaxIdleConns:        100,
        MaxIdleConnsPerHost: 10,
        IdleConnTimeout:     90 * time.Second,
    },
}

client := cocobase.NewClient(cocobase.Config{
    APIKey:     apiKey,
    HTTPClient: httpClient,
})

2. Minimize Network Requests

// Bad - N+1 query problem
final posts = await db.listDocuments("posts", limit: 10);
for (var post in posts) {
  final author = await db.getDocument("users", post.data['author_id']);
  print('Author: ${author.data['name']}');
}

// Good - use populate
final posts = await db.listDocuments(
  "posts",
  queryBuilder: QueryBuilder().populate('author'),
  limit: 10
);
for (var post in posts) {
  print('Author: ${post.data['author']['name']}');
}

3. Use Bulk Operations

// Bad - individual creates
for (const book of books) {
  await db.createDocument("books", book);
}

// Good - batch create
await db.batchCreateDocuments("books", books);

4. Implement Lazy Loading

Load data only when needed to improve initial load time.

5. Optimize Images and Files

  • Compress images before upload
  • Use appropriate formats (WebP for web)
  • Implement lazy loading for images
  • Use CDN for static assets

Testing Best Practices

Unit Testing

// Flutter example
void main() {
  group('Book operations', () {
    late Cocobase db;

    setUp(() {
      final config = CocobaseConfig(apiKey: 'test-key');
      db = Cocobase(config);
    });

    test('should create a book', () async {
      final book = await db.createDocument('books', {
        'title': 'Test Book',
        'author': 'Test Author',
        'price': 19.99,
      });

      expect(book.data['title'], 'Test Book');
      expect(book.data['price'], 19.99);
    });

    test('should handle validation errors', () async {
      expect(
        () => db.createDocument('books', {}),
        throwsA(isA<ValidationError>()),
      );
    });
  });
}

Next Steps