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:
Flutter
JavaScript
Python
Go
// 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!);
// Bad - hardcoded credentials
const config = {
apiKey: "hardcoded-key", // DON'T DO THIS
};
// Good - use environment variables
const config = {
apiKey: process.env.COCOBASE_API_KEY,
};
// Good - use .env file
import dotenv from 'dotenv';
dotenv.config();
const config = {
apiKey: process.env.COCOBASE_API_KEY,
};
# Bad - hardcoded credentials
config = CocobaseConfig(
api_key="hardcoded-key" # DON'T DO THIS
)
# Good - use environment variables
import os
config = CocobaseConfig(
api_key=os.getenv('COCOBASE_API_KEY')
)
// Bad - hardcoded credentials
config := cocobase.Config{
APIKey: "hardcoded-key", // DON'T DO THIS
}
// Good - use environment variables
config := cocobase.Config{
APIKey: os.Getenv("COCOBASE_API_KEY"),
}
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:
Python Cloud Functions
Flutter
JavaScript
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}
Future<void> performSecureOperation() async {
// Check authentication first
final isAuth = await db.isAuthenticated();
if (!isAuth) {
throw Exception('Authentication required');
}
// Proceed with operation
await db.createDocument("posts", data);
}
async function performSecureOperation() {
// Check authentication first
const isAuth = await db.isAuthenticated();
if (!isAuth) {
throw new Error('Authentication required');
}
// Proceed with operation
await db.createDocument('posts', data);
}
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}
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}
Future<void> createPost(String title, String content) async {
// Validate title
if (title.trim().isEmpty) {
throw ArgumentError('Title is required');
}
if (title.length > 200) {
throw ArgumentError('Title too long (max 200 characters)');
}
// Validate content
if (content.trim().isEmpty) {
throw ArgumentError('Content is required');
}
if (content.length > 10000) {
throw ArgumentError('Content too long (max 10000 characters)');
}
// Create post
await db.createDocument('posts', {
'title': title.trim(),
'content': content.trim(),
});
}
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
Flutter
JavaScript
Python
Go
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');
}
}
async function performOperation() {
try {
const result = await db.createDocument('posts', data);
console.log('Success:', result.id);
} catch (error) {
if (error.response) {
switch (error.response.status) {
case 400:
console.error('Bad request - check your data');
break;
case 401:
console.error('Unauthorized - login required');
await handleSessionExpired();
break;
case 403:
console.error('Forbidden - insufficient permissions');
break;
case 404:
console.error('Not found');
break;
case 429:
console.error('Rate limited - slow down');
await sleep(5000);
break;
default:
console.error('Error:', error.message);
}
} else {
console.error('Unexpected error:', error);
}
}
}
def main():
try:
user_id = req.get("user_id")
if not user_id:
return {"error": "user_id is required"}, 400
posts = db.query("posts",
author_id=user_id,
limit=20
)
return {"posts": posts["data"]}
except ValueError as e:
return {"error": str(e)}, 400
except Exception as e:
print(f"Error: {str(e)}")
return {"error": "Internal server error"}, 500
func performOperation() error {
doc, err := client.CreateDocument(ctx, "posts", data)
if err != nil {
if apiErr, ok := err.(*cocobase.APIError); ok {
switch apiErr.StatusCode {
case 400:
return fmt.Errorf("bad request: %w", err)
case 401:
return fmt.Errorf("unauthorized: %w", err)
case 403:
return fmt.Errorf("forbidden: %w", err)
case 404:
return fmt.Errorf("not found: %w", err)
case 429:
time.Sleep(5 * time.Second)
return fmt.Errorf("rate limited: %w", err)
default:
return fmt.Errorf("error: %w", err)
}
}
return err
}
fmt.Printf("Success: %s\n", doc.ID)
return nil
}
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
Code Quality
Data Management
Monitoring
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