diff --git a/README.md b/README.md index 03c7d25..41d97d5 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Documentation is available at https://kiro.dev/docs/powers/ ## Available powers + ### aws-agentcore **Build an agent with Amazon Bedrock AgentCore** - Build, test, and deploy AI agents using AWS Bedrock AgentCore with local development workflow. Amazon Bedrock AgentCore is an agentic platform for building, deploying, and operating effective agents. @@ -104,6 +105,13 @@ Documentation is available at https://kiro.dev/docs/powers/ --- +### appwrite +**Appwrite Backend Platform** - Build backend services with Appwrite - databases, authentication, storage, functions, and messaging for web and mobile apps. + +**MCP Servers:** appwrite-api, appwrite-docs + +--- + ## Security diff --git a/appwrite/POWER.md b/appwrite/POWER.md new file mode 100644 index 0000000..d15cbad --- /dev/null +++ b/appwrite/POWER.md @@ -0,0 +1,775 @@ +--- +name: "appwrite" +displayName: "Appwrite Backend Platform" +description: "Build backend services with Appwrite - databases, authentication, storage, functions, and messaging for web and mobile apps" +keywords: ["appwrite", "backend", "database", "auth", "authentication", "storage", "functions", "serverless", "baas", "api", "users", "teams", "messaging"] +author: "Appwrite" +--- + +# Onboarding + +Before using Appwrite MCP, ensure you have completed the following steps. + +## Step 1: Validate Prerequisites + +Check that the required tools are installed: + +**For API Server:** +- **uv**: Required to run the Appwrite API MCP server + - Verify with: `uv --version` + - Install if missing: Follow instructions at https://docs.astral.sh/uv/getting-started/installation/ + - **CRITICAL**: If uv is not installed, DO NOT proceed with API server setup + +**For Docs Server:** +- **Node.js and npm**: Required for the documentation MCP server + - Verify with: `node --version` and `npm --version` + - Install if missing: Download from https://nodejs.org/ + +## Step 2: Configure Appwrite Credentials + +You need an Appwrite project with API credentials: + +1. **Create or access your Appwrite project** at https://cloud.appwrite.io +2. **Get your Project ID** from the Settings page +3. **Create an API Key** with necessary scopes: + - Navigate to Settings → API Keys + - Click "Create API Key" + - Enable required scopes (databases, users, storage, functions, etc.) + - Copy the generated API key +4. **Note your region and endpoint**: + - Format: `https://.cloud.appwrite.io/v1` + - Common regions: `nyc`, `fra`, `sfo`, `blr`, `sgp`, `syd` + +**Environment Variables:** + +Set these environment variables on your system or add them to your MCP configuration: + +- `APPWRITE_PROJECT_ID` - Your project ID +- `APPWRITE_API_KEY` - Your API key (keep secure!) +- `APPWRITE_ENDPOINT` - Your endpoint URL (e.g., `https://nyc.cloud.appwrite.io/v1`) + +## Step 3: Choose MCP Servers + +This power provides two MCP servers: + +- **appwrite-api**: Interact with Appwrite services (databases, users, storage, functions, etc.) +- **appwrite-docs**: Query Appwrite documentation for guidance and examples + +You can enable one or both depending on your needs. The API server is essential for building applications, while the docs server helps with learning and troubleshooting. + +## Step 4: Add Development Hooks (Optional) + +Create a hook to validate database operations. Save to `.kiro/hooks/appwrite-db-check.kiro.hook`: + +```json +{ + "enabled": true, + "name": "Appwrite Database Validation", + "description": "Validates database schema and queries when database files are modified", + "version": "1", + "when": { + "type": "fileEdited", + "patterns": [ + "**/appwrite/**/*.ts", + "**/appwrite/**/*.js", + "**/lib/appwrite/**", + "**/src/appwrite/**" + ] + }, + "then": { + "type": "askAgent", + "prompt": "Review the Appwrite database code for best practices: proper error handling, type safety, query optimization, and security permissions" + } +} +``` + +# Overview + +Appwrite is an open-source backend-as-a-service platform that provides authentication, databases, storage, functions, and messaging. This power gives you access to both the Appwrite API and comprehensive documentation through MCP servers. + +**Key capabilities:** + +- **Databases**: Create collections, manage documents, query data with powerful filters +- **Authentication**: User management, OAuth providers, sessions, teams +- **Storage**: File uploads, downloads, image transformations, permissions +- **Functions**: Serverless functions with multiple runtimes +- **Messaging**: Email, SMS, and push notifications +- **Real-time**: WebSocket subscriptions for live data updates +- **Sites**: Deploy static sites and SSR applications + +**Perfect for:** +- Building full-stack web and mobile applications +- Creating serverless backends +- Managing user authentication and authorization +- Storing and serving files with CDN +- Implementing real-time features +- Deploying static sites and server-side rendered apps + +## Available MCP Servers + +### appwrite-api + +**Package:** `mcp-server-appwrite` (via uvx) +**Connection:** Python-based MCP server +**Authentication:** API key with project ID and endpoint + +**Command-line Arguments:** + +By default, only Database tools are enabled. Use these flags to enable additional APIs: + +- `--tables-db` - TablesDB API (relational database operations) +- `--users` - Users API (user management and authentication) +- `--teams` - Teams API (team and membership management) +- `--storage` - Storage API (file upload, download, management) +- `--functions` - Functions API (serverless function deployment) +- `--messaging` - Messaging API (email, SMS, push notifications) +- `--locale` - Locale API (country, language, currency data) +- `--avatars` - Avatars API (generate avatars and QR codes) +- `--sites` - Sites API (deploy static sites and SSR apps) +- `--databases` - Legacy Databases API +- `--all` - Enable all Appwrite APIs + +**Important:** Only enable the APIs you need. Each enabled API adds tools to the LLM context, reducing available context window. Start with databases and add others as needed. + +**Available Tools (varies by enabled APIs):** + +**Databases API** (enabled by default): +- `mcp_appwrite_api_databases_create` - Create a new database +- `mcp_appwrite_api_databases_list` - List all databases +- `mcp_appwrite_api_databases_get` - Get database details +- `mcp_appwrite_api_databases_update` - Update database properties +- `mcp_appwrite_api_databases_delete` - Delete a database +- `mcp_appwrite_api_databases_create_collection` - Create a collection +- `mcp_appwrite_api_databases_list_collections` - List collections +- `mcp_appwrite_api_databases_get_collection` - Get collection details +- `mcp_appwrite_api_databases_update_collection` - Update collection +- `mcp_appwrite_api_databases_delete_collection` - Delete collection +- `mcp_appwrite_api_databases_create_document` - Create a document +- `mcp_appwrite_api_databases_list_documents` - Query documents +- `mcp_appwrite_api_databases_get_document` - Get document by ID +- `mcp_appwrite_api_databases_update_document` - Update document +- `mcp_appwrite_api_databases_delete_document` - Delete document +- Plus attribute, index, and transaction management tools + +**Users API** (with `--users` flag): +- `mcp_appwrite_api_users_create` - Create a new user +- `mcp_appwrite_api_users_list` - List all users +- `mcp_appwrite_api_users_get` - Get user details +- `mcp_appwrite_api_users_update_email` - Update user email +- `mcp_appwrite_api_users_update_password` - Update user password +- `mcp_appwrite_api_users_delete` - Delete a user +- Plus session, preferences, and MFA management tools + +**Storage API** (with `--storage` flag): +- `mcp_appwrite_api_storage_create_bucket` - Create storage bucket +- `mcp_appwrite_api_storage_list_buckets` - List all buckets +- `mcp_appwrite_api_storage_create_file` - Upload a file +- `mcp_appwrite_api_storage_list_files` - List files in bucket +- `mcp_appwrite_api_storage_get_file` - Get file metadata +- `mcp_appwrite_api_storage_get_file_download` - Download file +- `mcp_appwrite_api_storage_get_file_preview` - Get image preview +- `mcp_appwrite_api_storage_delete_file` - Delete a file + +**Functions API** (with `--functions` flag): +- `mcp_appwrite_api_functions_create` - Create a function +- `mcp_appwrite_api_functions_list` - List all functions +- `mcp_appwrite_api_functions_create_deployment` - Deploy function code +- `mcp_appwrite_api_functions_create_execution` - Execute a function +- `mcp_appwrite_api_functions_list_executions` - List function executions +- Plus variable and runtime management tools + +**Messaging API** (with `--messaging` flag): +- `mcp_appwrite_api_messaging_create_email` - Send email message +- `mcp_appwrite_api_messaging_create_sms` - Send SMS message +- `mcp_appwrite_api_messaging_create_push` - Send push notification +- `mcp_appwrite_api_messaging_create_topic` - Create messaging topic +- `mcp_appwrite_api_messaging_create_subscriber` - Add subscriber +- Plus provider and target management tools + +**Sites API** (with `--sites` flag): +- `mcp_appwrite_api_sites_create` - Create a new site +- `mcp_appwrite_api_sites_list` - List all sites +- `mcp_appwrite_api_sites_create_deployment` - Deploy site code +- `mcp_appwrite_api_sites_list_deployments` - List deployments +- `mcp_appwrite_api_sites_update_site_deployment` - Activate deployment +- Plus variable and framework management tools + +### appwrite-docs + +**Connection:** HTTPS API endpoint at `https://mcp-for-docs.appwrite.io` +**Authentication:** None required +**Type:** HTTP-based MCP server + +**Available Tools:** + +- `search_documentation` - Search Appwrite documentation +- `get_documentation_page` - Get specific documentation page +- `list_documentation_sections` - List available doc sections +- `get_api_reference` - Get API reference for specific endpoint +- `get_code_examples` - Get code examples for specific feature + +## Tool Usage Examples + +### Database Operations + +**Create a database:** +```javascript +mcp_appwrite_api_databases_create({ + "database_id": "main", + "name": "Main Database", + "enabled": true +}) +``` + +**Create a collection:** +```javascript +mcp_appwrite_api_databases_create_collection({ + "database_id": "main", + "collection_id": "users", + "name": "Users", + "permissions": ["read(\"any\")"], + "document_security": true +}) +``` + +**Add attributes to collection:** +```javascript +// String attribute +mcp_appwrite_api_databases_create_string_attribute({ + "database_id": "main", + "collection_id": "users", + "key": "name", + "size": 255, + "required": true +}) + +// Email attribute +mcp_appwrite_api_databases_create_email_attribute({ + "database_id": "main", + "collection_id": "users", + "key": "email", + "required": true +}) + +// Boolean attribute +mcp_appwrite_api_databases_create_boolean_attribute({ + "database_id": "main", + "collection_id": "users", + "key": "is_active", + "required": true, + "default": true +}) +``` + +**Create a document:** +```javascript +mcp_appwrite_api_databases_create_document({ + "database_id": "main", + "collection_id": "users", + "document_id": "unique()", + "data": { + "name": "John Doe", + "email": "john@example.com", + "is_active": true + }, + "permissions": ["read(\"user:USER_ID\")"] +}) +``` + +**Query documents:** +```javascript +mcp_appwrite_api_databases_list_documents({ + "database_id": "main", + "collection_id": "users", + "queries": [ + "equal(\"is_active\", true)", + "orderDesc(\"$createdAt\")", + "limit(10)" + ] +}) +``` + +### User Management + +**Create a user:** +```javascript +mcp_appwrite_api_users_create({ + "user_id": "unique()", + "email": "user@example.com", + "password": "SecurePass123!", + "name": "Jane Smith" +}) +``` + +**List users:** +```javascript +mcp_appwrite_api_users_list({ + "queries": ["limit(25)"], + "search": "john" +}) +``` + +**Update user email:** +```javascript +mcp_appwrite_api_users_update_email({ + "user_id": "USER_ID", + "email": "newemail@example.com" +}) +``` + +### Storage Operations + +**Create a storage bucket:** +```javascript +mcp_appwrite_api_storage_create_bucket({ + "bucket_id": "avatars", + "name": "User Avatars", + "permissions": ["read(\"any\")"], + "file_security": true, + "enabled": true, + "maximum_file_size": 5000000, // 5MB + "allowed_file_extensions": ["jpg", "jpeg", "png", "gif"] +}) +``` + +**Upload a file:** +```javascript +mcp_appwrite_api_storage_create_file({ + "bucket_id": "avatars", + "file_id": "unique()", + "file": "/path/to/avatar.jpg", + "permissions": ["read(\"any\")"] +}) +``` + +### Documentation Queries + +**Search documentation:** +```javascript +search_documentation({ + "query": "real-time subscriptions", + "limit": 5 +}) +``` + +**Get API reference:** +```javascript +get_api_reference({ + "endpoint": "/databases/{databaseId}/collections/{collectionId}/documents", + "method": "POST" +}) +``` + +## Common Workflows + +### Workflow 1: Complete Database Setup + +```javascript +// Step 1: Create database +const db = await mcp_appwrite_api_databases_create({ + "database_id": "main", + "name": "Main Database", + "enabled": true +}) + +// Step 2: Create collection +const collection = await mcp_appwrite_api_databases_create_collection({ + "database_id": "main", + "collection_id": "posts", + "name": "Blog Posts", + "permissions": ["read(\"any\")"], + "document_security": true +}) + +// Step 3: Add attributes +await mcp_appwrite_api_databases_create_string_attribute({ + "database_id": "main", + "collection_id": "posts", + "key": "title", + "size": 255, + "required": true +}) + +await mcp_appwrite_api_databases_create_string_attribute({ + "database_id": "main", + "collection_id": "posts", + "key": "content", + "size": 10000, + "required": true +}) + +await mcp_appwrite_api_databases_create_datetime_attribute({ + "database_id": "main", + "collection_id": "posts", + "key": "published_at", + "required": false +}) + +// Step 4: Create index for queries +await mcp_appwrite_api_databases_create_index({ + "database_id": "main", + "collection_id": "posts", + "key": "title_index", + "type": "key", + "attributes": ["title"] +}) + +// Step 5: Create first document +await mcp_appwrite_api_databases_create_document({ + "database_id": "main", + "collection_id": "posts", + "document_id": "unique()", + "data": { + "title": "Getting Started with Appwrite", + "content": "Appwrite is an amazing backend platform...", + "published_at": "2024-02-05T10:00:00.000Z" + } +}) +``` + +### Workflow 2: User Authentication Setup + +```javascript +// Step 1: Create admin user +const admin = await mcp_appwrite_api_users_create({ + "user_id": "unique()", + "email": "admin@example.com", + "password": "SecureAdminPass123!", + "name": "Admin User" +}) + +// Step 2: Create team for organization +const team = await mcp_appwrite_api_teams_create({ + "team_id": "unique()", + "name": "Engineering Team" +}) + +// Step 3: Add user to team +await mcp_appwrite_api_teams_create_membership({ + "team_id": team.id, + "email": "admin@example.com", + "roles": ["owner"], + "url": "https://example.com/join-team" +}) + +// Step 4: Set user preferences +await mcp_appwrite_api_users_update_prefs({ + "user_id": admin.id, + "prefs": { + "theme": "dark", + "notifications": true + } +}) +``` + +### Workflow 3: File Upload and Management + +```javascript +// Step 1: Create storage bucket +const bucket = await mcp_appwrite_api_storage_create_bucket({ + "bucket_id": "documents", + "name": "User Documents", + "permissions": ["read(\"any\")"], + "file_security": true, + "enabled": true, + "maximum_file_size": 10000000, // 10MB + "allowed_file_extensions": ["pdf", "doc", "docx", "txt"] +}) + +// Step 2: Upload file +const file = await mcp_appwrite_api_storage_create_file({ + "bucket_id": "documents", + "file_id": "unique()", + "file": "/path/to/document.pdf", + "permissions": ["read(\"user:USER_ID\")"] +}) + +// Step 3: Get file download URL +const download = await mcp_appwrite_api_storage_get_file_download({ + "bucket_id": "documents", + "file_id": file.id +}) + +// Step 4: List all files +const files = await mcp_appwrite_api_storage_list_files({ + "bucket_id": "documents", + "queries": ["limit(25)"] +}) +``` + +### Workflow 4: Serverless Function Deployment + +```javascript +// Step 1: Create function +const func = await mcp_appwrite_api_functions_create({ + "function_id": "unique()", + "name": "Process Payment", + "runtime": "node-18.0", + "execute": ["any"], + "events": [], + "schedule": "", + "timeout": 15 +}) + +// Step 2: Create deployment +const deployment = await mcp_appwrite_api_functions_create_deployment({ + "function_id": func.id, + "code": "/path/to/function.tar.gz", + "activate": true, + "entrypoint": "index.js" +}) + +// Step 3: Set environment variables +await mcp_appwrite_api_functions_create_variable({ + "function_id": func.id, + "key": "STRIPE_API_KEY", + "value": "sk_test_...", + "secret": true +}) + +// Step 4: Execute function +const execution = await mcp_appwrite_api_functions_create_execution({ + "function_id": func.id, + "body": JSON.stringify({ amount: 1000, currency: "usd" }), + "xasync": false +}) +``` + +## Best Practices + +### ✅ Do: + +- **Use document-level permissions** for fine-grained access control +- **Enable document security** on collections with sensitive data +- **Create indexes** for frequently queried attributes +- **Use `unique()` for IDs** to auto-generate unique identifiers +- **Validate data** before creating documents +- **Handle errors gracefully** with try-catch blocks +- **Use queries efficiently** with proper filters and limits +- **Store sensitive data** in environment variables +- **Enable file security** on storage buckets +- **Set appropriate file size limits** on buckets +- **Use transactions** for atomic multi-document operations +- **Test with sandbox** before production deployment +- **Monitor function executions** for errors and performance +- **Use webhooks** for real-time event handling +- **Keep API keys secure** - never commit to version control + +### ❌ Don't: + +- **Expose API keys** in client-side code +- **Skip permission configuration** - always set appropriate permissions +- **Create collections without attributes** - define schema first +- **Use weak passwords** for user accounts +- **Ignore query limits** - always paginate large result sets +- **Store large files** without compression +- **Hardcode credentials** in function code +- **Skip error handling** in production code +- **Use `read("any")` for sensitive data** - restrict access properly +- **Forget to enable collections** - disabled collections are inaccessible +- **Mix test and production data** - use separate projects +- **Skip backup strategy** - export data regularly +- **Ignore rate limits** - implement proper throttling +- **Use synchronous functions** for long operations - use async + +## Troubleshooting + +### Error: "Invalid API key" +**Cause:** Incorrect or missing API key +**Solution:** +1. Verify API key in Appwrite console +2. Check environment variable is set correctly +3. Ensure API key has required scopes +4. Regenerate key if compromised + +### Error: "Database not found" +**Cause:** Invalid database ID or database doesn't exist +**Solution:** +1. Call `list_databases` to verify database exists +2. Check database ID spelling +3. Ensure database is enabled +4. Create database if it doesn't exist + +### Error: "Collection not found" +**Cause:** Invalid collection ID or collection doesn't exist +**Solution:** +1. Call `list_collections` to verify collection exists +2. Check collection ID spelling +3. Ensure collection is enabled +4. Create collection if it doesn't exist + +### Error: "Document not found" +**Cause:** Invalid document ID or document was deleted +**Solution:** +1. Verify document ID is correct +2. Check if document was deleted +3. Ensure user has read permissions +4. Query collection to find document + +### Error: "Attribute not found" +**Cause:** Trying to query or set non-existent attribute +**Solution:** +1. Call `list_attributes` to see available attributes +2. Create attribute before using it +3. Check attribute key spelling +4. Wait for attribute creation to complete (async operation) + +### Error: "Permission denied" +**Cause:** User doesn't have required permissions +**Solution:** +1. Check document/collection permissions +2. Verify user is authenticated +3. Update permissions to grant access +4. Use `read("any")` for public data + +### Error: "File too large" +**Cause:** File exceeds bucket's maximum file size +**Solution:** +1. Check bucket's `maximum_file_size` setting +2. Compress file before upload +3. Update bucket settings to allow larger files +4. Split large files into chunks + +### Error: "Invalid file extension" +**Cause:** File type not allowed in bucket +**Solution:** +1. Check bucket's `allowed_file_extensions` +2. Convert file to allowed format +3. Update bucket to allow file type +4. Remove extension restriction if appropriate + +### Error: "Function execution failed" +**Cause:** Error in function code or timeout +**Solution:** +1. Check function logs in Appwrite console +2. Verify function has required environment variables +3. Increase timeout if needed +4. Test function locally before deployment +5. Check function runtime is correct + +### Error: "Rate limit exceeded" +**Cause:** Too many requests in short time +**Solution:** +1. Implement exponential backoff +2. Cache frequently accessed data +3. Batch operations when possible +4. Upgrade plan for higher limits +5. Optimize query patterns + +## Configuration + +**Authentication Required:** Appwrite API key, Project ID, and Endpoint URL + +**Setup Steps:** + +1. Create Appwrite account at https://cloud.appwrite.io +2. Create a new project or select existing one +3. Navigate to Settings → API Keys +4. Create API key with required scopes: + - `databases.read`, `databases.write` for database operations + - `users.read`, `users.write` for user management + - `files.read`, `files.write` for storage operations + - `functions.read`, `functions.write` for serverless functions + - Or select "All" for full access +5. Copy Project ID from Settings page +6. Note your region (e.g., `nyc`, `fra`, `sfo`) +7. Set environment variables: + ```bash + export APPWRITE_PROJECT_ID="your-project-id" + export APPWRITE_API_KEY="your-api-key" + export APPWRITE_ENDPOINT="https://nyc.cloud.appwrite.io/v1" + ``` + +**MCP Configuration:** + +For API server (minimal - databases only): +```json +{ + "mcpServers": { + "appwrite-api": { + "command": "uvx", + "args": ["mcp-server-appwrite"], + "env": { + "APPWRITE_PROJECT_ID": "${APPWRITE_PROJECT_ID}", + "APPWRITE_API_KEY": "${APPWRITE_API_KEY}", + "APPWRITE_ENDPOINT": "${APPWRITE_ENDPOINT}" + } + } + } +} +``` + +For API server (with additional services): +```json +{ + "mcpServers": { + "appwrite-api": { + "command": "uvx", + "args": [ + "mcp-server-appwrite", + "--users", + "--storage", + "--functions", + "--sites" + ], + "env": { + "APPWRITE_PROJECT_ID": "${APPWRITE_PROJECT_ID}", + "APPWRITE_API_KEY": "${APPWRITE_API_KEY}", + "APPWRITE_ENDPOINT": "${APPWRITE_ENDPOINT}" + } + } + } +} +``` + +For documentation server: +```json +{ + "mcpServers": { + "appwrite-docs": { + "url": "https://mcp-for-docs.appwrite.io", + "type": "http" + } + } +} +``` + +**Permissions:** API key should have appropriate scopes for intended operations. Use least privilege principle - only grant necessary permissions. + +## Tips + +1. **Start with databases** - Most apps need data storage first +2. **Use document security** - Enable for collections with user-specific data +3. **Create indexes early** - Add indexes before large datasets +4. **Test permissions** - Verify access control works as expected +5. **Use query helpers** - Leverage Appwrite's query builder +6. **Monitor usage** - Check Appwrite console for metrics +7. **Enable real-time** - Use subscriptions for live updates +8. **Batch operations** - Use transactions for related changes +9. **Cache strategically** - Reduce API calls with smart caching +10. **Read the docs** - Use the docs MCP server for guidance +11. **Use TypeScript** - Type safety prevents many errors +12. **Version your schema** - Track database structure changes +13. **Test locally** - Use Appwrite CLI for local development +14. **Backup regularly** - Export data periodically +15. **Monitor logs** - Check function and API logs for issues + +## Resources + +- [Appwrite Documentation](https://appwrite.io/docs) +- [API Reference](https://appwrite.io/docs/references) +- [Quick Starts](https://appwrite.io/docs/quick-starts) +- [Database Guide](https://appwrite.io/docs/products/databases) +- [Authentication Guide](https://appwrite.io/docs/products/auth) +- [Storage Guide](https://appwrite.io/docs/products/storage) +- [Functions Guide](https://appwrite.io/docs/products/functions) +- [Sites Guide](https://appwrite.io/docs/products/sites) +- [Community Discord](https://appwrite.io/discord) +- [GitHub Repository](https://github.com/appwrite/appwrite) + +--- + +**Package:** `mcp-server-appwrite` (Python via uvx) +**Source:** Official Appwrite +**License:** BSD-3-Clause +**Connection:** Python MCP server with API key authentication diff --git a/appwrite/mcp.json b/appwrite/mcp.json new file mode 100644 index 0000000..6a71f67 --- /dev/null +++ b/appwrite/mcp.json @@ -0,0 +1,19 @@ +{ + "mcpServers": { + "appwrite-api": { + "command": "uvx", + "args": [ + "mcp-server-appwrite" + ], + "env": { + "APPWRITE_PROJECT_ID": "${APPWRITE_PROJECT_ID}", + "APPWRITE_API_KEY": "${APPWRITE_API_KEY}", + "APPWRITE_ENDPOINT": "${APPWRITE_ENDPOINT}" + } + }, + "appwrite-docs": { + "url": "https://mcp-for-docs.appwrite.io", + "type": "http" + } + } +} diff --git a/appwrite/steering/steering.md b/appwrite/steering/steering.md new file mode 100644 index 0000000..cb8d454 --- /dev/null +++ b/appwrite/steering/steering.md @@ -0,0 +1,724 @@ +# Appwrite Best Practices + +This guide provides best practices for building applications with Appwrite. + +## Database Design + +### Schema Planning + +**Always define your schema before creating documents:** + +```javascript +// ✅ Good: Define attributes first +await createStringAttribute({ key: "name", size: 255, required: true }) +await createEmailAttribute({ key: "email", required: true }) +await createBooleanAttribute({ key: "is_active", required: true, default: true }) + +// Then create documents +await createDocument({ data: { name: "John", email: "john@example.com", is_active: true }}) +``` + +**Use appropriate attribute types:** +- `string` - Short text (names, titles) - specify size +- `email` - Email addresses with validation +- `url` - URLs with validation +- `integer` - Whole numbers +- `float` - Decimal numbers +- `boolean` - True/false values +- `datetime` - ISO 8601 timestamps +- `enum` - Predefined values +- `relationship` - References to other collections + +### Indexing Strategy + +**Create indexes for frequently queried attributes:** + +```javascript +// ✅ Good: Index commonly queried fields +await createIndex({ + key: "email_index", + type: "unique", + attributes: ["email"] +}) + +await createIndex({ + key: "created_at_index", + type: "key", + attributes: ["$createdAt"], + orders: ["DESC"] +}) + +// For full-text search +await createIndex({ + key: "title_search", + type: "fulltext", + attributes: ["title", "content"] +}) +``` + +**Index types:** +- `key` - Standard index for equality and range queries +- `unique` - Ensures unique values (e.g., email, username) +- `fulltext` - Full-text search on text fields +- `array` - Index array values + +### Query Optimization + +**Use specific queries instead of fetching all documents:** + +```javascript +// ❌ Bad: Fetch everything +const all = await listDocuments({ collection_id: "posts" }) + +// ✅ Good: Use filters and limits +const recent = await listDocuments({ + collection_id: "posts", + queries: [ + "equal(\"status\", \"published\")", + "orderDesc(\"$createdAt\")", + "limit(10)" + ] +}) +``` + +**Common query methods:** +- `equal(attribute, value)` - Exact match +- `notEqual(attribute, value)` - Not equal +- `lessThan(attribute, value)` - Less than +- `greaterThan(attribute, value)` - Greater than +- `search(attribute, value)` - Full-text search +- `orderAsc(attribute)` - Sort ascending +- `orderDesc(attribute)` - Sort descending +- `limit(count)` - Limit results +- `offset(count)` - Skip results + +## Security & Permissions + +### Document-Level Security + +**Enable document security for user-specific data:** + +```javascript +// ✅ Good: Enable document security +await createCollection({ + collection_id: "posts", + name: "Posts", + document_security: true, + permissions: ["read(\"any\")"] // Anyone can list posts +}) + +// Create document with user-specific permissions +await createDocument({ + collection_id: "posts", + document_id: "unique()", + data: { title: "My Post", content: "..." }, + permissions: [ + "read(\"any\")", // Anyone can read + "update(\"user:USER_ID\")", // Only author can update + "delete(\"user:USER_ID\")" // Only author can delete + ] +}) +``` + +### Permission Patterns + +**Common permission patterns:** + +```javascript +// Public read, authenticated write +permissions: [ + "read(\"any\")", + "create(\"users\")", + "update(\"users\")", + "delete(\"users\")" +] + +// User-specific access +permissions: [ + "read(\"user:USER_ID\")", + "update(\"user:USER_ID\")", + "delete(\"user:USER_ID\")" +] + +// Team-based access +permissions: [ + "read(\"team:TEAM_ID\")", + "update(\"team:TEAM_ID/owner\")", + "delete(\"team:TEAM_ID/owner\")" +] + +// Role-based access +permissions: [ + "read(\"any\")", + "update(\"role:admin\")", + "delete(\"role:admin\")" +] +``` + +### API Key Security + +**Never expose API keys in client-side code:** + +```javascript +// ❌ Bad: API key in frontend +const client = new Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('PROJECT_ID') + .setKey('API_KEY') // NEVER DO THIS! + +// ✅ Good: Use API key only in backend/server +// Frontend should use account sessions +const client = new Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('PROJECT_ID') +``` + +## User Management + +### User Creation + +**Create users with proper validation:** + +```javascript +// ✅ Good: Validate before creating +function validateEmail(email) { + return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) +} + +function validatePassword(password) { + return password.length >= 8 && /[A-Z]/.test(password) && /[0-9]/.test(password) +} + +if (validateEmail(email) && validatePassword(password)) { + await createUser({ + user_id: "unique()", + email: email, + password: password, + name: name + }) +} +``` + +### User Preferences + +**Store user settings in preferences:** + +```javascript +// ✅ Good: Use preferences for user settings +await updatePrefs({ + user_id: userId, + prefs: { + theme: "dark", + language: "en", + notifications: { + email: true, + push: false + } + } +}) +``` + +## Storage Management + +### Bucket Configuration + +**Configure buckets with appropriate limits:** + +```javascript +// ✅ Good: Set reasonable limits +await createBucket({ + bucket_id: "avatars", + name: "User Avatars", + permissions: ["read(\"any\")"], + file_security: true, + enabled: true, + maximum_file_size: 5000000, // 5MB + allowed_file_extensions: ["jpg", "jpeg", "png", "gif", "webp"], + compression: "gzip", + encryption: true, + antivirus: true +}) +``` + +### File Upload Best Practices + +**Validate files before upload:** + +```javascript +// ✅ Good: Validate file before upload +function validateFile(file) { + const maxSize = 5 * 1024 * 1024 // 5MB + const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'] + + if (file.size > maxSize) { + throw new Error('File too large') + } + + if (!allowedTypes.includes(file.type)) { + throw new Error('Invalid file type') + } + + return true +} + +if (validateFile(file)) { + await createFile({ + bucket_id: "avatars", + file_id: "unique()", + file: file, + permissions: ["read(\"any\")"] + }) +} +``` + +### Image Optimization + +**Use preview endpoint for optimized images:** + +```javascript +// ✅ Good: Use preview with optimization +const imageUrl = `${endpoint}/storage/buckets/${bucketId}/files/${fileId}/preview?width=400&height=400&quality=80&output=webp` + +// For thumbnails +const thumbnail = `${endpoint}/storage/buckets/${bucketId}/files/${fileId}/preview?width=150&height=150&gravity=center&output=webp` +``` + +## Functions Development + +### Function Structure + +**Organize function code properly:** + +```javascript +// ✅ Good: Clean function structure +export default async ({ req, res, log, error }) => { + try { + // Validate input + const { amount, currency } = JSON.parse(req.body) + + if (!amount || !currency) { + return res.json({ error: 'Missing required fields' }, 400) + } + + // Process logic + const result = await processPayment(amount, currency) + + // Return response + return res.json({ success: true, result }) + + } catch (err) { + error(err.message) + return res.json({ error: 'Internal server error' }, 500) + } +} +``` + +### Environment Variables + +**Use environment variables for secrets:** + +```javascript +// ✅ Good: Use environment variables +const stripeKey = process.env.STRIPE_API_KEY +const webhookSecret = process.env.WEBHOOK_SECRET + +// ❌ Bad: Hardcoded secrets +const stripeKey = 'sk_test_...' // NEVER DO THIS! +``` + +### Error Handling + +**Implement comprehensive error handling:** + +```javascript +// ✅ Good: Proper error handling +export default async ({ req, res, log, error }) => { + try { + const data = JSON.parse(req.body) + + // Validate + if (!data.email) { + return res.json({ error: 'Email required' }, 400) + } + + // Process + const result = await sendEmail(data.email) + + log('Email sent successfully') + return res.json({ success: true }) + + } catch (err) { + error(`Failed to send email: ${err.message}`) + + // Return appropriate error + if (err.code === 'INVALID_EMAIL') { + return res.json({ error: 'Invalid email address' }, 400) + } + + return res.json({ error: 'Internal server error' }, 500) + } +} +``` + +## Real-time Subscriptions + +### Subscribe to Changes + +**Use real-time for live updates:** + +```javascript +// ✅ Good: Subscribe to specific channels +client.subscribe('databases.main.collections.posts.documents', response => { + if (response.events.includes('databases.*.collections.*.documents.*.create')) { + console.log('New post created:', response.payload) + } + + if (response.events.includes('databases.*.collections.*.documents.*.update')) { + console.log('Post updated:', response.payload) + } +}) + +// Subscribe to specific document +client.subscribe('databases.main.collections.posts.documents.POST_ID', response => { + console.log('Document changed:', response.payload) +}) +``` + +## Error Handling + +### Graceful Error Handling + +**Always handle errors gracefully:** + +```javascript +// ✅ Good: Comprehensive error handling +async function createPost(title, content) { + try { + const document = await createDocument({ + database_id: "main", + collection_id: "posts", + document_id: "unique()", + data: { title, content } + }) + + return { success: true, document } + + } catch (error) { + // Handle specific errors + if (error.code === 404) { + console.error('Collection not found') + return { success: false, error: 'Collection not found' } + } + + if (error.code === 401) { + console.error('Unauthorized') + return { success: false, error: 'Unauthorized' } + } + + if (error.code === 409) { + console.error('Document already exists') + return { success: false, error: 'Duplicate document' } + } + + // Generic error + console.error('Failed to create post:', error) + return { success: false, error: 'Failed to create post' } + } +} +``` + +## Performance Optimization + +### Caching Strategy + +**Implement caching for frequently accessed data:** + +```javascript +// ✅ Good: Cache frequently accessed data +const cache = new Map() + +async function getPost(postId) { + // Check cache first + if (cache.has(postId)) { + return cache.get(postId) + } + + // Fetch from database + const post = await getDocument({ + database_id: "main", + collection_id: "posts", + document_id: postId + }) + + // Cache for 5 minutes + cache.set(postId, post) + setTimeout(() => cache.delete(postId), 5 * 60 * 1000) + + return post +} +``` + +### Batch Operations + +**Use transactions for multiple related operations:** + +```javascript +// ✅ Good: Use transactions for atomic operations +const transaction = await createTransaction({ ttl: 300 }) + +try { + // Create user + await createDocument({ + database_id: "main", + collection_id: "users", + document_id: "unique()", + data: { name: "John", email: "john@example.com" }, + transaction_id: transaction.id + }) + + // Create user profile + await createDocument({ + database_id: "main", + collection_id: "profiles", + document_id: "unique()", + data: { user_id: userId, bio: "..." }, + transaction_id: transaction.id + }) + + // Commit transaction + await updateTransaction({ + transaction_id: transaction.id, + commit: true + }) + +} catch (error) { + // Rollback on error + await updateTransaction({ + transaction_id: transaction.id, + rollback: true + }) +} +``` + +## Testing + +### Test Environment + +**Use separate projects for testing:** + +```javascript +// ✅ Good: Separate test and production +const config = { + test: { + endpoint: 'https://cloud.appwrite.io/v1', + project: 'TEST_PROJECT_ID', + apiKey: 'TEST_API_KEY' + }, + production: { + endpoint: 'https://cloud.appwrite.io/v1', + project: 'PROD_PROJECT_ID', + apiKey: 'PROD_API_KEY' + } +} + +const env = process.env.NODE_ENV || 'test' +const client = new Client() + .setEndpoint(config[env].endpoint) + .setProject(config[env].project) + .setKey(config[env].apiKey) +``` + +### Integration Tests + +**Write tests for critical operations:** + +```javascript +// ✅ Good: Test critical operations +describe('User Management', () => { + test('should create user', async () => { + const user = await createUser({ + user_id: "unique()", + email: "test@example.com", + password: "SecurePass123!", + name: "Test User" + }) + + expect(user.email).toBe("test@example.com") + expect(user.name).toBe("Test User") + }) + + test('should handle duplicate email', async () => { + await expect( + createUser({ + user_id: "unique()", + email: "test@example.com", + password: "SecurePass123!", + name: "Test User" + }) + ).rejects.toThrow() + }) +}) +``` + +## Monitoring & Logging + +### Function Logging + +**Use structured logging:** + +```javascript +// ✅ Good: Structured logging +export default async ({ req, res, log, error }) => { + log('Function started', { + method: req.method, + path: req.path, + timestamp: new Date().toISOString() + }) + + try { + const result = await processRequest(req) + + log('Function completed successfully', { + duration: Date.now() - startTime, + result: result + }) + + return res.json({ success: true, result }) + + } catch (err) { + error('Function failed', { + error: err.message, + stack: err.stack, + timestamp: new Date().toISOString() + }) + + return res.json({ error: 'Internal server error' }, 500) + } +} +``` + +### Usage Monitoring + +**Monitor API usage and performance:** + +```javascript +// ✅ Good: Track API usage +const metrics = { + requests: 0, + errors: 0, + latency: [] +} + +async function apiCall(operation) { + const start = Date.now() + metrics.requests++ + + try { + const result = await operation() + metrics.latency.push(Date.now() - start) + return result + + } catch (error) { + metrics.errors++ + throw error + } +} + +// Log metrics periodically +setInterval(() => { + console.log('API Metrics:', { + requests: metrics.requests, + errors: metrics.errors, + avgLatency: metrics.latency.reduce((a, b) => a + b, 0) / metrics.latency.length, + errorRate: (metrics.errors / metrics.requests * 100).toFixed(2) + '%' + }) +}, 60000) // Every minute +``` + +## Migration & Backup + +### Data Export + +**Regularly export important data:** + +```javascript +// ✅ Good: Export data for backup +async function exportCollection(databaseId, collectionId) { + const documents = [] + let offset = 0 + const limit = 100 + + while (true) { + const response = await listDocuments({ + database_id: databaseId, + collection_id: collectionId, + queries: [`limit(${limit})`, `offset(${offset})`] + }) + + documents.push(...response.documents) + + if (response.documents.length < limit) { + break + } + + offset += limit + } + + // Save to file + fs.writeFileSync( + `backup-${collectionId}-${Date.now()}.json`, + JSON.stringify(documents, null, 2) + ) + + return documents +} +``` + +### Schema Migration + +**Version your schema changes:** + +```javascript +// ✅ Good: Version schema migrations +const migrations = { + v1: async () => { + await createStringAttribute({ + database_id: "main", + collection_id: "users", + key: "name", + size: 255, + required: true + }) + }, + + v2: async () => { + await createEmailAttribute({ + database_id: "main", + collection_id: "users", + key: "email", + required: true + }) + }, + + v3: async () => { + await createBooleanAttribute({ + database_id: "main", + collection_id: "users", + key: "is_verified", + required: true, + default: false + }) + } +} + +async function runMigrations(currentVersion, targetVersion) { + for (let v = currentVersion + 1; v <= targetVersion; v++) { + console.log(`Running migration v${v}`) + await migrations[`v${v}`]() + } +} +``` + +--- + +Following these best practices will help you build secure, performant, and maintainable applications with Appwrite.