diff --git a/.optimize-cache.json b/.optimize-cache.json index ef218fa5e19..3e777a08a2e 100644 --- a/.optimize-cache.json +++ b/.optimize-cache.json @@ -1457,12 +1457,22 @@ "static/images/docs/databases/dark/csv-export.png": "8d2fbffbdc4b1e6e443d61a93fe4a331ecd91c66ae1457270f51f296c406aeb9", "static/images/docs/databases/dark/csv-import.png": "bdd1e700c747e703ac75b744a8e1caa7e0704ac3439e4ed1077ee0a8e76389d3", "static/images/docs/databases/dark/databases.png": "2cc14bcda3d289c3fb77f8ee4f432d93b46bd0b9755cbf26a53fcadbca3cf32b", + "static/images/docs/databases/dark/documentsdb-import-export.png": "8e87428ef2f5698bd041d398de165ac5063aa7db951901913ec4b7b1674073bc", "static/images/docs/databases/dark/manual-backup.png": "539bdf15bf654a1a696951f4447465b286566460b311ce3db82eb010502a7e03", "static/images/docs/databases/dark/pro-policy.png": "7f74b6eae525187faa9f34a12a0804b227ba9cb2bbd9300f5e8337e9512a6f14", "static/images/docs/databases/dark/restore.png": "f2605303eba4c528bf0041b0e37bb64e61c66503b2d753716b224f66b8f62ecf", "static/images/docs/databases/dark/scale-custom-policies.png": "0013e987e9b8b917cb9be4c28048f851f2d188f3bfa5ff17a11a7ac7cf9c3ade", "static/images/docs/databases/dark/scale-policies.png": "9ca9523f2e20e9aa993f0ad933cdf1dcd12adbaa35ecb2a0b8b3d2fd65877e1f", "static/images/docs/databases/databases.png": "0278a6bc5672684653f74bcf3c0d022fdd82a08d7a7fd438b28e21bd81b5e5d5", + "static/images/docs/databases/documentsdb-import-export.png": "4456efce983bfe6aebaafde0b119801cf35173a036bd0ddd06be4a74afd14778", + "static/images/docs/databases/documentsdb/backup-policy.png": "913cacbe957847f8a71efa2d2e704754e52cf9c276dce8978f6c473a9ba6595b", + "static/images/docs/databases/documentsdb/backups-tab.png": "231163729496618d26863c6fa7ad88069875f78a3c320033d4bddc3c2f8da376", + "static/images/docs/databases/documentsdb/create-database.png": "49ba017dcec96631a87f924b13b6b93478add7b516cc62947e87230b5a0d5de1", + "static/images/docs/databases/documentsdb/dark/backup-policy.png": "a29fad13b50374b3de55824f1332fa4be9c2c7a0c4c63f550d03306ffd546551", + "static/images/docs/databases/documentsdb/dark/backups-tab.png": "dedb6cfcdd9217e814e58cb14db3d8309f53e3071cb9f12edd9a0d5f9bad1d7c", + "static/images/docs/databases/documentsdb/dark/create-database.png": "fe4c1340226d31fb3fa1d655563c9c8fdd150b4d0aac3e217fdd2af40718c272", + "static/images/docs/databases/documentsdb/dark/manual-backup.png": "880f077a3a598c35c9939a1cfcab4bc82a2a88e730be1b4157359657f0a47dcd", + "static/images/docs/databases/documentsdb/manual-backup.png": "0f3432e170b68503b00fac4cf65ed7c5e5a04f34031b5011fe1c80f1eda2bf1a", "static/images/docs/databases/manual-backup.png": "a5854158c5350e333ae14b699db4626c5c26e17b529f3acd137cfae8cb08e9c4", "static/images/docs/databases/pro-policy.png": "b0d35de73c334614dc3f644459dea2bbc56d0da3157db985f24897597ae26302", "static/images/docs/databases/restore.png": "97611c54c654631d2a86a8453a2ea3603c32e85888c065668e945eeeeb894df0", @@ -1591,6 +1601,34 @@ "static/images/docs/platform/dark/execution-details.png": "c0481ddc206447460f9d317ba8d421615066f67a50bc9ef41a8f71766ecffb14", "static/images/docs/platform/env-variables.png": "16f95151e4f6ee9fb8056f075b9f5027fd91f0c2a28586c5624c3ffe9b7a1ee8", "static/images/docs/platform/execution-details.png": "ece1364b8b00254bbd982421b6eed6d7f519d34c4e80377fcaaa4cb5d5dd3f89", + "static/images/docs/products/databases/mysql/create-database-specs.png": "623282bddd45eb245adb20166fd336089718a91bb7207a4f22b9811e13b96ba7", + "static/images/docs/products/databases/mysql/create-database-type.png": "0ff2c521f3f40f467587954120339572bcac0c6d6f76f0f2e6935e640d7adba3", + "static/images/docs/products/databases/mysql/dark/create-database-specs.png": "41bcda64b5cfac906639e077549c1429ec3701f8af9f49c4d1548e4716a23028", + "static/images/docs/products/databases/mysql/dark/create-database-type.png": "2a212e8322d6475dfdd2a1599021bd94f5fef42414e76ff13d0a59aca431b287", + "static/images/docs/products/databases/postgresql/connections.png": "e3096521fc169b41bed8a01c77afc923d1fd1ef53939940b90c3bbabeb3294b3", + "static/images/docs/products/databases/postgresql/create-database-specs.png": "80c5136b2df7f9636f230cda64efd024e5a0867d283d5d6750b05b1da8514b25", + "static/images/docs/products/databases/postgresql/create-database-type.png": "dbfb5e319e8ff5cdefb2691d0ab8a9a4a97edb94a62e52fedee726b1585f56f7", + "static/images/docs/products/databases/postgresql/credentials.png": "45d224d28771564581ccb4c4ae8d733fa192e3c8121fd4fefd63e28b91b9b764", + "static/images/docs/products/databases/postgresql/dark/connections.png": "c4a9abd58079668afd131627e8625811bc2d9b902a395050720a8cfafabc341f", + "static/images/docs/products/databases/postgresql/dark/create-database-specs.png": "07d067e17932689a9e014262c6315e46dd8bb2edd4e4619bf112dd743eca7936", + "static/images/docs/products/databases/postgresql/dark/create-database-type.png": "de3281805c2193d8f82ec81b53e1031bdcb6bfdfda875832d041e07d8f407579", + "static/images/docs/products/databases/postgresql/dark/credentials.png": "b44c3e27fe3e217ac048f5207ec55cba75a34ffe0a056290899ab62b70536491", + "static/images/docs/products/databases/postgresql/dark/monitor.png": "f8921ca48411bab35403b13894a8cd05216c931c206f86b853a3d236302660ad", + "static/images/docs/products/databases/postgresql/dark/roles-tab.png": "2fa52745b9e2c6e072332aa4050d4995fe10a779af931f01ad22dd280e337b9e", + "static/images/docs/products/databases/postgresql/dark/settings-compute-tier.png": "8efccefed72b6543f5a62ef64990a150425fad02aaecc531c42d51ac900fb210", + "static/images/docs/products/databases/postgresql/dark/settings-high-availability.png": "d8f12ff6c9b1e6ac1911323ec2ad4591e6bbcdc6bc56f0bc96e9344ff9e5aa5c", + "static/images/docs/products/databases/postgresql/dark/settings-network.png": "cd2924cdfa1457338a0f53a5055f62abf13350b722ef4b788693f1d7861e2631", + "static/images/docs/products/databases/postgresql/dark/settings-pitr.png": "773fc9622de3299b5d94c6de1fda0de53233143be5acc032f6eac65f044434ce", + "static/images/docs/products/databases/postgresql/dark/settings.png": "51ecfdd292149a4b010704b2a21d2f0cad00a5bd3aed1f6f88d9efb35be46f09", + "static/images/docs/products/databases/postgresql/dark/sql-editor.png": "128fa75d8518612915df4a4b5de6c096ab05dd7df48bb89414e3330977ea5a17", + "static/images/docs/products/databases/postgresql/monitor.png": "da2160f8c22ae0dba29619672d04adea9e553e07e342931775fb432cd000c69a", + "static/images/docs/products/databases/postgresql/roles-tab.png": "b13d5caaf9a839ee5dabcbcb156fda3594ed38f7c30c4f092d1095bb5a895edb", + "static/images/docs/products/databases/postgresql/settings-compute-tier.png": "a4a7ed1d2ad3fb01ca8adac0497d075fd58cc11e0f53ece7ed2257eca0828fd8", + "static/images/docs/products/databases/postgresql/settings-high-availability.png": "1a84efe9e0132012d009eac1b52a67739fbf471761ff0cce323f7b160f3aff5b", + "static/images/docs/products/databases/postgresql/settings-network.png": "ef685f1b56c80259c944d39033a895bbddf6aa8744964d47677863b9c94f0ceb", + "static/images/docs/products/databases/postgresql/settings-pitr.png": "267a9f4b60667e618f8c6ff09615c19423cfec7e182421fc929339e537d861cf", + "static/images/docs/products/databases/postgresql/settings.png": "ded8f34bcd1b812ea5a9ddcb48a34db8678ae696a9b03ab16c934b29621b395a", + "static/images/docs/products/databases/postgresql/sql-editor.png": "595b73cbd6f712d31b015ac224f6cb606875fe3a98cb7bf3c09ac6cbec25720a", "static/images/docs/quick-starts/add-platform.png": "3b13ba983ea1d2529a1f34a719acef903ec0b58879ed511012280a28ccbde17e", "static/images/docs/quick-starts/create-project.png": "7fdb25def02c5dbdb08cd38c2d03b7b454c930194a900553e3e68d51cb28a1d5", "static/images/docs/quick-starts/dark/add-platform.png": "b12a85b64b136589268831b9cb26a664ec97418ad25a38be5273baab8253aa16", diff --git a/src/lib/utils/code.ts b/src/lib/utils/code.ts index 87503c1066a..41a9a3089a9 100644 --- a/src/lib/utils/code.ts +++ b/src/lib/utils/code.ts @@ -29,6 +29,8 @@ import groovy from 'highlight.js/lib/languages/groovy'; import rust from 'highlight.js/lib/languages/rust'; import ini from 'highlight.js/lib/languages/ini'; import dockerfile from 'highlight.js/lib/languages/dockerfile'; +import sql from 'highlight.js/lib/languages/sql'; +import pgsql from 'highlight.js/lib/languages/pgsql'; import { Platform } from './references'; import { writable } from 'svelte/store'; @@ -71,7 +73,9 @@ const languages = { env: ini, dotenv: ini, dockerfile: dockerfile, - docker: dockerfile + docker: dockerfile, + sql: sql, + pgsql: pgsql } as const satisfies Record; const platformAliases: Record = { @@ -114,6 +118,9 @@ Object.entries(platformAliases).forEach(([key, value]) => { /** HashiCorp Configuration Language (Terraform); core highlight.js has no HCL grammar */ hljs.registerAliases(['hcl', 'terraform', 'tf'], { languageName: 'ini' }); +/** Prisma schema language; core highlight.js has no Prisma grammar, GraphQL is the closest match */ +hljs.registerAliases(['prisma'], { languageName: 'graphql' }); + export type Language = keyof typeof languages | Platform; type Args = { diff --git a/src/redirects.json b/src/redirects.json index af48ad0536c..7413a07cd1f 100644 --- a/src/redirects.json +++ b/src/redirects.json @@ -97,15 +97,15 @@ }, { "link": "/docs/databases-queries", - "redirect": "/docs/products/databases/queries" + "redirect": "/docs/products/databases/tablesdb/queries" }, { "link": "/docs/databases-pagination", - "redirect": "/docs/products/databases/pagination" + "redirect": "/docs/products/databases/tablesdb/pagination" }, { "link": "/docs/databases-relationships", - "redirect": "/docs/products/databases/relationships" + "redirect": "/docs/products/databases/tablesdb/relationships" }, { "link": "/docs/storage", @@ -213,11 +213,11 @@ }, { "link": "/docs/queries", - "redirect": "/docs/products/databases/queries" + "redirect": "/docs/products/databases/tablesdb/queries" }, { "link": "/docs/pagination", - "redirect": "/docs/products/databases/pagination" + "redirect": "/docs/products/databases/tablesdb/pagination" }, { "link": "/docs/webhooks", @@ -916,6 +916,142 @@ "link": "/heroes", "redirect": "/" }, + { + "link": "/docs/products/databases/ai-suggestions", + "redirect": "/docs/products/databases/tablesdb/ai-suggestions" + }, + { + "link": "/docs/products/databases/atomic-numeric-operations", + "redirect": "/docs/products/databases/tablesdb/atomic-numeric-operations" + }, + { + "link": "/docs/products/databases/backups", + "redirect": "/docs/products/databases/tablesdb/backups" + }, + { + "link": "/docs/products/databases/bulk-operations", + "redirect": "/docs/products/databases/tablesdb/bulk-operations" + }, + { + "link": "/docs/products/databases/csv-exports", + "redirect": "/docs/products/databases/tablesdb/csv-exports" + }, + { + "link": "/docs/products/databases/csv-imports", + "redirect": "/docs/products/databases/tablesdb/csv-imports" + }, + { + "link": "/docs/products/databases/databases", + "redirect": "/docs/products/databases/tablesdb/databases" + }, + { + "link": "/docs/products/databases/geo-queries", + "redirect": "/docs/products/databases/tablesdb/geo-queries" + }, + { + "link": "/docs/products/databases/legacy", + "redirect": "/docs/products/databases/tablesdb/legacy" + }, + { + "link": "/docs/products/databases/legacy/atomic-numeric-operations", + "redirect": "/docs/products/databases/tablesdb/legacy/atomic-numeric-operations" + }, + { + "link": "/docs/products/databases/legacy/bulk-operations", + "redirect": "/docs/products/databases/tablesdb/legacy/bulk-operations" + }, + { + "link": "/docs/products/databases/legacy/collections", + "redirect": "/docs/products/databases/tablesdb/legacy/collections" + }, + { + "link": "/docs/products/databases/legacy/databases", + "redirect": "/docs/products/databases/tablesdb/legacy/databases" + }, + { + "link": "/docs/products/databases/legacy/documents", + "redirect": "/docs/products/databases/tablesdb/legacy/documents" + }, + { + "link": "/docs/products/databases/legacy/order", + "redirect": "/docs/products/databases/tablesdb/legacy/order" + }, + { + "link": "/docs/products/databases/legacy/pagination", + "redirect": "/docs/products/databases/tablesdb/legacy/pagination" + }, + { + "link": "/docs/products/databases/legacy/permissions", + "redirect": "/docs/products/databases/tablesdb/legacy/permissions" + }, + { + "link": "/docs/products/databases/legacy/queries", + "redirect": "/docs/products/databases/tablesdb/legacy/queries" + }, + { + "link": "/docs/products/databases/legacy/quick-start", + "redirect": "/docs/products/databases/tablesdb/legacy/quick-start" + }, + { + "link": "/docs/products/databases/legacy/relationships", + "redirect": "/docs/products/databases/tablesdb/legacy/relationships" + }, + { + "link": "/docs/products/databases/legacy/type-generation", + "redirect": "/docs/products/databases/tablesdb/legacy/type-generation" + }, + { + "link": "/docs/products/databases/offline", + "redirect": "/docs/products/databases/tablesdb/offline" + }, + { + "link": "/docs/products/databases/operators", + "redirect": "/docs/products/databases/tablesdb/operators" + }, + { + "link": "/docs/products/databases/order", + "redirect": "/docs/products/databases/tablesdb/order" + }, + { + "link": "/docs/products/databases/pagination", + "redirect": "/docs/products/databases/tablesdb/pagination" + }, + { + "link": "/docs/products/databases/permissions", + "redirect": "/docs/products/databases/tablesdb/permissions" + }, + { + "link": "/docs/products/databases/queries", + "redirect": "/docs/products/databases/tablesdb/queries" + }, + { + "link": "/docs/products/databases/quick-start", + "redirect": "/docs/products/databases/tablesdb/quick-start" + }, + { + "link": "/docs/products/databases/relationships", + "redirect": "/docs/products/databases/tablesdb/relationships" + }, + { + "link": "/docs/products/databases/rows", + "redirect": "/docs/products/databases/tablesdb/rows" + }, + { + "link": "/docs/products/databases/tables", + "redirect": "/docs/products/databases/tablesdb/tables" + }, + { + "link": "/docs/products/databases/timestamp-overrides", + "redirect": "/docs/products/databases/tablesdb/timestamp-overrides" + }, + { + "link": "/docs/products/databases/transactions", + "redirect": "/docs/products/databases/tablesdb/transactions" + }, + { + "link": "/docs/products/databases/type-generation", + "redirect": "/docs/products/databases/tablesdb/type-generation" + }, { "link": "/docs/advanced/platform", "redirect": "/docs" diff --git a/src/routes/blog/post/announcing-documentsdb/+page.markdoc b/src/routes/blog/post/announcing-documentsdb/+page.markdoc new file mode 100644 index 00000000000..08d51ba866f --- /dev/null +++ b/src/routes/blog/post/announcing-documentsdb/+page.markdoc @@ -0,0 +1,328 @@ +--- +layout: post +title: "Announcing DocumentsDB: Documents that evolve with your product" +description: Store flexible JSON documents, query them with the Appwrite SDKs, and let your data model evolve with your product instead of ahead of it. +date: 2026-07-09 +cover: /images/blog/announcing-documentsdb/cover.png +timeToRead: 5 +author: atharva +category: announcement +featured: false +callToAction: true +draft: true +faqs: + - question: "What is Appwrite DocumentsDB?" + answer: "DocumentsDB is an Appwrite database for schemaless documents. Collections hold flexible JSON, so you can add or change fields as your application grows without defining columns or running schema migrations. You work with it through the Appwrite SDKs and APIs, with the same permissions, queries, and pagination used across Appwrite Databases." + - question: "How is DocumentsDB different from TablesDB?" + answer: "TablesDB is relational: you define typed columns and indexes up front, and every row follows that schema. DocumentsDB is schemaless: every document is JSON and its shape can vary between documents in the same collection. Choose TablesDB when your data is structured and consistent, and DocumentsDB when it changes shape often or arrives in unpredictable forms." + - question: "Does DocumentsDB support transactions and bulk operations?" + answer: "Yes. DocumentsDB shares the platform capabilities used across Appwrite Databases, including transactions, bulk operations, atomic numeric operations for counters, pagination, and ordering. Permissions work at the document level, so client applications can read and write data safely." + - question: "Can I import existing data into DocumentsDB?" + answer: "Yes, DocumentsDB supports JSON imports and exports. You can bring existing documents in from a JSON file and export collections back out, which makes migrations into and out of Appwrite straightforward." + - question: "How is a DocumentsDB database provisioned?" + answer: "You create a database, choose DocumentsDB as the type, and select a compute specification that fits your workload. From there you can back it up and scale it as usage grows, all from the same project as the rest of your Appwrite backend." + - question: "Is DocumentsDB available on Appwrite Cloud?" + answer: "Yes, DocumentsDB is available on Appwrite Cloud as a database type in the create database flow, with a compute specification you choose per database." +--- + +Most application data does not arrive with a fixed shape. Early products change weekly, integrations return payloads you do not control, and user-generated content rarely fits the columns you designed three months ago. Forcing all of that through a rigid schema turns every product change into a migration. + +That is why we are announcing **DocumentsDB**, an Appwrite database built for schemaless documents. + +# Create your first database + +In your project, go to **Databases**, click **Create database**, and choose **DocumentsDB** as the type. Pick a compute specification, and the database is ready to hold collections in minutes. + +![Creating a DocumentsDB database](/images/docs/databases/documentsdb/dark/create-database.avif) + +# Schemaless collections + +A DocumentsDB collection holds documents as JSON. There are no columns to define and no schema to migrate. Two documents in the same collection can have different fields, and adding a new field is as simple as writing it. + +You still get the full capabilities of an Appwrite database: document-level permissions, queries, ordering, and pagination, through the same SDK patterns as the rest of Appwrite Databases. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.createDocument({ + databaseId: '', + collectionId: '', + documentId: sdk.ID.unique(), + data: { title: 'Hamlet', year: 1601 }, + permissions: [sdk.Permission.read(sdk.Role.any())] // optional +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.createDocument({ + databaseId: '', + collectionId: '', + documentId: sdk.ID.unique(), + data: { title: 'Hamlet', year: 1601 }, + permissions: [sdk.Permission.read(sdk.Role.any())] // optional +}); +``` +```php +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$documentsDB = new DocumentsDB($client); + +$result = $documentsDB->createDocument( + databaseId: '', + collectionId: '', + documentId: ID::unique(), + data: ['title' => 'Hamlet', 'year' => 1601], + permissions: [Permission::read(Role::any())] // optional +); +``` +```python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB +from appwrite.id import ID +from appwrite.permission import Permission +from appwrite.role import Role + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +documents_db = DocumentsDB(client) + +result = documents_db.create_document( + database_id = '', + collection_id = '', + document_id = ID.unique(), + data = { "title": "Hamlet", "year": 1601 }, + permissions = [Permission.read(Role.any())] # optional +) +``` +```ruby +require 'appwrite' + +include Appwrite +include Appwrite::Permission +include Appwrite::Role + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +documents_db = DocumentsDB.new(client) + +result = documents_db.create_document( + database_id: '', + collection_id: '', + document_id: ID.unique(), + data: { "title" => "Hamlet", "year" => 1601 }, + permissions: [Permission.read(Role.any())] # optional +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +Document result = await documentsDB.CreateDocument( + databaseId: "", + collectionId: "", + documentId: ID.Unique(), + data: new { title = "Hamlet", year = 1601 }, + permissions: new List { Permission.Read(Role.Any()) } // optional +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; +import 'package:dart_appwrite/permission.dart'; +import 'package:dart_appwrite/role.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +DocumentsDB documentsDB = DocumentsDB(client); + +Document result = await documentsDB.createDocument( + databaseId: '', + collectionId: '', + documentId: ID.unique(), + data: { "title": "Hamlet", "year": 1601 }, + permissions: [Permission.read(Role.any())], // (optional) +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.ID +import io.appwrite.Permission +import io.appwrite.Role +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val documentsDB = DocumentsDB(client) + +val response = documentsDB.createDocument( + databaseId = "", + collectionId = "", + documentId = ID.unique(), + data = mapOf("title" to "Hamlet", "year" to 1601), + permissions = listOf(Permission.read(Role.any())), // optional +) +``` +```java +import io.appwrite.Client; +import io.appwrite.ID; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; +import java.util.Map; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.createDocument( + "", + "", + ID.unique(), + Map.of("title", "Hamlet", "year", 1601), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let documentsDB = DocumentsDB(client) + +let document = try await documentsDB.createDocument( + databaseId: "", + collectionId: "", + documentId: ID.unique(), + data: ["title": "Hamlet", "year": 1601], + permissions: [Permission.read(Role.any())] // optional +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::DocumentsDB; +use appwrite::id::ID; +use appwrite::permission::Permission; +use appwrite::role::Role; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new(); + client.set_endpoint("https://.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project(""); // Your project ID + client.set_key(""); // Your secret API key + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.create_document( + "", + "", + ID::unique(), + serde_json::json!({ "title": "Hamlet", "year": 1601 }), + Some(vec![Permission::read(Role::any()).to_string()]), // optional + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite documents-db create-document \ + --database-id \ + --collection-id \ + --document-id 'unique()' \ + --data '{ "title": "Hamlet", "year": 1601 }' +``` +{% /multicode %} + +# Platform capabilities included + +DocumentsDB is a first-class member of Appwrite Databases, so the capabilities that make application data manageable come built in: + +- **[Transactions](/docs/products/databases/documentsdb/transactions)** group writes so related changes succeed or fail together. +- **[Bulk operations](/docs/products/databases/documentsdb/bulk-operations)** create, update, or delete many documents in one request. +- **[Atomic numeric operations](/docs/products/databases/documentsdb/atomic-numeric-operations)** increment and decrement counters safely on the server, with no read-modify-write races. +- **[JSON imports](/docs/products/databases/documentsdb/json-imports) and [exports](/docs/products/databases/documentsdb/json-exports)** move existing data in and out without custom scripts. +- **[Backups](/docs/products/databases/documentsdb/backups)** protect your data with scheduled and manual snapshots. + +Because permissions are enforced at the document level, you can serve data directly to client applications without building an API layer in between. + +# Compute specifications + +When you create a DocumentsDB database, you select a compute specification that fits your workload, and the database is provisioned for your project with its own resources. As usage grows, backups and scaling are handled from the same place you manage the rest of your Appwrite project. + +# When to use DocumentsDB + +DocumentsDB fits workloads where the shape of the data is the moving part: + +- Product catalogs where every category carries different attributes +- Event and activity payloads from third-party integrations +- User-generated content with optional and evolving fields +- Prototypes that need persistence before the data model settles + +If your data is structured and consistent, [TablesDB](/docs/products/databases/tablesdb) with its typed columns and indexes remains the right choice. The two share the same platform, so mixing them within one project is normal. + +# Get started + +DocumentsDB is available on Appwrite Cloud today. Create a database, choose DocumentsDB as the type, and you can write your first document within minutes. + +- [DocumentsDB documentation](/docs/products/databases/documentsdb) +- [Quick start](/docs/products/databases/documentsdb/quick-start) + diff --git a/src/routes/blog/post/announcing-native-mysql-databases/+page.markdoc b/src/routes/blog/post/announcing-native-mysql-databases/+page.markdoc new file mode 100644 index 00000000000..c1063ae58b5 --- /dev/null +++ b/src/routes/blog/post/announcing-native-mysql-databases/+page.markdoc @@ -0,0 +1,80 @@ +--- +layout: post +title: "Announcing native MySQL databases: Bring your MySQL workloads to Appwrite" +description: Provision a managed MySQL 8.4 instance inside your Appwrite project and keep using the drivers, ORMs, and tools your applications already depend on. +date: 2026-07-09 +cover: /images/blog/announcing-native-mysql-databases/cover.png +timeToRead: 5 +author: atharva +category: announcement +featured: false +callToAction: true +draft: true +faqs: + - question: "What is a native MySQL database in Appwrite?" + answer: "It is a managed MySQL instance running on dedicated compute inside your Appwrite project. Appwrite provisions the engine with its own hostname, credentials, and TLS, and you connect with the mysql client or any MySQL driver. Appwrite manages the infrastructure; your code talks to MySQL directly." + - question: "Do the Appwrite SDKs manage data in a native MySQL database?" + answer: "No. Native databases are accessed with standard MySQL clients, drivers, and ORMs, not the Appwrite SDKs. If you want SDK-driven data APIs with built-in permissions, use an Appwrite database such as TablesDB, DocumentsDB, or VectorsDB instead." + - question: "Which MySQL versions are available?" + answer: "MySQL 8.4 is the default, with 8.0 also supported. Version upgrades run online with logical replication and a traffic cutover, so moving between supported versions does not require a dump and restore." + - question: "Can I move an existing MySQL application to Appwrite?" + answer: "Yes, that is the primary use case. Because the database is a standard MySQL engine, existing applications built on frameworks like Laravel, Django, Rails, or Spring Boot connect with a changed connection string and no code rewrite." + - question: "What infrastructure features are included?" + answer: "Scheduled backups and point-in-time recovery based on binary log archiving, database branches, high availability with up to five replicas and automatic failover, online compute resizing, storage autoscaling, IP allowlists, a ProxySQL-based connection pooler, and maintenance windows." + - question: "How is the MySQL offering different from the PostgreSQL one?" + answer: "The platform features are the same: identical compute specifications and pricing, backups, PITR, branches, high availability, scaling, and network controls. The engine-specific difference is extensions, which are a PostgreSQL concept and do not apply to MySQL." +--- + +MySQL runs an enormous share of the software already in production. Most teams considering a backend platform are not starting from zero; they have a Laravel application, a Django service, or a decade-old schema that works, and the real question is where it should live. + +Today we are announcing **native MySQL databases** in Appwrite, so the answer can be: inside the same project as the rest of your backend. + +# Create your first database + +Provisioning happens in the Console: + +1. In your project, go to **Databases** and click **Create database**. +2. Under **Choose database type**, select **MySQL** from the **Native databases** group. + +![Choosing MySQL in the create database flow](/images/docs/products/databases/mysql/dark/create-database-type.avif) + +3. Under **Specifications**, pick the tier that fits your workload, and optionally configure read replicas and point-in-time recovery. + +![Selecting a specification](/images/docs/products/databases/mysql/dark/create-database-specs.avif) + +4. Review the summary and click **Create database**. + +The database provisions in minutes and reports `ready` when you can connect. + +# Connect your existing application + +A native MySQL database is a standard MySQL 8.4 engine (8.0 is also supported) with its own hostname, credentials, and TLS. Connecting is exactly what you expect: + +```bash +mysql -h db-..appwrite.center -P 3306 -u admin -p -D +``` + +Change the connection string in your existing application and it runs. We verified the common paths end to end and published [integration guides](/docs/products/databases/mysql/integrations/laravel) for Laravel, Django, Rails, Spring Boot, EF Core, GORM, Prisma, Drizzle, and more, each with the MySQL-specific configuration spelled out. + +Because this is the engine itself, the Appwrite SDKs are not part of the data path. Reads and writes go through your drivers and ORMs, the way they already do. If you want SDK-driven data APIs with document permissions instead, that is what [Appwrite Databases](/docs/products/databases) are for. Native databases and Appwrite databases live side by side in the same project, so you can use both. + +# Managed operations + +Provisioning MySQL is straightforward; operating it over time is where the cost accumulates. The managed layer covers that operational work: + +- **[Backups and PITR.](/docs/products/databases/mysql/backups)** Scheduled and manual backups, plus continuous binary log archiving that restores to any point in the retention window, such as the second before a bad migration. +- **[High availability.](/docs/products/databases/mysql/high-availability)** Up to five replicas with a choice of replication mode and automatic failover behind a stable hostname. +- **[Branches.](/docs/products/databases/mysql/branches)** Instant, isolated copies of the database for previews and CI, created from a storage snapshot. +- **[Scaling.](/docs/products/databases/mysql/scaling)** Online resizes across eight compute tiers and storage autoscaling with a configurable threshold. +- **[Connection pooling.](/docs/products/databases/mysql/connection-pooling)** A ProxySQL-based pooler absorbs connection churn from serverless and per-request runtimes. +- **[Network security.](/docs/products/databases/mysql/network-security)** TLS on by default and IP allowlists enforced before authentication. + +Compute specifications and pricing are identical to the PostgreSQL offering, starting at **$10 per month**, with the full table in the [documentation](/docs/products/databases/mysql#specifications). + +# Get started + +Native MySQL databases are available on Appwrite Cloud today on dedicated infrastructure. + +- [MySQL documentation](/docs/products/databases/mysql) +- [Quick start](/docs/products/databases/mysql/quick-start) + diff --git a/src/routes/blog/post/announcing-native-postgresql-databases/+page.markdoc b/src/routes/blog/post/announcing-native-postgresql-databases/+page.markdoc new file mode 100644 index 00000000000..815ec150e4f --- /dev/null +++ b/src/routes/blog/post/announcing-native-postgresql-databases/+page.markdoc @@ -0,0 +1,88 @@ +--- +layout: post +title: "Announcing native PostgreSQL databases: The full engine, provisioned in minutes" +description: Run a managed PostgreSQL instance inside your Appwrite project and connect to it with psql, your ORM, and the entire PostgreSQL ecosystem. +date: 2026-07-09 +cover: /images/blog/announcing-native-postgresql-databases/cover.png +timeToRead: 6 +author: atharva +category: announcement +featured: false +callToAction: true +draft: true +faqs: + - question: "What is a native PostgreSQL database in Appwrite?" + answer: "It is a managed PostgreSQL instance provisioned on dedicated compute for your Appwrite project. You get the raw engine with its own hostname, credentials, and TLS, and you connect with psql or any PostgreSQL driver. There is no Appwrite abstraction layer between your code and the database." + - question: "How is a native database different from Appwrite Databases like TablesDB?" + answer: "Appwrite databases such as TablesDB, DocumentsDB, and VectorsDB are accessed through Appwrite SDKs and platform APIs, with document permissions and platform features built in. A native PostgreSQL database is the engine itself: you bring your own ORM, migrations, and drivers, and Appwrite manages the infrastructure underneath." + - question: "Can I use the Appwrite SDK to read and write data in a native PostgreSQL database?" + answer: "No. Data in a native database is accessed with standard PostgreSQL clients, drivers, and ORMs rather than the Appwrite SDKs. That is the point of the product: your existing PostgreSQL code and tooling work unchanged." + - question: "Which PostgreSQL versions are supported?" + answer: "PostgreSQL 18 is the default, with 17 also supported. Version upgrades run online: a second instance is provisioned on the new version, data streams over with logical replication, and traffic cuts over without a read or write outage." + - question: "What infrastructure features are included?" + answer: "Scheduled backups and point-in-time recovery, database branches, high availability with up to five replicas, online compute resizing, storage autoscaling, IP allowlists, a connection pooler, and maintenance windows. Extensions like PostGIS and pgvector can be installed per database." + - question: "How much does a native PostgreSQL database cost?" + answer: "Compute specifications start at $10 per month for 1 vCPU and 1 GB of memory and scale up to 8 vCPU and 64 GB. Storage and bandwidth allowances are included per tier, with overages and optional add-ons like high availability replicas and point-in-time recovery billed on top." +--- + +Some projects outgrow abstractions. You want `psql`, your own migrations, an ORM you chose yourself, and the thirty years of tooling that surround PostgreSQL, without also taking on provisioning, backups, failover, and patching. + +Today we are announcing **native PostgreSQL databases** in Appwrite. You pick a compute specification, Appwrite provisions the engine in your project's region, and you connect to it like any PostgreSQL server, because it is one. + +# Create your first database + +Provisioning happens in the Console: + +1. In your project, go to **Databases** and click **Create database**. +2. Under **Choose database type**, select **PostgreSQL** from the **Native databases** group. + +![Choosing PostgreSQL in the create database flow](/images/docs/products/databases/postgresql/dark/create-database-type.avif) + +3. Under **Specifications**, pick the tier that fits your workload, and optionally configure read replicas and point-in-time recovery. + +![Selecting a specification](/images/docs/products/databases/postgresql/dark/create-database-specs.avif) + +4. Review the summary and click **Create database**. + +The database provisions in minutes and reports `ready` when you can connect. + +# Direct access to the PostgreSQL engine + +A native database gets its own hostname, credentials, and TLS out of the box: + +```bash +psql "postgresql://admin:@db-..appwrite.center:5432/" +``` + +From there, everything you know applies. Prisma, Drizzle, Django, Rails, Spring Boot, EF Core, and GORM work against it with no Appwrite-specific configuration, and we ship [integration guides](/docs/products/databases/postgresql/integrations/prisma) for each. Extensions like PostGIS, pgvector, and pg_trgm install per database. + +This is a different product from [Appwrite Databases](/docs/products/databases). TablesDB, DocumentsDB, and VectorsDB are accessed through Appwrite SDKs with platform permissions built in. A native PostgreSQL database has no Appwrite layer between your code and the engine, and the Appwrite SDKs do not manage its data. Your drivers do. + +# Managed infrastructure features + +Appwrite manages the operational work that self-hosting PostgreSQL would put on your team: + +- **[Backups and point-in-time recovery.](/docs/products/databases/postgresql/backups)** Scheduled and manual backups, plus continuous archiving that can restore to any moment inside the retention window. +- **[Branches.](/docs/products/databases/postgresql/branches)** Ephemeral copies of your database created from a storage snapshot, ideal for previews and testing against production-shaped data. +- **[High availability.](/docs/products/databases/postgresql/high-availability)** Up to five replicas with asynchronous, synchronous, or quorum replication and automatic failover behind a stable hostname. +- **[Scaling.](/docs/products/databases/postgresql/scaling)** Resize compute online between eight specifications, and let storage autoscaling grow the disk before it fills. +- **[Connection pooling.](/docs/products/databases/postgresql/connection-pooling)** A per-database pooler multiplexes serverless traffic, with read/write splitting when replicas are enabled. +- **[Network security.](/docs/products/databases/postgresql/network-security)** TLS by default, IP allowlists, and a dedicated hostname per database. + +# SQL tooling in the Console + +The Console treats the database as a first-class SQL environment. The SQL editor runs queries with formatting and explain plans, the Monitor tab tracks compute, connections, storage, and workload, and the active connections view surfaces what `pg_stat_activity` knows with actions to cancel or terminate sessions. + +![The SQL editor in the Appwrite Console](/images/docs/products/databases/postgresql/dark/sql-editor.avif) + +# Specifications and pricing + +Specifications run from **$10 per month** (1 vCPU, 1 GB memory, 10 GB storage) to **$699 per month** (8 vCPU, 64 GB memory, 3 TB storage), each with included storage and bandwidth. High availability replicas and point-in-time recovery are add-ons, priced as a percentage of the tier. The full table lives in the [documentation](/docs/products/databases/postgresql#specifications). + +# Get started + +Native PostgreSQL databases are available on Appwrite Cloud today on dedicated infrastructure. + +- [PostgreSQL documentation](/docs/products/databases/postgresql) +- [Quick start](/docs/products/databases/postgresql/quick-start) + diff --git a/src/routes/blog/post/announcing-vectorsdb/+page.markdoc b/src/routes/blog/post/announcing-vectorsdb/+page.markdoc new file mode 100644 index 00000000000..89867d70e9e --- /dev/null +++ b/src/routes/blog/post/announcing-vectorsdb/+page.markdoc @@ -0,0 +1,418 @@ +--- +layout: post +title: "Announcing VectorsDB: Similarity search as a first-class Appwrite database" +description: Store embeddings, generate them from text with built-in models, and rank documents by similarity without adding a separate vector service to your stack. +date: 2026-07-09 +cover: /images/blog/announcing-vectorsdb/cover.png +timeToRead: 6 +author: atharva +category: announcement +featured: false +callToAction: true +draft: true +faqs: + - question: "What is Appwrite VectorsDB?" + answer: "VectorsDB is an Appwrite database for vector embeddings and similarity search. Every collection is created with a fixed dimension, documents hold an embeddings vector plus optional JSON metadata, and an HNSW index keeps similarity search fast as the collection grows." + - question: "Do I need a separate embedding service to use VectorsDB?" + answer: "No. VectorsDB can generate text embeddings with built-in models, so you can turn strings into vectors and store them in one flow. If you already produce embeddings elsewhere, you can store those instead, as long as their length matches the collection's dimension." + - question: "Which similarity metrics does VectorsDB support?" + answer: "VectorsDB supports cosine, dot product, and Euclidean distance. You choose the metric when you create the HNSW index, and query with the matching vector query method to get documents ranked from most to least similar." + - question: "What is an HNSW index and why does it matter?" + answer: "HNSW (Hierarchical Navigable Small World) is an approximate nearest neighbor index. It keeps similarity search fast even as collections grow large, which is what makes vector search practical in production instead of a linear scan over every document." + - question: "Can I combine vector search with regular queries?" + answer: "Yes. A vector query can be combined with other queries, such as limits, so you can cap how many results come back. Results return as documents with their metadata, ready to render or feed into your application logic." + - question: "What can I build with VectorsDB?" + answer: "Semantic search over content, recommendations based on similarity, deduplication, and retrieval for AI applications such as RAG. Anywhere ranking by meaning beats matching by exact value, VectorsDB applies." +--- + +Semantic search, recommendations, and retrieval for AI features all reduce to the same primitive: store vectors, then find the ones closest to a query. Until now, that primitive usually meant adding a dedicated vector service to your stack, with its own hosting, its own client, and its own auth model to reconcile with the rest of your backend. + +Today we are announcing **VectorsDB**, which makes that primitive a first-class Appwrite database. + +# Create your first database + +In your project, go to **Databases**, click **Create database**, and choose **VectorsDB** as the type. Pick a compute specification, then create your first collection with the `dimension` your embedding model produces. From there, every document you write is a vector ready to be searched. + +# A schema designed for vectors + +Where other Appwrite databases let you define your own fields, a VectorsDB collection has a deliberate, fixed shape. You create it with a `dimension`, and every document carries two things: + +- `embeddings`: a vector of exactly that length +- `metadata`: optional JSON for whatever belongs alongside the vector, such as the source text, a URL, or labels + +That constraint is the feature. The collection always knows what a valid vector looks like, and the HNSW index built on `embeddings` keeps search fast as your data grows. + +# Built-in embedding generation + +VectorsDB can generate text embeddings with built-in models. You pass a string, and Appwrite turns it into a vector you can store directly, so a working semantic search does not require standing up an embedding service or wiring a third-party API into your ingest path. If you already have embeddings from your own models, store those instead. + +# Run a similarity search + +Searching is a two-step flow. First, turn the search text into a vector, either with your own model or with the built-in embedding generation: + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createTextEmbeddings({ + texts: ['The quick brown fox jumps over the lazy dog'], + model: sdk.EmbeddingModel.Nomicembedtext // optional, defaults to nomic-embed-text +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createTextEmbeddings({ + texts: ['The quick brown fox jumps over the lazy dog'], + model: sdk.EmbeddingModel.Nomicembedtext // optional, defaults to nomic-embed-text +}); +``` +```php +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->createTextEmbeddings( + texts: ['The quick brown fox jumps over the lazy dog'], + model: EmbeddingModel::NOMICEMBEDTEXT() // optional, defaults to nomic-embed-text +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.enums import EmbeddingModel + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.create_text_embeddings( + texts = ['The quick brown fox jumps over the lazy dog'], + model = EmbeddingModel.NOMIC_EMBED_TEXT # optional, defaults to nomic-embed-text +) +``` +```ruby +require 'appwrite' + +include Appwrite +include Appwrite::Enums + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.create_text_embeddings( + texts: ['The quick brown fox jumps over the lazy dog'], + model: EmbeddingModel::NOMIC_EMBED_TEXT # optional, defaults to nomic-embed-text +) +``` +```csharp +using Appwrite; +using Appwrite.Enums; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +EmbeddingList result = await vectorsDB.CreateTextEmbeddings( + texts: new List { "The quick brown fox jumps over the lazy dog" }, + model: EmbeddingModel.NomicEmbedText // optional, defaults to nomic-embed-text +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; +import 'package:dart_appwrite/enums.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +EmbeddingList result = await vectorsDB.createTextEmbeddings( + texts: ['The quick brown fox jumps over the lazy dog'], + model: EmbeddingModel.nomicEmbedText, // optional, defaults to nomic-embed-text +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.enums.EmbeddingModel +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.createTextEmbeddings( + texts = listOf("The quick brown fox jumps over the lazy dog"), + model = EmbeddingModel.NOMIC_EMBED_TEXT, // optional, defaults to nomic-embed-text +) +``` +```java +import io.appwrite.Client; +import io.appwrite.enums.EmbeddingModel; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.createTextEmbeddings( + List.of("The quick brown fox jumps over the lazy dog"), + EmbeddingModel.NOMIC_EMBED_TEXT, // optional, defaults to nomic-embed-text + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite +import AppwriteEnums + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let embeddingList = try await vectorsDB.createTextEmbeddings( + texts: ["The quick brown fox jumps over the lazy dog"], + model: .nomicEmbedText // optional, defaults to nomic-embed-text +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::enums::EmbeddingModel; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new(); + client.set_endpoint("https://.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project(""); // Your project ID + client.set_key(""); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create_text_embeddings( + vec!["The quick brown fox jumps over the lazy dog"], + Some(EmbeddingModel::NomicEmbedText), // optional, defaults to nomic-embed-text + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db create-text-embeddings \ + --texts 'The quick brown fox jumps over the lazy dog' \ + --model 'nomic-embed-text' +``` +{% /multicode %} + +Then pass that vector as a query to the same `listDocuments` method used across Appwrite Databases. Choose the metric when you create the index, cosine, dot product, or Euclidean distance, and query with the matching method. The vectors below are shortened for readability; in practice you pass the embedding returned by the step above: + +{% multicode %} +```server-nodejs +const result = await vectorsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + sdk.Query.vectorCosine('embeddings', [0.11, 0.21, 0.30, 0.40]), + sdk.Query.limit(3) + ] +}); +``` +```deno +const result = await vectorsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + sdk.Query.vectorCosine('embeddings', [0.11, 0.21, 0.30, 0.40]), + sdk.Query.limit(3) + ] +}); +``` +```php +$result = $vectorsDB->listDocuments( + databaseId: '', + collectionId: '', + queries: [ + Query::vectorCosine('embeddings', [0.11, 0.21, 0.30, 0.40]), + Query::limit(3) + ] +); +``` +```python +result = vectors_db.list_documents( + database_id = '', + collection_id = '', + queries = [ + Query.vector_cosine('embeddings', [0.11, 0.21, 0.30, 0.40]), + Query.limit(3) + ] +) +``` +```ruby +result = vectors_db.list_documents( + database_id: '', + collection_id: '', + queries: [ + Query.vector_cosine('embeddings', [0.11, 0.21, 0.30, 0.40]), + Query.limit(3) + ] +) +``` +```csharp +DocumentList result = await vectorsDB.ListDocuments( + databaseId: "", + collectionId: "", + queries: new List { + Query.VectorCosine("embeddings", new List { 0.11, 0.21, 0.30, 0.40 }), + Query.Limit(3) + } +); +``` +```dart +DocumentList result = await vectorsDB.listDocuments( + databaseId: '', + collectionId: '', + queries: [ + Query.vectorCosine('embeddings', [0.11, 0.21, 0.30, 0.40]), + Query.limit(3) + ], +); +``` +```kotlin +val result = vectorsDB.listDocuments( + databaseId = "", + collectionId = "", + queries = listOf( + Query.vectorCosine("embeddings", listOf(0.11, 0.21, 0.30, 0.40)), + Query.limit(3) + ), +) +``` +```java +vectorsDB.listDocuments( + "", + "", + List.of( + Query.vectorCosine("embeddings", List.of(0.11, 0.21, 0.30, 0.40)), + Query.limit(3) + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +let result = try await vectorsDB.listDocuments( + databaseId: "", + collectionId: "", + queries: [ + Query.vectorCosine("embeddings", [0.11, 0.21, 0.30, 0.40]), + Query.limit(3) + ] +) +``` +```server-rust +let result = vectors_db.list_documents( + "", + "", + Some(vec![ + Query::vector_cosine("embeddings", json!([0.11, 0.21, 0.30, 0.40])).to_string(), + Query::limit(3).to_string(), + ]), + None, // transaction_id + None, // total + None, // ttl +).await?; +``` +```bash +appwrite vectors-db list-documents \ + --database-id \ + --collection-id \ + --queries '{"method":"vectorCosine","attribute":"embeddings","values":[[0.11,0.21,0.30,0.40]]}' '{"method":"limit","values":[3]}' +``` +{% /multicode %} + +Results come back as documents ranked from most to least similar, with their metadata attached, ready to render. + +# Shared Appwrite Databases capabilities + +VectorsDB shares the platform capabilities available across Appwrite Databases: + +- **[Permissions](/docs/products/databases/vectorsdb/permissions)** control access at the document level, so clients can search safely. +- **[Queries](/docs/products/databases/vectorsdb/queries)**, **[pagination](/docs/products/databases/vectorsdb/pagination)**, and ordering work alongside vector queries. +- **[Transactions](/docs/products/databases/vectorsdb/transactions)** group writes so related changes succeed or fail together. +- **[Bulk operations](/docs/products/databases/vectorsdb/bulk-operations)** ingest large embedding sets in one request. +- **[CSV imports](/docs/products/databases/vectorsdb/csv-imports) and [exports](/docs/products/databases/vectorsdb/csv-exports)** move vectors and metadata in and out. +- **[Backups](/docs/products/databases/vectorsdb/backups)** protect your collections with scheduled and manual snapshots. + +Your vectors live next to the rest of your application data, behind the same SDKs and the same access control, instead of in a service on the side. + +Every VectorsDB database is provisioned for your project with a compute specification you choose at creation, so search performance scales with the tier you pick. + +# Get started + +VectorsDB is available on Appwrite Cloud today. Create a database, choose VectorsDB as the type, pick a dimension, and you can run your first similarity search in minutes. + +- [VectorsDB documentation](/docs/products/databases/vectorsdb) +- [Quick start](/docs/products/databases/vectorsdb/quick-start) +- [Vector search](/docs/products/databases/vectorsdb/vector-search) + diff --git a/src/routes/docs/products/databases/(overview)/+layout.svelte b/src/routes/docs/products/databases/(overview)/+layout.svelte new file mode 100644 index 00000000000..ae270c5d9e4 --- /dev/null +++ b/src/routes/docs/products/databases/(overview)/+layout.svelte @@ -0,0 +1,64 @@ + + + + + + {@render children()} + diff --git a/src/routes/docs/products/databases/(overview)/+page.markdoc b/src/routes/docs/products/databases/(overview)/+page.markdoc new file mode 100644 index 00000000000..635ec2fd952 --- /dev/null +++ b/src/routes/docs/products/databases/(overview)/+page.markdoc @@ -0,0 +1,40 @@ +--- +layout: article +title: Databases +description: Store and query your application data with Appwrite Databases. Choose between Appwrite databases with managed APIs and dedicated native databases with direct access. +--- + +Appwrite Databases provide performant and scalable storage for your application, business, and user data. Choose the database that fits your use case, from managed APIs with permissions and realtime to dedicated native engines you connect to directly. + +{% info title="Looking for file storage?" %} +Databases store data. If you need to store files like images, PDFs, or videos, use [Appwrite Storage](/docs/products/storage). +{% /info %} + +# Appwrite databases {% #appwrite-databases %} + +Managed databases with an Appwrite API on top, including permissions, indexes, queries, and realtime. Available on shared and dedicated infrastructure. + +{% cards %} +{% cards_item href="/docs/products/databases/tablesdb" title="TablesDB" %} +Structured, relational data with typed columns, rows, relationships, and indexes. +{% /cards_item %} +{% cards_item href="/docs/products/databases/documentsdb" title="DocumentsDB" %} +Schemaless document storage for flexible, JSON-style data. +{% /cards_item %} +{% cards_item href="/docs/products/databases/vectorsdb" title="VectorsDB" %} +Store embeddings and run similarity search to power AI features. +{% /cards_item %} +{% /cards %} + +# Native databases {% #native-databases %} + +Native database engines provisioned for your project with direct connection access and no abstraction layer. Available on dedicated infrastructure. + +{% cards %} +{% cards_item href="/docs/products/databases/postgresql" title="PostgreSQL" %} +A dedicated, native PostgreSQL database you connect to directly. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql" title="MySQL" %} +A dedicated, native MySQL database you connect to directly. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/documentsdb/+layout.svelte b/src/routes/docs/products/databases/documentsdb/+layout.svelte new file mode 100644 index 00000000000..6d43eae7041 --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/+layout.svelte @@ -0,0 +1,99 @@ + + + + + + {@render children()} + diff --git a/src/routes/docs/products/databases/documentsdb/+page.markdoc b/src/routes/docs/products/databases/documentsdb/+page.markdoc new file mode 100644 index 00000000000..cb74d847aff --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/+page.markdoc @@ -0,0 +1,18 @@ +--- +layout: article +title: DocumentsDB +description: Store and query schemaless documents with Appwrite DocumentsDB. Collections give you flexible, JSON-style storage for your application, business, and user data. +--- + +Appwrite DocumentsDB lets you store and query schemaless documents. +Collections hold documents as flexible JSON, so you can add fields as your data evolves without defining a schema up front. + +{% info title="Looking for file storage?" %} +Databases store data, if you need to store files like images, PDFs or videos, use [Appwrite Storage](/docs/products/storage). +{% /info %} + +You can organize data into databases, collections, and documents. You can also paginate, order, and query documents. + +{% arrow_link href="/docs/products/databases/documentsdb/quick-start" %} +Quick start +{% /arrow_link %} diff --git a/src/routes/docs/products/databases/documentsdb/atomic-numeric-operations/+page.markdoc b/src/routes/docs/products/databases/documentsdb/atomic-numeric-operations/+page.markdoc new file mode 100644 index 00000000000..9dd095b1a31 --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/atomic-numeric-operations/+page.markdoc @@ -0,0 +1,867 @@ +--- +layout: article +title: Atomic numeric operations +description: Safely increment and decrement numeric fields without race conditions. Perfect for counters, quotas, inventory, and usage metrics in high-concurrency applications. +--- + +Atomic numeric operations allow you to safely increase or decrease numeric fields without fetching the full document. This eliminates race conditions and reduces bandwidth usage when updating any numeric values that need to be modified atomically, such as counters, scores, balances, and other fast-moving numeric data. + +These operations work on numeric document fields, whether the value is an integer or a floating-point number. + +# How atomic operations work {% #how-atomic-operations-work %} + +Instead of the traditional read-modify-write pattern, atomic numeric operations use dedicated methods to modify values directly on the server. The server applies the change atomically under concurrency control and returns the new value. + +**Traditional approach:** +1. Fetch document → `{ likes: 42 }` +2. Update client-side → `likes: 43` +3. Write back → `{ likes: 43 }` + +**Atomic approach:** +1. Call `incrementDocumentAttribute()` with the attribute key and the value to increment by +2. Server applies atomically → `likes: 43` + +# When to use atomic operations {% #when-to-use-atomic-operations %} + +Atomic numeric operations work well for: + +- **Social features**: Likes, follows, comment counts +- **Usage metering**: API credits, storage quotas, request limits +- **Game state**: Scores, lives, currency, experience points +- **E-commerce**: Stock counts, inventory levels +- **Workflow tracking**: Retry counts, progress indicators +- **Rate limiting**: Request counters, usage tracking + +# Perform atomic operations {% #perform-atomic-operations %} + +Use the `incrementDocumentAttribute` and `decrementDocumentAttribute` methods to perform atomic numeric operations. The server will apply these changes atomically under concurrency control. + +## Increment a field {% #increment-field %} + +{% multicode %} +```client-web +import { Client, DocumentsDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const documentsDB = new DocumentsDB(client); + +const result = await documentsDB.incrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', // attribute + value: 1 // value +}); +``` +```client-flutter +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final documentsDB = DocumentsDB(client); + +final document = await documentsDB.incrementDocumentAttribute( + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', + value: 1 +); +``` +```client-apple +import Appwrite +import AppwriteModels + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let documentsDB = DocumentsDB(client) + +let document = try await documentsDB.incrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "likes", + value: 1 +) +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB + +val client = Client(applicationContext) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val documentsDB = DocumentsDB(client) + +val document = documentsDB.incrementDocumentAttribute( + databaseId = "", + collectionId = "", + documentId = "", + attribute = "likes", + value = 1 +) +``` +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.incrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', // attribute + value: 1 // value +}); +``` +```server-python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +documents_db = DocumentsDB(client) + +result = documents_db.increment_document_attribute( + database_id = '', + collection_id = '', + document_id = '', + attribute = 'likes', # attribute + value = 1 # value +) +``` +```rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; + +let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + +let documents_db = DocumentsDB::new(&client); + +let result = documents_db.increment_document_attribute( + "", + "", + "", + "likes", // attribute + Some(1.0), // value + None, // max + None, // transaction_id +).await?; +``` +```graphql +mutation { + documentsDBIncrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "likes", + value: 1 + ) { + _id + _collectionId + _databaseId + _createdAt + _updatedAt + _permissions + data + } +} +``` +{% /multicode %} + +## Decrement a field {% #decrement-field %} + +Use the `decrementDocumentAttribute` method to decrease numeric fields: + +{% multicode %} +```client-web +import { Client, DocumentsDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const documentsDB = new DocumentsDB(client); + +const result = await documentsDB.decrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'credits', // attribute + value: 5 // value +}); +``` +```client-flutter +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final documentsDB = DocumentsDB(client); + +final document = await documentsDB.decrementDocumentAttribute( + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'credits', + value: 5 +); +``` +```client-apple +import Appwrite +import AppwriteModels + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let documentsDB = DocumentsDB(client) + +let document = try await documentsDB.decrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "credits", + value: 5 +) +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB + +val client = Client(applicationContext) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val documentsDB = DocumentsDB(client) + +val document = documentsDB.decrementDocumentAttribute( + databaseId = "", + collectionId = "", + documentId = "", + attribute = "credits", + value = 5 +) +``` +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.decrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'credits', // attribute + value: 5 // value +}); +``` +```server-python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +documents_db = DocumentsDB(client) + +result = documents_db.decrement_document_attribute( + database_id = '', + collection_id = '', + document_id = '', + attribute = 'credits', # attribute + value = 5 # value +) +``` +```rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; + +let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + +let documents_db = DocumentsDB::new(&client); + +let result = documents_db.decrement_document_attribute( + "", + "", + "", + "credits", // attribute + Some(5.0), // value + None, // min + None, // transaction_id +).await?; +``` +```graphql +mutation { + documentsDBDecrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "credits", + value: 5 + ) { + _id + _collectionId + _databaseId + _createdAt + _updatedAt + _permissions + data + } +} +``` +{% /multicode %} + +# Set constraints and bounds {% #set-constraints-and-bounds %} + +You can set minimum and maximum bounds for individual operations to prevent invalid values. Use the optional `min` and `max` parameters to ensure the final value stays within acceptable limits. If an operation would move the value past the bound, the request is rejected. + +## Example with constraints {% #example-with-constraints %} + +{% multicode %} +```client-web +// Increment with maximum constraint +const result = await documentsDB.incrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'credits', // attribute + value: 100, // value + max: 1000 // max (optional) +}); + +// Decrement with minimum constraint +const result2 = await documentsDB.decrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'credits', // attribute + value: 50, // value + min: 0 // min (optional) +}); +``` +```client-flutter +// Increment with maximum constraint +final document = await documentsDB.incrementDocumentAttribute( + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'credits', + value: 100, + max: 1000 +); + +// Decrement with minimum constraint +final document2 = await documentsDB.decrementDocumentAttribute( + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'credits', + value: 50, + min: 0 +); +``` +```client-apple +// Increment with maximum constraint +let document = try await documentsDB.incrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "credits", + value: 100, + max: 1000 +) + +// Decrement with minimum constraint +let document2 = try await documentsDB.decrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "credits", + value: 50, + min: 0 +) +``` +```client-android-kotlin +// Increment with maximum constraint +val document = documentsDB.incrementDocumentAttribute( + databaseId = "", + collectionId = "", + documentId = "", + attribute = "credits", + value = 100, + max = 1000 +) + +// Decrement with minimum constraint +val document2 = documentsDB.decrementDocumentAttribute( + databaseId = "", + collectionId = "", + documentId = "", + attribute = "credits", + value = 50, + min = 0 +) +``` +```server-nodejs +// Increment with maximum constraint +const result = await documentsDB.incrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'credits', // attribute + value: 100, // value + max: 1000 // max (optional) +}); + +// Decrement with minimum constraint +const result2 = await documentsDB.decrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'credits', // attribute + value: 50, // value + min: 0 // min (optional) +}); +``` +```server-python +# Increment with maximum constraint +result = documents_db.increment_document_attribute( + database_id = '', + collection_id = '', + document_id = '', + attribute = 'credits', # attribute + value = 100, # value + max = 1000 # max (optional) +) + +# Decrement with minimum constraint +result2 = documents_db.decrement_document_attribute( + database_id = '', + collection_id = '', + document_id = '', + attribute = 'credits', # attribute + value = 50, # value + min = 0 # min (optional) +) +``` +```rust +// Increment with maximum constraint +let result = documents_db.increment_document_attribute( + "", + "", + "", + "credits", // attribute + Some(100.0), // value + Some(1000.0), // max (optional) + None, // transaction_id +).await?; + +// Decrement with minimum constraint +let result2 = documents_db.decrement_document_attribute( + "", + "", + "", + "credits", // attribute + Some(50.0), // value + Some(0.0), // min (optional) + None, // transaction_id +).await?; +``` +{% /multicode %} + +# Follow best practices {% #follow-best-practices %} + +## Use for high-concurrency scenarios {% #use-for-high-concurrency-scenarios %} + +Atomic numeric operations are most beneficial when multiple users or processes might update the same numeric field simultaneously. + +## Combine with regular updates {% #combine-with-regular-updates %} + +For complex updates that include both atomic operations and regular field changes, you'll need to use separate API calls: + +{% multicode %} +```client-web +// First, increment the likes atomically +const likeResult = await documentsDB.incrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', // attribute + value: 1 // value +}); + +// Then, update other fields +const updateResult = await documentsDB.updateDocument({ + databaseId: '', + collectionId: '', + documentId: '', + data: { + lastLikedBy: userId, + lastLikedAt: new Date().toISOString() + } +}); +``` +```client-flutter +// First, increment the likes atomically +final likeResult = await documentsDB.incrementDocumentAttribute( + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', + value: 1 +); + +// Then, update other fields +final updateResult = await documentsDB.updateDocument( + databaseId: '', + collectionId: '', + documentId: '', + data: { + 'lastLikedBy': userId, + 'lastLikedAt': DateTime.now().toIso8601String() + } +); +``` +```client-apple +// First, increment the likes atomically +let likeResult = try await documentsDB.incrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "likes", + value: 1 +) + +// Then, update other fields +let updateResult = try await documentsDB.updateDocument( + databaseId: "", + collectionId: "", + documentId: "", + data: [ + "lastLikedBy": userId, + "lastLikedAt": ISO8601DateFormatter().string(from: Date()) + ] +) +``` +```client-android-kotlin +// First, increment the likes atomically +val likeResult = documentsDB.incrementDocumentAttribute( + databaseId = "", + collectionId = "", + documentId = "", + attribute = "likes", + value = 1 +) + +// Then, update other fields +val updateResult = documentsDB.updateDocument( + databaseId = "", + collectionId = "", + documentId = "", + data = mapOf( + "lastLikedBy" to userId, + "lastLikedAt" to Instant.now().toString() + ) +) +``` +```server-nodejs +// First, increment the likes atomically +const likeResult = await documentsDB.incrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', // attribute + value: 1 // value +}); + +// Then, update other fields +const updateResult = await documentsDB.updateDocument({ + databaseId: '', + collectionId: '', + documentId: '', + data: { + lastLikedBy: userId, + lastLikedAt: new Date().toISOString() + } +}); +``` +```server-python +# First, increment the likes atomically +like_result = documents_db.increment_document_attribute( + database_id = '', + collection_id = '', + document_id = '', + attribute = 'likes', # attribute + value = 1 # value +) + +# Then, update other fields +update_result = documents_db.update_document( + database_id = '', + collection_id = '', + document_id = '', + data = { + 'lastLikedBy': user_id, + 'lastLikedAt': datetime.now().isoformat() + } +) +``` +```rust +use serde_json::json; + +// First, increment the likes atomically +let like_result = documents_db.increment_document_attribute( + "", + "", + "", + "likes", // attribute + Some(1.0), // value + None, // max + None, // transaction_id +).await?; + +// Then, update other fields +let update_result = documents_db.update_document( + "", + "", + "", + Some(json!({ + "lastLikedBy": user_id, + "lastLikedAt": chrono::Utc::now().to_rfc3339() + })), + None, // permissions + None, // transaction_id +).await?; +``` +{% /multicode %} + +# Use transactions {% #use-transactions %} + +Atomic numeric operations accept `transactionId`. When provided, increments/decrements are staged and applied on commit. + +{% multicode %} +```client-web +await documentsDB.incrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', + value: 1, + transactionId: '' +}); +``` +```client-flutter +await documentsDB.incrementDocumentAttribute( + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', + value: 1, + transactionId: '' +); +``` +```client-apple +try await documentsDB.incrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "likes", + value: 1, + transactionId: "" +) +``` +```client-android-kotlin +documentsDB.incrementDocumentAttribute( + databaseId = "", + collectionId = "", + documentId = "", + attribute = "likes", + value = 1, + transactionId = "" +) +``` +```client-android-java +documentsDB.incrementDocumentAttribute( + "", + "", + "", + "likes", + 1, + "", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return null; + } + System.out.println(result); + return null; + }) +); +``` +```client-react-native +await documentsDB.incrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', + value: 1, + transactionId: '' +}); +``` +```server-nodejs +await documentsDB.incrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', + value: 1, + transactionId: '' +}); +``` +```server-deno +await documentsDB.incrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', + value: 1, + transactionId: '' +}); +``` +```server-python +documents_db.increment_document_attribute( + database_id = '', + collection_id = '', + document_id = '', + attribute = 'likes', + value = 1, + transaction_id = '' +) +``` +```rust +documents_db.increment_document_attribute( + "", + "", + "", + "likes", + Some(1.0), + None, // max + Some(""), // transaction_id +).await?; +``` +```server-php +$documentsDB->incrementDocumentAttribute( + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', + value: 1, + transactionId: '' +); +``` +```server-ruby +documents_db.increment_document_attribute( + database_id: '', + collection_id: '', + document_id: '', + attribute: 'likes', + value: 1, + transaction_id: '' +) +``` +```server-dotnet +await documentsDB.IncrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "likes", + value: 1, + transactionId: "" +); +``` +```server-dart +await documentsDB.incrementDocumentAttribute( + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', + value: 1, + transactionId: '' +); +``` +```server-swift +try await documentsDB.incrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "likes", + value: 1, + transactionId: "" +) +``` +```server-kotlin +documentsDB.incrementDocumentAttribute( + databaseId = "", + collectionId = "", + documentId = "", + attribute = "likes", + value = 1, + transactionId = "" +) +``` +```server-java +documentsDB.incrementDocumentAttribute( + "", + "", + "", + "likes", + 1, + "", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return null; + } + System.out.println(result); + return null; + }) +); +``` +{% /multicode %} + +## Explore related features + +- [Bulk operations](/docs/products/databases/documentsdb/bulk-operations) - Update multiple documents at once +- [Permissions](/docs/products/databases/documentsdb/permissions) - Control access to documents +- [Queries](/docs/products/databases/documentsdb/queries) - Find documents to update diff --git a/src/routes/docs/products/databases/documentsdb/backups/+page.markdoc b/src/routes/docs/products/databases/documentsdb/backups/+page.markdoc new file mode 100644 index 00000000000..629bab646ce --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/backups/+page.markdoc @@ -0,0 +1,88 @@ +--- +layout: article +title: Backups +description: Learn how to back up and restore your DocumentsDB databases, ensuring data security and seamless recovery. +--- + +Backups protect your DocumentsDB data by capturing a full copy of a database that you can restore later. Every backup is **encrypted** and taken as a **hot** backup, so your database keeps serving traffic with zero downtime and recovery stays fast. + +{% info title="Backups are available for all Pro and Enterprise customers." %} +{% /info %} + +You manage backups from a database's **Backups** tab, where you can automate backups with policies or create manual backups on demand. + +{% only_dark %} +![Backups tab](/images/docs/databases/documentsdb/dark/backups-tab.avif) +{% /only_dark %} +{% only_light %} +![Backups tab](/images/docs/databases/documentsdb/backups-tab.avif) +{% /only_light %} + +# Backup policies {% #backup-policies %} + +Backup policies automate your backups on a schedule. To create one, open your database's **Backups** tab and click **Create policy**, then choose a preset policy or add a custom one. + +{% only_dark %} +![Create backup policy](/images/docs/databases/documentsdb/dark/backup-policy.avif) +{% /only_dark %} +{% only_light %} +![Create backup policy](/images/docs/databases/documentsdb/backup-policy.avif) +{% /only_light %} + +The available options depend on your plan: + +- On the **Pro** plan, you get a **Daily** backup policy retained for 7 days. +- On the **Enterprise** plan, you get access to additional preset policies and custom policies, where you control how often backups run and how long they are retained. + +Click **Create** to save the policy. Your database is now set up for automated backups. + +# Manual backups {% #manual-backups %} + +You can create an on-demand backup whenever necessary. In your database's **Backups** tab, click **Manual backup**, then click **Create**. + +{% only_dark %} +![Manual backup](/images/docs/databases/documentsdb/dark/manual-backup.avif) +{% /only_dark %} +{% only_light %} +![Manual backup](/images/docs/databases/documentsdb/manual-backup.avif) +{% /only_light %} + +Manual backups are retained until you delete them. Depending on the size of your database, the backup may take some time to complete. You can monitor its progress in the backups list. + +# Restoring backups {% #restoring-backups %} + +To restore a database, you need an existing backup. + +1. Open your database's **Backups** tab. +2. In the backups list, open the **Actions** menu for the backup you want to restore. +3. Click **Restore**. +4. Enter a name for the new database and an optional database ID. +5. Click **Restore**. + +Depending on the size of your database, the restoration may take some time. The restore creates a new database from the backup, leaving the original untouched. + +# Backup security & performance {% #backup-security-and-performance %} + +All backups created with Appwrite are: + +1. **Encrypted**: + All backups are securely encrypted to ensure your data remains protected at all times. + +2. **Remotely stored**: + Backups are stored in a remote location, providing an additional layer of security and ensuring your data is always recoverable. + +3. **Hot backups**: + Backups are hot, meaning they occur with zero downtime, allowing you to recover data quickly without interrupting your projects and services. + +# Best practices {% #best-practices %} + +To ensure your backups are robust and effective, consider the following best practices: + +1. **Schedule regular backups**: + Add backup policies based on the frequency of database changes. Daily backups are often sufficient for most use cases. + +2. **Retain critical backups longer**: + Use custom policies with longer retention to keep backups of critical data for extended periods, ensuring historical records are available when needed. + +3. **Optimize backup policies based on data sensitivity**: + Tailor your backup frequency and retention settings according to the sensitivity and importance of the data. diff --git a/src/routes/docs/products/databases/documentsdb/bulk-operations/+page.markdoc b/src/routes/docs/products/databases/documentsdb/bulk-operations/+page.markdoc new file mode 100644 index 00000000000..e83d69316b4 --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/bulk-operations/+page.markdoc @@ -0,0 +1,567 @@ +--- +layout: article +title: Bulk operations +description: Perform bulk operations on documents within your collections for efficient data handling. +--- + +Appwrite DocumentsDB supports bulk operations for documents, allowing you to create, update, or delete multiple documents in a single request. This can significantly improve performance for apps as it allows you to reduce the number of API calls needed while working with large data sets. + +Bulk operations can only be performed via the server-side SDKs. The client-side SDKs do not support bulk operations by design to prevent abuse and protect against unexpected costs. This ensures that only trusted server environments can perform large-scale data operations. + +For client applications that need bulk-like functionality, consider using [Appwrite Functions](/docs/products/functions) with proper rate limiting and validation. + +{% info title="Important notes" %} +Bulk operations trigger Functions, Webhooks, or Realtime events for each document manipulated. Rather than a single event for the entire bulk operation, each document generates a separate event on the existing realtime channels for its operation type. +{% /info %} + +# Atomic behavior {% #atomic-behavior %} + +Bulk operations in Appwrite are **atomic**, meaning they follow an all-or-nothing approach. Either all documents in your bulk request succeed, or all documents fail. + +This atomicity ensures: +- **Data consistency**: Your database remains in a consistent state even if some operations would fail. +- **Race condition prevention**: Multiple clients can safely perform bulk operations simultaneously. +- **Simplified error handling**: You only need to handle complete success or complete failure scenarios. + +For example, if you attempt to create 100 documents and one fails due to a validation error, none of the 100 documents will be created. + +# Plan limits {% #plan-limits %} + +Bulk operations have different limits based on your Appwrite plan: + +| Plan | Documents per request | +|------|----------------------| +| Free | 100 | +| Pro | 1,000 | + +These limits apply to all bulk operations including create, update, upsert, and delete operations. If you need higher limits than what the Pro plan offers, you can [inquire](/contact-us/enterprise) about a custom plan. + +# Create documents {% #create-documents %} + +You can create multiple documents in a single request using the `createDocuments` method. + +{% info title="Custom timestamps" %} +When creating, updating or upserting in bulk, you can set `$createdAt` and `$updatedAt` for each document in the payload. Values must be ISO 8601 date-time strings. If omitted, Appwrite sets them automatically. +{% /info %} + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.createDocuments({ + databaseId: '', + collectionId: '', + documents: [ + { + $id: sdk.ID.unique(), + name: 'Document 1' + }, + { + $id: sdk.ID.unique(), + name: 'Document 2' + } + ] +}); +``` + +```server-python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB +from appwrite.id import ID + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +documents_db = DocumentsDB(client) + +result = documents_db.create_documents( + database_id = '', + collection_id = '', + documents = [ + { + '$id': ID.unique(), + 'name': 'Document 1' + }, + { + '$id': ID.unique(), + 'name': 'Document 2' + } + ] +) +``` + +```rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use appwrite::id::ID; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.create_documents( + "", + "", + vec![ + json!({ + "$id": ID::unique(), + "name": "Document 1" + }), + json!({ + "$id": ID::unique(), + "name": "Document 2" + }), + ], + None, + ).await?; + + Ok(()) +} +``` +{% /multicode %} + +# Update documents {% #update-documents %} + +{% info title="Permissions required" %} +You must grant **update** permissions to users at the **collection level** before users can update documents. +[Learn more about permissions](/docs/products/databases/documentsdb/permissions) +{% /info %} + +You can update multiple documents in a single request using the `updateDocuments` method. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.updateDocuments({ + databaseId: '', + collectionId: '', + data: { + status: 'published' + }, + queries: [ + sdk.Query.equal('status', 'draft') + ] +}); +``` + +```server-python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +documents_db = DocumentsDB(client) + +result = documents_db.update_documents( + database_id = '', + collection_id = '', + data = { + 'status': 'published' + }, + queries = [ + Query.equal('status', 'draft') + ] +) +``` + +```rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use appwrite::query::Query; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.update_documents( + "", + "", + Some(json!({ + "status": "published" + })), + Some(vec![ + Query::equal("status", "draft").to_string(), + ]), + None, + ).await?; + + Ok(()) +} +``` +{% /multicode %} + +# Upsert documents {% #upsert-documents %} + +{% info title="Permissions required" %} +You must grant **create** and **update** permissions to users at the **collection level** before users can create documents. +[Learn more about permissions](/docs/products/databases/documentsdb/permissions) +{% /info %} + +You can upsert multiple documents in a single request using the `upsertDocuments` method. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.upsertDocuments({ + databaseId: '', + collectionId: '', + documents: [ + { + $id: sdk.ID.unique(), + name: 'New Document 1' + }, + { + $id: 'document-id-2', // Existing document ID + name: 'New Document 2' + } + ] +}); +``` + +```server-python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB +from appwrite.id import ID + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +documents_db = DocumentsDB(client) + +result = documents_db.upsert_documents( + database_id = '', + collection_id = '', + documents = [ + { + '$id': ID.unique(), + 'name': 'New Document 1' + }, + { + '$id': 'document-id-2', # Existing document ID + 'name': 'New Document 2' + } + ] +) +``` + +```rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use appwrite::id::ID; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.upsert_documents( + "", + "", + vec![ + json!({ + "$id": ID::unique(), + "name": "New Document 1" + }), + json!({ + "$id": "document-id-2", // Existing document ID + "name": "New Document 2" + }), + ], + None, + ).await?; + + Ok(()) +} +``` +{% /multicode %} + +# Delete documents {% #delete-documents %} + +{% info title="Permissions required" %} +You must grant **delete** permissions to users at the **collection level** before users can delete documents. +[Learn more about permissions](/docs/products/databases/documentsdb/permissions) +{% /info %} + +You can delete multiple documents in a single request using the `deleteDocuments` method. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.deleteDocuments({ + databaseId: '', + collectionId: '', + queries: [ + sdk.Query.equal('status', 'archived') + ] +}); +``` + +```server-python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +documents_db = DocumentsDB(client) + +result = documents_db.delete_documents( + database_id = '', + collection_id = '', + queries = [ + Query.equal('status', 'archived') + ] +) +``` + +```rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use appwrite::query::Query; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.delete_documents( + "", + "", + Some(vec![ + Query::equal("status", "archived").to_string(), + ]), + None, + ).await?; + + Ok(()) +} +``` +{% /multicode %} + +{% info title="Queries for deletion" %} + +When deleting documents, you must specify queries to filter which documents to delete. +If no queries are provided, all documents in the collection will be deleted. +[Learn more about queries](/docs/products/databases/documentsdb/queries). + +{% /info %} + +# Use transactions {% #use-transactions %} + +All bulk operations accept `transactionId`. When provided, Appwrite stages the bulk request and applies it on commit. See [Transactions](/docs/products/databases/documentsdb/transactions). + +{% multicode %} +```server-nodejs +await documentsDB.createDocuments({ + databaseId: '', + collectionId: '', + documents: [ + { $id: sdk.ID.unique(), name: 'One' }, + { $id: sdk.ID.unique(), name: 'Two' } + ], + transactionId: '' +}); +``` +```server-python +documents_db.create_documents( + database_id = '', + collection_id = '', + documents = [ + { '$id': ID.unique(), 'name': 'One' }, + { '$id': ID.unique(), 'name': 'Two' } + ], + transaction_id = '' +) +``` +```server-deno +await documentsDB.createDocuments({ + databaseId: '', + collectionId: '', + documents: [ + { $id: sdk.ID.unique(), name: 'One' }, + { $id: sdk.ID.unique(), name: 'Two' } + ], + transactionId: '' +}); +``` +```server-php +$documentsDB->createDocuments( + databaseId: '', + collectionId: '', + documents: [ + [ '$id' => ID::unique(), 'name' => 'One' ], + [ '$id' => ID::unique(), 'name' => 'Two' ] + ], + transactionId: '' +); +``` +```server-ruby +documents_db.create_documents( + database_id: '', + collection_id: '', + documents: [ + { '$id' => ID.unique(), 'name' => 'One' }, + { '$id' => ID.unique(), 'name' => 'Two' } + ], + transaction_id: '' +) +``` +```server-dotnet +await documentsDB.CreateDocuments( + databaseId: "", + collectionId: "", + documents: new List> + { + new Dictionary + { + ["$id"] = ID.Unique(), + ["name"] = "One" + }, + new Dictionary + { + ["$id"] = ID.Unique(), + ["name"] = "Two" + } + }, + transactionId: "" +); +``` +```server-dart +await documentsDB.createDocuments( + databaseId: '', + collectionId: '', + documents: [ + { '\$id': ID.unique(), 'name': 'One' }, + { '\$id': ID.unique(), 'name': 'Two' } + ], + transactionId: '' +); +``` +```server-swift +try await documentsDB.createDocuments( + databaseId: "", + collectionId: "", + documents: [ + ["$id": ID.unique(), "name": "One"], + ["$id": ID.unique(), "name": "Two"] + ], + transactionId: "" +) +``` +```server-kotlin +documentsDB.createDocuments( + databaseId = "", + collectionId = "", + documents = listOf( + mapOf("\$id" to ID.unique(), "name" to "One"), + mapOf("\$id" to ID.unique(), "name" to "Two") + ), + transactionId = "" +) +``` +```server-java +documentsDB.createDocuments( + "", + "", + Arrays.asList( + Map.of( + "$id", ID.unique(), + "name", "One" + ), + Map.of( + "$id", ID.unique(), + "name", "Two" + ) + ), + "", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return null; + } + System.out.println(result); + return null; + }) +); +``` +```rust +let result = documents_db.create_documents( + "", + "", + vec![ + json!({ + "$id": ID::unique(), + "name": "One" + }), + json!({ + "$id": ID::unique(), + "name": "Two" + }), + ], + Some(""), +).await?; +``` +{% /multicode %} diff --git a/src/routes/docs/products/databases/documentsdb/collections/+page.markdoc b/src/routes/docs/products/databases/documentsdb/collections/+page.markdoc new file mode 100644 index 00000000000..09ec098633d --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/collections/+page.markdoc @@ -0,0 +1,526 @@ +--- +layout: article +title: Collections +description: Organize documents with Appwrite DocumentsDB collections. Learn how to create collections, configure permissions, and add indexes for fast queries. +--- +Appwrite uses collections as containers of documents. +Collections are schemaless, so documents in the same collection can hold different fields. You shape data in your application instead of defining columns up front. + +# Create collection {% #create-collection %} +You can create collections using the Appwrite Console, a [Server SDK](/docs/sdks#server), or using the [CLI](/docs/tooling/command-line/installation). +{% tabs %} + +{% tabsitem #console title="Console" %} +Head to the **Databases** page, open a [database](/docs/products/databases/documentsdb/databases), and click **Create collection**. + +{% /tabsitem %} + +{% tabsitem #server-sdk title="Server SDK" %} +You can also create collections programmatically using a [Server SDK](/docs/sdks#server). Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/platform/api-keys). + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.createCollection({ + databaseId: '', + collectionId: '', + name: '', + permissions: [sdk.Permission.read(sdk.Role.any())], // optional + documentSecurity: false // optional +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.createCollection({ + databaseId: '', + collectionId: '', + name: '', + permissions: [sdk.Permission.read(sdk.Role.any())], // optional + documentSecurity: false // optional +}); +``` +```php +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$documentsDB = new DocumentsDB($client); + +$result = $documentsDB->createCollection( + databaseId: '', + collectionId: '', + name: '', + permissions: [Permission::read(Role::any())], // optional + documentSecurity: false // optional +); +``` +```python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB +from appwrite.permission import Permission +from appwrite.role import Role + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +documents_db = DocumentsDB(client) + +result = documents_db.create_collection( + database_id = '', + collection_id = '', + name = '', + permissions = [Permission.read(Role.any())], # optional + document_security = False # optional +) +``` +```ruby +require 'appwrite' + +include Appwrite +include Appwrite::Permission +include Appwrite::Role + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +documents_db = DocumentsDB.new(client) + +result = documents_db.create_collection( + database_id: '', + collection_id: '', + name: '', + permissions: [Permission.read(Role.any())], # optional + document_security: false # optional +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +Collection result = await documentsDB.CreateCollection( + databaseId: "", + collectionId: "", + name: "", + permissions: new List { Permission.Read(Role.Any()) }, // optional + documentSecurity: false // optional +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; +import 'package:dart_appwrite/permission.dart'; +import 'package:dart_appwrite/role.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +DocumentsDB documentsDB = DocumentsDB(client); + +Collection result = await documentsDB.createCollection( + databaseId: '', + collectionId: '', + name: '', + permissions: [Permission.read(Role.any())], // (optional) + documentSecurity: false, // (optional) +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Permission +import io.appwrite.Role +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val documentsDB = DocumentsDB(client) + +val response = documentsDB.createCollection( + databaseId = "", + collectionId = "", + name = "", + permissions = listOf(Permission.read(Role.any())), // optional + documentSecurity = false, // optional +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.createCollection( + "", + "", + "", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let documentsDB = DocumentsDB(client) + +let collection = try await documentsDB.createCollection( + databaseId: "", + collectionId: "", + name: "", + permissions: [Permission.read(Role.any())], // optional + documentSecurity: false // optional +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::DocumentsDB; +use appwrite::permission::Permission; +use appwrite::role::Role; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new(); + client.set_endpoint("https://.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project(""); // Your project ID + client.set_key(""); // Your secret API key + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.create_collection( + "", + "", + "", + Some(vec![Permission::read(Role::any()).to_string()]), // permissions (optional) + Some(false), // documentSecurity (optional) + None, // enabled (optional) + None, // attributes (optional) + None, // indexes (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +{% /multicode %} +{% /tabsitem %} +{% /tabs %} + +# Permissions {% #permissions %} +Appwrite uses permissions to control data access. +For security, only users that are granted permissions can access a resource. + +By default, Appwrite doesn't grant permissions to any users when a new collection is created. +This means users can't create documents or read, update, and delete existing documents until you grant access. + +Set `documentSecurity` to `true` on a collection to configure permissions on individual documents. A user then needs either collection level or document level permissions to access a document. + +[Learn about configuring permissions](/docs/products/databases/documentsdb/permissions). + +# Indexes {% #indexes %} +Databases use indexes to quickly locate data without scanning every document. +To ensure the best performance, Appwrite recommends an index for every field you query. +If you plan to query multiple fields in a single query, creating an index with **all** queried fields will yield optimal performance. + +The following indexes are currently supported: + +| Type | Description | +|------------|--------------------------------------------------------------------------------------------------------------| +| `key` | Plain index to allow queries. | +| `unique` | Unique index to disallow duplicates. | +| `fulltext` | For searching within text fields. Required for the [search query method](/docs/products/databases/documentsdb/queries). | + +You can create an index by navigating to your collection's **Indexes** tab or by using your favorite [Server SDK](/docs/sdks#server). + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.createIndex({ + databaseId: '', + collectionId: '', + key: 'title_index', + type: sdk.DocumentsDBIndexType.Key, + attributes: ['title'] +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.createIndex({ + databaseId: '', + collectionId: '', + key: 'title_index', + type: sdk.DocumentsDBIndexType.Key, + attributes: ['title'] +}); +``` +```php +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$documentsDB = new DocumentsDB($client); + +$result = $documentsDB->createIndex( + databaseId: '', + collectionId: '', + key: 'title_index', + type: DocumentsDBIndexType::KEY(), + attributes: ['title'] +); +``` +```python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB +from appwrite.enums import DocumentsDBIndexType + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +documents_db = DocumentsDB(client) + +result = documents_db.create_index( + database_id = '', + collection_id = '', + key = 'title_index', + type = DocumentsDBIndexType.KEY, + attributes = ['title'] +) +``` +```ruby +require 'appwrite' + +include Appwrite +include Appwrite::Enums + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +documents_db = DocumentsDB.new(client) + +result = documents_db.create_index( + database_id: '', + collection_id: '', + key: 'title_index', + type: DocumentsDBIndexType::KEY, + attributes: ['title'] +) +``` +```csharp +using Appwrite; +using Appwrite.Enums; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +Index result = await documentsDB.CreateIndex( + databaseId: "", + collectionId: "", + key: "title_index", + type: DocumentsDBIndexType.Key, + attributes: new List { "title" } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; +import 'package:dart_appwrite/enums.dart' as enums; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +DocumentsDB documentsDB = DocumentsDB(client); + +Index result = await documentsDB.createIndex( + databaseId: '', + collectionId: '', + key: 'title_index', + type: enums.DocumentsDBIndexType.key, + attributes: ['title'], +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.enums.DocumentsDBIndexType +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val documentsDB = DocumentsDB(client) + +val response = documentsDB.createIndex( + databaseId = "", + collectionId = "", + key = "title_index", + type = DocumentsDBIndexType.KEY, + attributes = listOf("title"), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.enums.DocumentsDBIndexType; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.createIndex( + "", + "", + "title_index", + DocumentsDBIndexType.KEY, + List.of("title"), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite +import AppwriteEnums + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let documentsDB = DocumentsDB(client) + +let index = try await documentsDB.createIndex( + databaseId: "", + collectionId: "", + key: "title_index", + type: .key, + attributes: ["title"] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::DocumentsDB; +use appwrite::enums::DocumentsDBIndexType; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new(); + client.set_endpoint("https://.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project(""); // Your project ID + client.set_key(""); // Your secret API key + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.create_index( + "", + "", + "title_index", + DocumentsDBIndexType::Key, + vec!["title"], + None, // orders (optional) + None, // lengths (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +{% /multicode %} diff --git a/src/routes/docs/products/databases/documentsdb/databases/+page.markdoc b/src/routes/docs/products/databases/documentsdb/databases/+page.markdoc new file mode 100644 index 00000000000..f2f21db6e45 --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/databases/+page.markdoc @@ -0,0 +1,218 @@ +--- +layout: article +title: Databases +description: Dive deeper into Appwrite DocumentsDB and database configuration. Learn how to create and manage multiple databases for your application. +--- +Databases are the largest organizational unit in Appwrite. +Each database contains a group of [collections](/docs/products/databases/documentsdb/collections). + +# Create in Console {% #create-in-console %} +The easiest way to create a database is using the Appwrite Console. +Navigate to the **Databases** page and click **Create database**, choose **DocumentsDB** as the database type, and select your preferred tier. + +{% only_dark %} +![Create database](/images/docs/databases/documentsdb/dark/create-database.avif) +{% /only_dark %} +{% only_light %} +![Create database](/images/docs/databases/documentsdb/create-database.avif) +{% /only_light %} + +# Create using Server SDKs {% #create-using-server-sdks %} +You can programmatically create databases using a [Server SDK](/docs/sdks#server). Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/platform/api-keys). + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.create({ + databaseId: '', + name: '' +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.create({ + databaseId: '', + name: '' +}); +``` +```php +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$documentsDB = new DocumentsDB($client); + +$result = $documentsDB->create( + databaseId: '', + name: '' +); +``` +```python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +documents_db = DocumentsDB(client) + +result = documents_db.create( + database_id = '', + name = '' +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +documents_db = DocumentsDB.new(client) + +result = documents_db.create( + database_id: '', + name: '' +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +Database result = await documentsDB.Create( + databaseId: "", + name: "" +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +DocumentsDB documentsDB = DocumentsDB(client); + +Database result = await documentsDB.create( + databaseId: '', + name: '', +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val documentsDB = DocumentsDB(client) + +val response = documentsDB.create( + databaseId = "", + name = "", +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.create( + "", + "", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let documentsDB = DocumentsDB(client) + +let database = try await documentsDB.create( + databaseId: "", + name: "" +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::DocumentsDB; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new(); + client.set_endpoint("https://.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project(""); // Your project ID + client.set_key(""); // Your secret API key + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.create( + "", + "", + None, // enabled (optional) + None, // dedicatedDatabaseId (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +{% /multicode %} + diff --git a/src/routes/docs/products/databases/documentsdb/documents/+page.markdoc b/src/routes/docs/products/databases/documentsdb/documents/+page.markdoc new file mode 100644 index 00000000000..6bb47042249 --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/documents/+page.markdoc @@ -0,0 +1,1189 @@ +--- +layout: article +title: Documents +description: Create, read, update, and delete documents in Appwrite DocumentsDB. Learn how to work with schemaless JSON documents in your collections. +--- +Each piece of data in Appwrite DocumentsDB is a document. +Documents are schemaless JSON, so each document in a collection can hold its own set of fields. + +# Create documents {% #create-documents %} +{% info title="Permissions required" %} +You must grant _create_ permissions to users at the _collection level_ before users can create documents. +[Learn more about permissions](#permissions) +{% /info %} + +Use the `createDocument` method to add a document to a collection. Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/platform/api-keys). + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.createDocument({ + databaseId: '', + collectionId: '', + documentId: sdk.ID.unique(), + data: { title: 'Hamlet', year: 1601 }, + permissions: [sdk.Permission.read(sdk.Role.any())] // optional +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.createDocument({ + databaseId: '', + collectionId: '', + documentId: sdk.ID.unique(), + data: { title: 'Hamlet', year: 1601 }, + permissions: [sdk.Permission.read(sdk.Role.any())] // optional +}); +``` +```php +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$documentsDB = new DocumentsDB($client); + +$result = $documentsDB->createDocument( + databaseId: '', + collectionId: '', + documentId: ID::unique(), + data: ['title' => 'Hamlet', 'year' => 1601], + permissions: [Permission::read(Role::any())] // optional +); +``` +```python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB +from appwrite.id import ID +from appwrite.permission import Permission +from appwrite.role import Role + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +documents_db = DocumentsDB(client) + +result = documents_db.create_document( + database_id = '', + collection_id = '', + document_id = ID.unique(), + data = { "title": "Hamlet", "year": 1601 }, + permissions = [Permission.read(Role.any())] # optional +) +``` +```ruby +require 'appwrite' + +include Appwrite +include Appwrite::Permission +include Appwrite::Role + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +documents_db = DocumentsDB.new(client) + +result = documents_db.create_document( + database_id: '', + collection_id: '', + document_id: ID.unique(), + data: { "title" => "Hamlet", "year" => 1601 }, + permissions: [Permission.read(Role.any())] # optional +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +Document result = await documentsDB.CreateDocument( + databaseId: "", + collectionId: "", + documentId: ID.Unique(), + data: new { title = "Hamlet", year = 1601 }, + permissions: new List { Permission.Read(Role.Any()) } // optional +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; +import 'package:dart_appwrite/permission.dart'; +import 'package:dart_appwrite/role.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +DocumentsDB documentsDB = DocumentsDB(client); + +Document result = await documentsDB.createDocument( + databaseId: '', + collectionId: '', + documentId: ID.unique(), + data: { "title": "Hamlet", "year": 1601 }, + permissions: [Permission.read(Role.any())], // (optional) +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.ID +import io.appwrite.Permission +import io.appwrite.Role +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val documentsDB = DocumentsDB(client) + +val response = documentsDB.createDocument( + databaseId = "", + collectionId = "", + documentId = ID.unique(), + data = mapOf("title" to "Hamlet", "year" to 1601), + permissions = listOf(Permission.read(Role.any())), // optional +) +``` +```java +import io.appwrite.Client; +import io.appwrite.ID; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; +import java.util.Map; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.createDocument( + "", + "", + ID.unique(), + Map.of("title", "Hamlet", "year", 1601), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let documentsDB = DocumentsDB(client) + +let document = try await documentsDB.createDocument( + databaseId: "", + collectionId: "", + documentId: ID.unique(), + data: ["title": "Hamlet", "year": 1601], + permissions: [Permission.read(Role.any())] // optional +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::DocumentsDB; +use appwrite::id::ID; +use appwrite::permission::Permission; +use appwrite::role::Role; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new(); + client.set_endpoint("https://.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project(""); // Your project ID + client.set_key(""); // Your secret API key + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.create_document( + "", + "", + ID::unique(), + serde_json::json!({ "title": "Hamlet", "year": 1601 }), + Some(vec![Permission::read(Role::any()).to_string()]), // optional + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite documents-db create-document \ + --database-id \ + --collection-id \ + --document-id 'unique()' \ + --data '{ "title": "Hamlet", "year": 1601 }' +``` +{% /multicode %} + +# List documents {% #list-documents %} +Use the `listDocuments` method to read documents from a collection. Pass [queries](/docs/products/databases/documentsdb/queries) to filter, order, and paginate the results. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + sdk.Query.equal('title', 'Hamlet') + ] +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + sdk.Query.equal('title', 'Hamlet') + ] +}); +``` +```php +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$documentsDB = new DocumentsDB($client); + +$result = $documentsDB->listDocuments( + databaseId: '', + collectionId: '', + queries: [ + Query::equal('title', ['Hamlet']) + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +documents_db = DocumentsDB(client) + +result = documents_db.list_documents( + database_id = '', + collection_id = '', + queries = [ + Query.equal('title', 'Hamlet') + ] +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +documents_db = DocumentsDB.new(client) + +result = documents_db.list_documents( + database_id: '', + collection_id: '', + queries: [ + Query.equal('title', ['Hamlet']) + ] +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +DocumentList result = await documentsDB.ListDocuments( + databaseId: "", + collectionId: "", + queries: new List { + Query.Equal("title", new List { "Hamlet" }) + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +DocumentsDB documentsDB = DocumentsDB(client); + +DocumentList result = await documentsDB.listDocuments( + databaseId: '', + collectionId: '', + queries: [ + Query.equal('title', 'Hamlet') + ], +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val documentsDB = DocumentsDB(client) + +val response = documentsDB.listDocuments( + databaseId = "", + collectionId = "", + queries = listOf( + Query.equal("title", "Hamlet") + ), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.Query; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.listDocuments( + "", + "", + List.of( + Query.equal("title", List.of("Hamlet")) + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let documentsDB = DocumentsDB(client) + +let documentList = try await documentsDB.listDocuments( + databaseId: "", + collectionId: "", + queries: [ + Query.equal("title", value: "Hamlet") + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::DocumentsDB; +use appwrite::query::Query; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new(); + client.set_endpoint("https://.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project(""); // Your project ID + client.set_key(""); // Your secret API key + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.list_documents( + "", + "", + Some(vec![Query::equal("title", vec!["Hamlet"])]), + None, // transactionId (optional) + None, // total (optional) + None, // ttl (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite documents-db list-documents \ + --database-id \ + --collection-id \ + --queries '{"method":"equal","attribute":"title","values":["Hamlet"]}' +``` +{% /multicode %} + +# Update document {% #update-document %} +Use the `updateDocument` method to update a document. With the patch behavior, you only pass the fields you want to change. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.updateDocument({ + databaseId: '', + collectionId: '', + documentId: '', + data: { year: 1602 } +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.updateDocument({ + databaseId: '', + collectionId: '', + documentId: '', + data: { year: 1602 } +}); +``` +```php +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$documentsDB = new DocumentsDB($client); + +$result = $documentsDB->updateDocument( + databaseId: '', + collectionId: '', + documentId: '', + data: ['year' => 1602] +); +``` +```python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +documents_db = DocumentsDB(client) + +result = documents_db.update_document( + database_id = '', + collection_id = '', + document_id = '', + data = { "year": 1602 } +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +documents_db = DocumentsDB.new(client) + +result = documents_db.update_document( + database_id: '', + collection_id: '', + document_id: '', + data: { "year" => 1602 } +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +Document result = await documentsDB.UpdateDocument( + databaseId: "", + collectionId: "", + documentId: "", + data: new { year = 1602 } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +DocumentsDB documentsDB = DocumentsDB(client); + +Document result = await documentsDB.updateDocument( + databaseId: '', + collectionId: '', + documentId: '', + data: { "year": 1602 }, +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val documentsDB = DocumentsDB(client) + +val response = documentsDB.updateDocument( + databaseId = "", + collectionId = "", + documentId = "", + data = mapOf("year" to 1602), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; +import java.util.Map; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.updateDocument( + "", + "", + "", + Map.of("year", 1602), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let documentsDB = DocumentsDB(client) + +let document = try await documentsDB.updateDocument( + databaseId: "", + collectionId: "", + documentId: "", + data: ["year": 1602] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::DocumentsDB; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new(); + client.set_endpoint("https://.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project(""); // Your project ID + client.set_key(""); // Your secret API key + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.update_document( + "", + "", + "", + Some(serde_json::json!({ "year": 1602 })), + None, // permissions (optional) + None, // transactionId (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite documents-db update-document \ + --database-id \ + --collection-id \ + --document-id \ + --data '{ "year": 1602 }' +``` +{% /multicode %} + +# Upsert documents {% #upsert-documents %} +Use the `upsertDocument` method to create a document if it doesn't exist, or update it if it does. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.upsertDocument({ + databaseId: '', + collectionId: '', + documentId: '', + data: { title: 'Hamlet', year: 1603 } +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.upsertDocument({ + databaseId: '', + collectionId: '', + documentId: '', + data: { title: 'Hamlet', year: 1603 } +}); +``` +```php +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$documentsDB = new DocumentsDB($client); + +$result = $documentsDB->upsertDocument( + databaseId: '', + collectionId: '', + documentId: '', + data: ['title' => 'Hamlet', 'year' => 1603] +); +``` +```python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +documents_db = DocumentsDB(client) + +result = documents_db.upsert_document( + database_id = '', + collection_id = '', + document_id = '', + data = { "title": "Hamlet", "year": 1603 } +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +documents_db = DocumentsDB.new(client) + +result = documents_db.upsert_document( + database_id: '', + collection_id: '', + document_id: '', + data: { "title" => "Hamlet", "year" => 1603 } +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +Document result = await documentsDB.UpsertDocument( + databaseId: "", + collectionId: "", + documentId: "", + data: new { title = "Hamlet", year = 1603 } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +DocumentsDB documentsDB = DocumentsDB(client); + +Document result = await documentsDB.upsertDocument( + databaseId: '', + collectionId: '', + documentId: '', + data: { "title": "Hamlet", "year": 1603 }, +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val documentsDB = DocumentsDB(client) + +val response = documentsDB.upsertDocument( + databaseId = "", + collectionId = "", + documentId = "", + data = mapOf("title" to "Hamlet", "year" to 1603), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; +import java.util.Map; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.upsertDocument( + "", + "", + "", + Map.of("title", "Hamlet", "year", 1603), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let documentsDB = DocumentsDB(client) + +let document = try await documentsDB.upsertDocument( + databaseId: "", + collectionId: "", + documentId: "", + data: ["title": "Hamlet", "year": 1603] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::DocumentsDB; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new(); + client.set_endpoint("https://.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project(""); // Your project ID + client.set_key(""); // Your secret API key + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.upsert_document( + "", + "", + "", + Some(serde_json::json!({ "title": "Hamlet", "year": 1603 })), + None, // permissions (optional) + None, // transactionId (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite documents-db upsert-document \ + --database-id \ + --collection-id \ + --document-id \ + --data '{ "title": "Hamlet", "year": 1603 }' +``` +{% /multicode %} + +# Delete document {% #delete-document %} +Use the `deleteDocument` method to remove a document from a collection. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.deleteDocument({ + databaseId: '', + collectionId: '', + documentId: '' +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.deleteDocument({ + databaseId: '', + collectionId: '', + documentId: '' +}); +``` +```php +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$documentsDB = new DocumentsDB($client); + +$result = $documentsDB->deleteDocument( + databaseId: '', + collectionId: '', + documentId: '' +); +``` +```python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +documents_db = DocumentsDB(client) + +result = documents_db.delete_document( + database_id = '', + collection_id = '', + document_id = '' +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +documents_db = DocumentsDB.new(client) + +result = documents_db.delete_document( + database_id: '', + collection_id: '', + document_id: '' +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +await documentsDB.DeleteDocument( + databaseId: "", + collectionId: "", + documentId: "" +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +DocumentsDB documentsDB = DocumentsDB(client); + +await documentsDB.deleteDocument( + databaseId: '', + collectionId: '', + documentId: '', +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val documentsDB = DocumentsDB(client) + +documentsDB.deleteDocument( + databaseId = "", + collectionId = "", + documentId = "", +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.deleteDocument( + "", + "", + "", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println("Deleted"); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let documentsDB = DocumentsDB(client) + +try await documentsDB.deleteDocument( + databaseId: "", + collectionId: "", + documentId: "" +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::DocumentsDB; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new(); + client.set_endpoint("https://.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project(""); // Your project ID + client.set_key(""); // Your secret API key + + let documents_db = DocumentsDB::new(&client); + + documents_db.delete_document( + "", + "", + "", + None, // transactionId (optional) + ).await?; + + Ok(()) +} +``` +```bash +appwrite documents-db delete-document \ + --database-id \ + --collection-id \ + --document-id +``` +{% /multicode %} + +# Permissions {% #permissions %} +To access documents through a [Client SDK](/docs/sdks#client), you must grant the relevant permissions. +By default, Appwrite doesn't grant any user permissions when a new collection is created. + +You can configure permissions at the collection level, or enable `documentSecurity` on the collection to also set permissions on individual documents. + +[Learn about configuring permissions](/docs/products/databases/documentsdb/permissions). diff --git a/src/routes/docs/products/databases/documentsdb/json-exports/+page.markdoc b/src/routes/docs/products/databases/documentsdb/json-exports/+page.markdoc new file mode 100644 index 00000000000..05d23aae415 --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/json-exports/+page.markdoc @@ -0,0 +1,96 @@ +--- +layout: article +title: JSON exports +description: Export documents from a DocumentsDB collection to a JSON file. Share datasets, create custom backups, or move data to another system without writing custom scripts. +--- + +Appwrite's JSON Export feature allows you to export documents from a collection to a JSON file. This is especially useful for creating custom backups, sharing data with other teams, or moving datasets to another system. + +# Export configuration {% #export-configuration %} + +Before exporting, you can configure a few options to control the contents of the output. These settings let you export exactly the data you need. + +## Select fields {% #select-fields %} + +You can choose which fields to include in your export. By default, every field is exported, but selecting specific fields creates cleaner, more focused datasets that are easier to work with. + +{% info title="Good to know" %} +System fields like `$id`, `$permissions`, `$createdAt`, and `$updatedAt` are always included in the export. +{% /info %} + +## Filter documents {% #filter-documents %} + +You can apply queries to export only the documents you need. This is especially useful when you want to export a subset of a collection for a specific use case. + +# Export format {% #export-format %} + +Each document is exported as a JSON object that includes its system fields and data fields. The output file is an array of these objects. + +An example of exported data: + +```json +[ + { + "$id": "book-1", + "$permissions": ["read(\"any\")"], + "$createdAt": "2025-08-10T12:34:56.000Z", + "$updatedAt": "2025-08-10T12:34:56.000Z", + "title": "Harry Potter and the Sorcerer's Stone", + "author": "J.K. Rowling", + "year": 1997, + "available": true + } +] +``` + +# Timestamps {% #timestamps %} + +The `$createdAt` and `$updatedAt` fields are exported in ISO 8601 format, making them compatible with most tools that consume JSON. + +# Permissions {% #permissions %} + +If document level security is enabled for your collection, the `$permissions` field contains the permission strings for each document. Permission strings are formatted as an array of role definitions. + +The roles used are API strings that can be found in the [permissions documentation](/docs/apis/rest#roles). + +# Background processing {% #background-processing %} + +Large exports run as background tasks to avoid blocking your workflow. When an export completes, you'll receive an email with a short-lived download link to retrieve your JSON file. + +This means you can start an export, close the Console, and return later to download your file. The Console displays a floating progress bar while the export is active. + +# Export documents from the Console {% #export-console %} + +{% only_dark %} +![JSON export from the collection toolbar](/images/docs/databases/dark/documentsdb-import-export.avif) +{% /only_dark %} +{% only_light %} +![JSON export from the collection toolbar](/images/docs/databases/documentsdb-import-export.avif) +{% /only_light %} + +To export documents using the Appwrite Console: + +1. Go to your project and navigate to **Databases** +2. Select your database and open the target collection +3. Click the export (download) icon in the collection toolbar +4. Configure your export options: + - Choose which fields to include (optional) + - Apply queries to filter documents (optional) +5. Start the export + +The export begins processing in the background. You'll see a progress indicator and receive an email when the export is ready to download. + +# Use cases {% #use-cases %} + +JSON exports are useful for many common workflows: + +- **Custom backups**: Archive specific data subsets for record-keeping +- **Data sharing**: Hand off datasets to other teams +- **Migration preparation**: Extract data for migration to other systems +- **Reporting**: Feed data into tools that consume JSON + +# Additional resources {% #additional-resources %} + +- [JSON imports](/docs/products/databases/documentsdb/json-imports) +- [Documents](/docs/products/databases/documentsdb/documents) +- [Database permissions](/docs/products/databases/documentsdb/permissions) diff --git a/src/routes/docs/products/databases/documentsdb/json-imports/+page.markdoc b/src/routes/docs/products/databases/documentsdb/json-imports/+page.markdoc new file mode 100644 index 00000000000..5749ebf282b --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/json-imports/+page.markdoc @@ -0,0 +1,94 @@ +--- +layout: article +title: JSON imports +description: Create documents in a DocumentsDB collection by uploading a JSON file. Seed test data, restore a dataset, or migrate from another system without writing custom scripts. +--- + +Appwrite's JSON Import feature allows you to create multiple documents in a collection by uploading a single JSON file. This is especially useful for importing existing data, seeding test environments, or migrating from other systems. + +# Prepare your JSON file {% #prepare-file %} + +Your JSON file is an array of objects, where each object becomes one document in the collection. DocumentsDB is schemaless, so each object can hold its own set of fields. + +An example of a valid JSON file: + +```json +[ + { + "title": "Harry Potter and the Sorcerer's Stone", + "author": "J.K. Rowling", + "year": 1997, + "available": true + }, + { + "title": "The Fellowship of the Ring", + "author": "J.R.R. Tolkien", + "year": 1954, + "available": true + } +] +``` + +Each object is validated before being imported. + +# Custom document IDs {% #custom-ids %} + +You can include a `$id` field on any object to set a custom document ID. If you omit it, Appwrite generates a unique ID for the document automatically. + +```json +[ + { + "$id": "book-1", + "title": "Harry Potter and the Sorcerer's Stone", + "author": "J.K. Rowling" + } +] +``` + +# Handle duplicate IDs {% #duplicate-ids %} + +When an imported document uses a `$id` that already exists in the collection, Appwrite decides what to do based on the duplicate handling you choose: + +- **Fail** (default): the import stops at the first conflicting ID. +- **Skip**: conflicting documents are ignored, and the rest are imported. +- **Overwrite**: existing documents are replaced with the imported version. + +# Permissions {% #permissions %} + +You can set document level permissions by including a `$permissions` field with an array of permission strings. Make sure document level security is enabled for your collection. + +```json +[ + { + "$id": "book-1", + "$permissions": ["read(\"any\")", "update(\"users\")", "delete(\"user:user_id\")"], + "title": "Harry Potter and the Sorcerer's Stone" + } +] +``` + +The roles used are API strings that can be found in the [permissions documentation](/docs/apis/rest#roles). + +# Import documents from the Console {% #import-console %} + +{% only_dark %} +![JSON import from the collection toolbar](/images/docs/databases/dark/documentsdb-import-export.avif) +{% /only_dark %} +{% only_light %} +![JSON import from the collection toolbar](/images/docs/databases/documentsdb-import-export.avif) +{% /only_light %} + +To import documents using the Appwrite Console: + +1. Go to your project and navigate to **Databases** +2. Select your database and open the target collection +3. Click the import (upload) icon in the collection toolbar +4. Upload a new JSON file or choose an existing file from your Storage bucket + +JSON imports run as background tasks. The Console displays a floating progress bar while the import is active. + +# Additional resources {% #additional-resources %} + +- [JSON exports](/docs/products/databases/documentsdb/json-exports) +- [Documents](/docs/products/databases/documentsdb/documents) +- [Database permissions](/docs/products/databases/documentsdb/permissions) diff --git a/src/routes/docs/products/databases/documentsdb/order/+page.markdoc b/src/routes/docs/products/databases/documentsdb/order/+page.markdoc new file mode 100644 index 00000000000..cf7bb7a1f73 --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/order/+page.markdoc @@ -0,0 +1,336 @@ +--- +layout: article +title: Order +description: Understand how to do data ordering in Appwrite DocumentsDB. Learn how to order and sort your database records for efficient data retrieval. +--- + +You can order results returned by Appwrite DocumentsDB by using an order query. +For best performance, create an [index](/docs/products/databases/documentsdb/collections#indexes) on the field you plan to order by. + +# Ordering one field {% #one-field %} + +When querying using the [listDocuments](/docs/products/databases/documentsdb/documents#list-documents) endpoint, +you can specify the order of the documents returned using the `Query.orderAsc()` and `Query.orderDesc()` query methods. + +{% multicode %} +```client-web +import { Client, Query, DocumentsDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const documentsDB = new DocumentsDB(client); + +documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + Query.orderAsc('title'), + ] +}); +``` + +```client-flutter +import 'package:appwrite/appwrite.dart'; + +void main() async { + final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + + final documentsDB = DocumentsDB(client); + + try { + final documents = await documentsDB.listDocuments( + databaseId: '', + collectionId: '', + queries: [ + Query.orderAsc('title') + ] + ); + } on AppwriteException catch(e) { + print(e); + } +} +``` + +```client-apple +import Appwrite +import AppwriteModels + +func main() async throws { + let client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + + let documentsDB = DocumentsDB(client) + + do { + let documents = try await documentsDB.listDocuments( + databaseId: "", + collectionId: "", + queries: [ + Query.orderAsc("title") + ] + ) + } catch { + print(error.localizedDescription) + } +} +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.DocumentsDB + +suspend fun main() { + val client = Client(applicationContext) + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + + val documentsDB = DocumentsDB(client) + + try { + val documents = documentsDB.listDocuments( + databaseId = "", + collectionId = "", + queries = [ + Query.orderAsc("title") + ] + ) + } catch (e: AppwriteException) { + Log.e("Appwrite", e.message) + } +} +``` + +```graphql +query { + documentsDBListDocuments( + databaseId: "", + collectionId: "" + queries: ["orderAsc(\"title\")"] + ) { + total + documents { + _id + data + } + } +} +``` + +{% /multicode %} + +# Multiple fields {% #multiple-fields %} +To sort based on multiple fields, simply provide multiple query methods. +For better performance, create an index on the first field that you order by. + +In the example below, the movies returned will be first sorted by `title` in ascending order, then sorted by `year` in descending order. +{% multicode %} + +```js +// Web SDK code example for sorting based on multiple fields +// ... + +// List documents and sort based on multiple fields +documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + Query.orderAsc('title'), // Order first by title in ascending order + Query.orderDesc('year'), // Then, order by year in descending order + ] +}); +``` +```dart +// Flutter SDK code example for sorting based on multiple fields +// ... + +// List documents and sort based on multiple fields +try { + final documents = await documentsDB.listDocuments( + databaseId: '', + collectionId: '', + queries: [ + Query.orderAsc('title'), // Order by title in ascending order + Query.orderDesc('year') // Order by year in descending order + ] + ); +} on AppwriteException catch(e) { + print(e); +} +``` +```kotlin +// Android SDK code example for sorting based on multiple fields +// ... + +// List documents and sort based on multiple fields +try { + val documents = documentsDB.listDocuments( + databaseId = "", + collectionId = "", + queries = [ + Query.orderAsc("title"), // Order by title in ascending order + Query.orderDesc("year") // Order by year in descending order + ] + ); +} catch (e: AppwriteException) { + Log.e("Appwrite", e.message); +} +``` +```swift +// Apple SDK code example for sorting based on multiple fields +// ... + +// List documents and sort based on multiple fields +do { + let documents = try await documentsDB.listDocuments( + databaseId: "", + collectionId: "", + queries: [ + Query.orderAsc("title"), // Order by title in ascending order + Query.orderDesc("year") // Order by year in descending order + ] + ); +} catch { + print(error.localizedDescription); +} +``` +```graphql +query { + documentsDBListDocuments( + databaseId: "", + collectionId: "", + queries: ["orderAsc(\"title\")", "orderDesc(\"year\")"] + ) { + total + documents { + _id + data + } + } +} +``` +{% /multicode %} + +# Ordering by sequence {% #sequence-ordering %} + +For ordering based on insertion order, you can use the `$sequence` field, which Appwrite automatically adds to all documents. Sorting by `$sequence` returns documents in the order they were created. + +{% multicode %} +```client-web +import { Client, Query, DocumentsDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const documentsDB = new DocumentsDB(client); + +documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + Query.orderAsc('$sequence'), + ] +}); +``` + +```client-flutter +import 'package:appwrite/appwrite.dart'; + +void main() async { + final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + + final documentsDB = DocumentsDB(client); + + try { + final documents = await documentsDB.listDocuments( + databaseId: '', + collectionId: '', + queries: [ + Query.orderAsc('\$sequence') + ] + ); + } on AppwriteException catch(e) { + print(e); + } +} +``` + +```client-apple +import Appwrite +import AppwriteModels + +func main() async throws { + let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + let documentsDB = DocumentsDB(client) + + do { + let documents = try await documentsDB.listDocuments( + databaseId: "", + collectionId: "", + queries: [ + Query.orderAsc("$sequence") + ] + ) + } catch { + print(error.localizedDescription) + } +} +``` + +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.DocumentsDB + +suspend fun main() { + val client = Client(applicationContext) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + val documentsDB = DocumentsDB(client) + + try { + val documents = documentsDB.listDocuments( + databaseId = "", + collectionId = "", + queries = listOf( + Query.orderAsc("\$sequence") + ) + ) + } catch (e: AppwriteException) { + Log.e("Appwrite", e.message) + } +} +``` + +```graphql +query { + documentsDBListDocuments( + databaseId: "", + collectionId: "" + queries: ["orderAsc(\"$sequence\")"] + ) { + total + documents { + _id + data + } + } +} +``` +{% /multicode %} + +The `$sequence` field is useful when you need: +- Consistent ordering for pagination, especially with high-frequency inserts +- Reliable insertion order tracking when timestamps might not be precise enough +- Simple insertion-order sorting without managing custom counter fields diff --git a/src/routes/docs/products/databases/documentsdb/pagination/+page.markdoc b/src/routes/docs/products/databases/documentsdb/pagination/+page.markdoc new file mode 100644 index 00000000000..918642e682f --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/pagination/+page.markdoc @@ -0,0 +1,540 @@ +--- +layout: article +title: Pagination +description: Implement pagination for large data sets in Appwrite DocumentsDB. Explore techniques for splitting and displaying data across multiple pages. +--- + +As your collection grows in size, you'll need to paginate the documents returned. +Pagination improves performance by returning a subset of documents that match a query at a time, called a page. + +By default, list operations return 25 documents per page, which can be changed using the `Query.limit()` query method. +There is no hard limit on the number of documents you can request. However, beware that **large pages can degrade performance**. + +# Offset pagination {% #offset-pagination %} + +Offset pagination divides documents into pages of `N` documents each. +To read page number `P`, skip `offset = N * (P - 1)` documents, then read the next `N`. + +Using `Query.limit()` and `Query.offset()` you can achieve offset pagination. +With `Query.limit()` you define how many documents can be returned from one request. +The `Query.offset()` is the number of documents you wish to skip before selecting documents. + +{% multicode %} +```client-web +import { Client, Query, DocumentsDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const documentsDB = new DocumentsDB(client); + +// Page 1 +const page1 = await documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + Query.limit(25), + Query.offset(0) + ] +}); + +// Page 2 +const page2 = await documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + Query.limit(25), + Query.offset(25) + ] +}); +``` +```client-flutter +import 'package:appwrite/appwrite.dart'; + +void main() async { + final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + + final documentsDB = DocumentsDB(client); + + final page1 = await documentsDB.listDocuments( + databaseId: '', + collectionId: '', + queries: [ + Query.limit(25), + Query.offset(0) + ] + ); + + final page2 = await documentsDB.listDocuments( + databaseId: '', + collectionId: '', + queries: [ + Query.limit(25), + Query.offset(25) + ] + ); +} +``` +```client-apple +import Appwrite +import AppwriteModels + +func main() async throws { + let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + let documentsDB = DocumentsDB(client) + + let page1 = try await documentsDB.listDocuments( + databaseId: "", + collectionId: "", + queries: [ + Query.limit(25), + Query.offset(0) + ] + ) + + let page2 = try await documentsDB.listDocuments( + databaseId: "", + collectionId: "", + queries: [ + Query.limit(25), + Query.offset(25) + ] + ) +} +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.DocumentsDB + +suspend fun main() { + val client = Client(applicationContext) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + val documentsDB = DocumentsDB(client) + + val page1 = documentsDB.listDocuments( + databaseId = "", + collectionId = "", + queries = listOf( + Query.limit(25), + Query.offset(0) + ) + ) + + val page2 = documentsDB.listDocuments( + databaseId = "", + collectionId = "", + queries = listOf( + Query.limit(25), + Query.offset(25) + ) + ) +} +``` + +{% /multicode %} + +{% info title="Drawbacks" %} +While traditional offset pagination is familiar, it comes with some drawbacks. +The request gets slower as the offset increases because the database has to skip over all the preceding documents before it can start selecting data. +If the data changes frequently, offset pagination will also produce **missing and duplicate** results. +{% /info %} + +# Cursor pagination {% #cursor-pagination %} + +The cursor is a unique identifier for a document that points to where the next page should start. +After reading a page of documents, pass the last document's ID into the `Query.cursorAfter(lastId)` query method to get the next page of documents. +Pass the first document's ID into the `Query.cursorBefore(firstId)` query method to retrieve the previous page. + +{% multicode %} + +```client-web +import { Client, Query, DocumentsDB } from "appwrite"; + +const client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject(""); + +const documentsDB = new DocumentsDB(client); + +// Page 1 +const page1 = await documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + Query.limit(25) + ] +}); + +const lastId = page1.documents[page1.documents.length - 1].$id; + +// Page 2 +const page2 = await documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + Query.limit(25), + Query.cursorAfter(lastId) + ] +}); +``` + +```client-flutter +import 'package:appwrite/appwrite.dart'; + +void main() async { + final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + + final documentsDB = DocumentsDB(client); + + final page1 = await documentsDB.listDocuments( + databaseId: '', + collectionId: '', + queries: [ + Query.limit(25) + ] + ); + + final lastId = page1.documents[page1.documents.length - 1].$id; + + final page2 = await documentsDB.listDocuments( + databaseId: '', + collectionId: '', + queries: [ + Query.limit(25), + Query.cursorAfter(lastId) + ] + ); +} +``` +```client-apple +import Appwrite +import AppwriteModels + +func main() async throws { + let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + let documentsDB = DocumentsDB(client) + + let page1 = try await documentsDB.listDocuments( + databaseId: "", + collectionId: "", + queries: [ + Query.limit(25) + ] + ) + + let lastId = page1.documents[page1.documents.count - 1].$id + + let page2 = try await documentsDB.listDocuments( + databaseId: "", + collectionId: "", + queries: [ + Query.limit(25), + Query.cursorAfter(lastId) + ] + ) +} +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.DocumentsDB + +suspend fun main() { + val client = Client(applicationContext) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + val documentsDB = DocumentsDB(client) + + val page1 = documentsDB.listDocuments( + databaseId = "", + collectionId = "", + queries = listOf( + Query.limit(25) + ) + ) + + val lastId = page1.documents[page1.documents.size - 1].$id + + val page2 = documentsDB.listDocuments( + databaseId = "", + collectionId = "", + queries = listOf( + Query.limit(25), + Query.cursorAfter(lastId) + ) + ) +} +``` + +{% /multicode %} + +# When to use what? {% #when-to-use %} +Offset pagination should be used for collections that rarely change. +Offset pagination lets you build an indicator of the current page number and the total page count. +For example, a list with up to 20 pages or static data like a list of countries or currencies. +Using offset pagination on large and frequently updated collections may result in slow performance and **missing and duplicate** results. + +Cursor pagination should be used for frequently updated collections. +It is best suited for lazy-loaded pages with infinite scrolling. +For example, a feed, comment section, chat history, or high volume datasets. + +# Cache list responses {% #cache-list-responses %} + +You can cache list responses by passing a `ttl` (time-to-live) value in seconds. Subsequent identical requests return the cached result until the TTL expires. The cache is permission-aware, so users with different roles never see each other's cached data. + +Set `ttl` between `1` and `86400` (24 hours). The default is `0` (caching disabled). The response includes an `X-Appwrite-Cache` header with value `hit` or `miss`. + +{% multicode %} +```client-web +import { Client, Query, DocumentsDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const documentsDB = new DocumentsDB(client); + +const page = await documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + Query.limit(25) + ], + ttl: 60 // Cache for 60 seconds +}); +``` +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const documentsDB = new sdk.DocumentsDB(client); + +const page = await documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + sdk.Query.limit(25) + ], + ttl: 60 // Cache for 60 seconds +}); +``` +```server-python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +documents_db = DocumentsDB(client) + +page = documents_db.list_documents( + database_id='', + collection_id='', + queries=[ + Query.limit(25) + ], + ttl=60 # Cache for 60 seconds +) +``` +```server-ruby +require 'appwrite' + +client = Appwrite::Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +documents_db = Appwrite::DocumentsDB.new(client) + +page = documents_db.list_documents( + database_id: '', + collection_id: '', + queries: [ + Appwrite::Query.limit(25) + ], + ttl: 60 # Cache for 60 seconds +) +``` +```server-deno +import { Client, Query, DocumentsDB } from "https://deno.land/x/appwrite/mod.ts"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const documentsDB = new DocumentsDB(client); + +const page = await documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + Query.limit(25) + ], + ttl: 60 // Cache for 60 seconds +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$documentsDB = new DocumentsDB($client); + +$page = $documentsDB->listDocuments( + databaseId: '', + collectionId: '', + queries: [ + Query::limit(25) + ], + ttl: 60 // Cache for 60 seconds +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +void main() async { + final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + + final documentsDB = DocumentsDB(client); + + final page = await documentsDB.listDocuments( + databaseId: '', + collectionId: '', + queries: [ + Query.limit(25) + ], + ttl: 60 // Cache for 60 seconds + ); +} +``` +```server-swift +import Appwrite +import AppwriteModels + +func main() async throws { + let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + + let documentsDB = DocumentsDB(client) + + let page = try await documentsDB.listDocuments( + databaseId: "", + collectionId: "", + queries: [ + Query.limit(25) + ], + ttl: 60 // Cache for 60 seconds + ) +} +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.DocumentsDB + +suspend fun main() { + val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + + val documentsDB = DocumentsDB(client) + + val page = documentsDB.listDocuments( + databaseId = "", + collectionId = "", + queries = listOf( + Query.limit(25) + ), + ttl = 60 // Cache for 60 seconds + ) +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use appwrite::query::Query; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let documents_db = DocumentsDB::new(&client); + + let page = documents_db.list_documents( + "", + "", + Some(vec![ + Query::limit(25).to_string(), + ]), + None, // transaction_id + None, // total + Some(60), // ttl - Cache for 60 seconds + ).await?; + + println!("{:?}", page); + Ok(()) +} +``` +```graphql +query { + documentsDBListDocuments( + databaseId: "", + collectionId: "", + queries: ["limit(25)"], + ttl: 60 + ) { + total + documents { + _id + data + } + } +} +``` +```http +GET /v1/documentsdb//collections//documents?ttl=60 HTTP/1.1 +Content-Type: application/json +X-Appwrite-Project: +``` +{% /multicode %} + +Document writes do **not** invalidate the cache, so cached responses may contain stale data until the TTL expires. Use a short TTL for collections that change often, or skip caching entirely when you always need the latest documents. diff --git a/src/routes/docs/products/databases/documentsdb/permissions/+page.markdoc b/src/routes/docs/products/databases/documentsdb/permissions/+page.markdoc new file mode 100644 index 00000000000..5ccd0905b35 --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/permissions/+page.markdoc @@ -0,0 +1,35 @@ +--- +layout: article +title: Database permissions +description: Control access to your DocumentsDB data with permissions. Learn how to set collection level and document level access rules. +--- + +Permissions define who can access documents in a collection. By default **no permissions** are granted to any users, so no user can access any documents. +Permissions exist at two levels, collection level and document level permissions. + +In Appwrite, permissions are **granted**, meaning a user has no access by default and receives access when granted. +A user with access granted at either collection level or document level will be able to access a document. +Users **don't need access at both levels** to access documents. + +# Collection level {% #collection-level %} +Collection level permissions apply to every document in the collection. +If a user has read, create, update, or delete permissions at the collection level, the user can access **all documents** inside the collection. + +Configure collection level permissions by navigating to **Your collection** > **Security** > **Permissions**. + +[Learn more about permissions and roles](/docs/advanced/platform/permissions) + +# Document level {% #document-level %} +Document level permissions grant access to individual documents. +If a user has read, create, update, or delete permissions at the document level, the user can access the **individual document**. + +Document level permissions are only applied if row level security is enabled in the security settings of your collection. +Enable document level permissions by navigating to **Your collection** > **Security** > **Row level security (RLS)**. + +Document level permissions are configured on individual documents. + +[Learn more about permissions and roles](/docs/advanced/platform/permissions) + +# Common use cases {% #common-use-cases %} + +For examples of how to implement common permission patterns, including creating private documents that are only accessible to their creators, see the [permissions examples](/docs/advanced/platform/permissions#examples) in our platform documentation. diff --git a/src/routes/docs/products/databases/documentsdb/queries/+page.markdoc b/src/routes/docs/products/databases/documentsdb/queries/+page.markdoc new file mode 100644 index 00000000000..34d9f057694 --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/queries/+page.markdoc @@ -0,0 +1,1855 @@ +--- +layout: article +title: Queries +description: Harness the power of querying with Appwrite DocumentsDB. Discover various query options, filtering, sorting, and advanced querying techniques. +--- + +Many list endpoints in Appwrite allow you to filter, sort, and paginate results using queries. Appwrite provides a common set of syntax to build queries. + +# Query class {% #query-class %} + +Appwrite SDKs provide a `Query` class to help you build queries. The `Query` class has methods for each type of supported query operation. + +# Building queries {% #building-queries %} + +Queries are passed to an endpoint through the `queries` parameter as an array of query strings, which can be generated using the `Query` class. + +Each query method is logically separated via `AND` operations. For `OR` operation, pass multiple values into the query method separated by commas. +For example `Query.equal('title', ['Avatar', 'Lord of the Rings'])` will fetch the movies `Avatar` or `Lord of the Rings`. + +{% info title="Default pagination behavior" %} +By default, results are limited to the **first 25 items**. +You can change this through [pagination](/docs/products/databases/documentsdb/pagination). +{% /info %} + +{% multicode %} + +```client-web +import { Client, Query, DocumentsDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const documentsDB = new DocumentsDB(client); + +documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + Query.equal('title', ['Avatar', 'Lord of the Rings']), + Query.greaterThan('year', 1999) + ] +}); +``` +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client(); + +const documentsDB = new sdk.DocumentsDB(client); + +client + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey('') +; + +const promise = documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + sdk.Query.equal('title', ['Avatar', 'Lord of the Rings']), + sdk.Query.greaterThan('year', 1999) + ] +}); + +promise.then(function (response) { + console.log(response); +}, function (error) { + console.log(error); +}); +``` +```client-flutter +import 'package:appwrite/appwrite.dart'; + +void main() async { + final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + + final documentsDB = DocumentsDB(client); + + try { + final documents = await documentsDB.listDocuments( + '', + '', + [ + Query.equal('title', ['Avatar', 'Lord of the Rings']), + Query.greaterThan('year', 1999) + ] + ); + } on AppwriteException catch(e) { + print(e); + } +} +``` +```client-apple +import Appwrite +import AppwriteModels + +func main() async throws { + let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + let documentsDB = DocumentsDB(client) + + do { + let documents = try await documentsDB.listDocuments( + databaseId: "", + collectionId: "", + queries: [ + Query.equal("title", value: ["Avatar", "Lord of the Rings"]), + Query.greaterThan("year", value: 1999) + ] + ) + } catch { + print(error.localizedDescription) + } +} +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.DocumentsDB + +suspend fun main() { + val client = Client(applicationContext) + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + + val documentsDB = DocumentsDB(client) + + try { + val documents = documentsDB.listDocuments( + databaseId = "", + collectionId = "", + queries = listOf( + Query.equal("title", listOf("Avatar", "Lord of the Rings")), + Query.greaterThan("year", 1999) + ) + ) + } catch (e: AppwriteException) { + Log.e("Appwrite", e.message) + } +} +``` +```server-go +package main + +import ( + "fmt" + "log" + + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/query" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + documentsDB := appwrite.NewDocumentsDB(client) + + documents, err := documentsDB.ListDocuments( + "", + "", + documentsDB.WithListDocumentsQueries([]string{ + query.Equal("title", []string{"Avatar", "Lord of the Rings"}), + query.GreaterThan("year", 1999), + }), + ) + if err != nil { + log.Fatal(err) + } + + fmt.Printf("Documents: %+v\n", documents) +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use appwrite::query::Query; +use serde_json::Value; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let documents_db = DocumentsDB::new(&client); + + let documents = documents_db.list_documents( + "", + "", + Some(vec![ + Query::equal("title", Value::Array(vec![ + Value::String("Avatar".to_string()), + Value::String("Lord of the Rings".to_string()), + ])).to_string(), + Query::greater_than("year", 1999).to_string(), + ]), + None, + None, + None, + ).await?; + + println!("{:?}", documents); + Ok(()) +} +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey('') +; + +$documentsDB = new DocumentsDB($client); + +$result = $documentsDB->listDocuments( + '', + '', + [ + Query::equal('title', ['Avatar', 'Lord of the Rings']), + Query::greaterThan('year', 1999) + ] +); +``` +```server-python +from appwrite.client import Client +from appwrite.query import Query +from appwrite.services.documents_db import DocumentsDB + +client = Client() + +(client + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') +) + +documentsDB = DocumentsDB(client) + +result = documentsDB.list_documents( + '', + '', + [ + Query.equal('title', ['Avatar', 'Lord of the Rings']), + Query.greater_than('year', 1999) + ] +) +``` +```graphql +query { + documentsDBListDocuments( + databaseId: "", + collectionId: "" + queries: [ + "{\"method\":\"equal\",\"attribute\":\"title\",\"values\":[\"Avatar\",\"Lord of the Rings\"]}", + "{\"method\":\"greaterThan\",\"attribute\":\"year\",\"values\":[1999]}" + ] + ) { + total + documents { + _id + data + } + } +} +``` +```http +GET /v1/documentsdb//collections//documents?queries[]=%7B%22method%22%3A%22equal%22%2C%22attribute%22%3A%22title%22%2C%22values%22%3A%5B%22Avatar%22%2C%22Lord%20of%20the%20Rings%22%5D%7D&queries[]=%7B%22method%22%3A%22greaterThan%22%2C%22attribute%22%3A%22year%22%2C%22values%22%3A%5B1999%5D%7D HTTP/1.1 +Content-Type: application/json +X-Appwrite-Project: +``` +```bash +appwrite documents-db list-documents \ + --database-id \ + --collection-id \ + --queries '{"method":"equal","attribute":"title","values":["Avatar","Lord of the Rings"]}' '{"method":"greaterThan","attribute":"year","values":[1999]}' +``` +{% /multicode %} + +# Query operators {% #query-operators %} + +## Select {% #select %} + +The `select` operator allows you to specify which fields should be returned from a document. This optimizes response size and retrieves only the data you need. + +{% multicode %} +```client-web +Query.select(["name", "title"]) +``` +```client-flutter +Query.select(["name", "title"]) +``` +```server-python +Query.select(["name", "title"]) +``` +```server-ruby +Query.select(["name", "title"]) +``` +```server-deno +Query.select(["name", "title"]) +``` +```server-php +Query::select(["name", "title"]) +``` +```client-apple +Query.select(["name", "title"]) +``` +```server-go +query.Select([]string{"name", "title"}) +``` +```server-rust +Query::select(vec!["name", "title"]).to_string() +``` +```http +{"method":"select","values":["name","title"]} +``` +{% /multicode %} + +### Use selection patterns {% #select-patterns %} + +| Pattern | Description | Use case | +|---------|-------------|----------| +| `["field1", "field2"]` | Specific fields only | Minimize response size | +| `["*"]` | All document fields | Get complete document data | + +### Optimize performance {% #select-performance %} + +**Optimize response size** - Only select the fields you actually need. Smaller responses are faster to transfer and parse. + +**Reduce database load** - Selecting fewer fields reduces database processing time, especially for large documents. + +## Comparison operators {% #comparison %} + +### Equal {% #equal %} + +Returns document if field is equal to any value in the provided array. + +{% multicode %} +```client-web +Query.equal("title", ["Iron Man"]) +``` +```client-flutter +Query.equal("title", ["Iron Man"]) +``` +```server-python +Query.equal("title", ["Iron Man"]) +``` +```server-ruby +Query.equal("title", ["Iron Man"]) +``` +```server-deno +Query.equal("title", ["Iron Man"]) +``` +```server-php +Query::equal("title", ["Iron Man"]) +``` +```client-apple +Query.equal("title", value: ["Iron Man"]) +``` +```server-go +query.Equal("title", []string{"Iron Man"}) +``` +```server-rust +Query::equal("title", Value::Array(vec![Value::String("Iron Man".to_string())])).to_string() +``` +```http +{"method":"equal","attribute":"title","values":["Iron Man"]} +``` +{% /multicode %} + +### Not equal {% #not-equal %} + +Returns document if field is not equal to any value in the provided array. + +{% multicode %} +```client-web +Query.notEqual("title", "Iron Man") +``` +```client-flutter +Query.notEqual("title", "Iron Man") +``` +```server-python +Query.not_equal("title", "Iron Man") +``` +```server-ruby +Query.not_equal("title", "Iron Man") +``` +```server-deno +Query.notEqual("title", "Iron Man") +``` +```server-php +Query::notEqual("title", "Iron Man") +``` +```client-apple +Query.notEqual("title", value: "Iron Man") +``` +```server-go +query.NotEqual("title", "Iron Man") +``` +```server-rust +Query::not_equal("title", "Iron Man").to_string() +``` +```http +{"method":"notEqual","attribute":"title","values":"Iron Man"} +``` +{% /multicode %} + +### Less than {% #less-than %} + +Returns document if field is less than the provided value. + +{% multicode %} +```client-web +Query.lessThan("score", 10) +``` +```client-flutter +Query.lessThan("score", 10) +``` +```server-python +Query.less_than("score", 10) +``` +```server-ruby +Query.less_than("score", 10) +``` +```server-deno +Query.lessThan("score", 10) +``` +```server-php +Query::lessThan("score", 10) +``` +```client-apple +Query.lessThan("score", value: 10) +``` +```server-go +query.LessThan("score", 10) +``` +```server-rust +Query::less_than("score", 10).to_string() +``` +```http +{"method":"lessThan","attribute":"score","values":[10]} +``` +{% /multicode %} + +### Less than or equal {% #less-than-equal %} + +Returns document if field is less than or equal to the provided value. + +{% multicode %} +```client-web +Query.lessThanEqual("score", 10) +``` +```client-flutter +Query.lessThanEqual("score", 10) +``` +```server-python +Query.less_than_equal("score", 10) +``` +```server-ruby +Query.less_than_equal("score", 10) +``` +```server-deno +Query.lessThanEqual("score", 10) +``` +```server-php +Query::lessThanEqual("score", 10) +``` +```client-apple +Query.lessThanEqual("score", value: 10) +``` +```server-go +query.LessThanEqual("score", 10) +``` +```server-rust +Query::less_than_equal("score", 10).to_string() +``` +```http +{"method":"lessThanEqual","attribute":"score","values":[10]} +``` +{% /multicode %} + +### Greater than {% #greater-than %} + +Returns document if field is greater than the provided value. + +{% multicode %} +```client-web +Query.greaterThan("score", 10) +``` +```client-flutter +Query.greaterThan("score", 10) +``` +```server-python +Query.greater_than("score", 10) +``` +```server-ruby +Query.greater_than("score", 10) +``` +```server-deno +Query.greaterThan("score", 10) +``` +```server-php +Query::greaterThan("score", 10) +``` +```client-apple +Query.greaterThan("score", value: 10) +``` +```server-go +query.GreaterThan("score", 10) +``` +```server-rust +Query::greater_than("score", 10).to_string() +``` +```http +{"method":"greaterThan","attribute":"score","values":[10]} +``` +{% /multicode %} + +### Greater than or equal {% #greater-than-equal %} + +Returns document if field is greater than or equal to the provided value. + +{% multicode %} +```client-web +Query.greaterThanEqual("score", 10) +``` +```client-flutter +Query.greaterThanEqual("score", 10) +``` +```server-python +Query.greater_than_equal("score", 10) +``` +```server-ruby +Query.greater_than_equal("score", 10) +``` +```server-deno +Query.greaterThanEqual("score", 10) +``` +```server-php +Query::greaterThanEqual("score", 10) +``` +```client-apple +Query.greaterThanEqual("score", value: 10) +``` +```server-go +query.GreaterThanEqual("score", 10) +``` +```server-rust +Query::greater_than_equal("score", 10).to_string() +``` +```http +{"method":"greaterThanEqual","attribute":"score","values":[10]} +``` +{% /multicode %} + +### Between {% #between %} + +Returns document if field value falls between the two values. The boundary values are inclusive and can be strings or numbers. + +{% multicode %} +```client-web +Query.between("price", 5, 10) +``` +```client-flutter +Query.between("price", 5, 10) +``` +```server-python +Query.between("price", 5, 10) +``` +```server-ruby +Query.between("price", 5, 10) +``` +```server-deno +Query.between("price", 5, 10) +``` +```server-php +Query::between("price", 5, 10) +``` +```client-apple +Query.between("price", start: 5, end: 10) +``` +```server-go +query.Between("price", 5, 10) +``` +```server-rust +Query::between("price", 5, 10).to_string() +``` +```http +{"method":"between","attribute":"price","values":[5,10]} +``` +{% /multicode %} + +### Not between {% #not-between %} + +Returns documents if the field value is outside the range defined by the two values (strictly less than start OR strictly greater than end). +Works with strings or numbers. Boundary values are excluded. + +{% multicode %} +```client-web +Query.notBetween("price", 5, 10) +``` +```client-flutter +Query.notBetween("price", 5, 10) +``` +```client-apple +Query.notBetween("price", start: 5, end: 10) +``` +```client-android-kotlin +Query.notBetween("price", 5, 10) +``` +```client-android-java +Query.notBetween("price", 5, 10) +``` +```server-python +Query.not_between("price", 5, 10) +``` +```server-ruby +Query.not_between("price", 5, 10) +``` +```server-deno +Query.notBetween("price", 5, 10) +``` +```server-nodejs +Query.notBetween("price", 5, 10) +``` +```server-php +Query::notBetween("price", 5, 10) +``` +```server-swift +Query.notBetween("price", start: 5, end: 10) +``` +```server-rust +Query::not_between("price", 5, 10).to_string() +``` +```http +{"method":"notBetween","attribute":"price","values":[5,10]} +``` +{% /multicode %} + +## Null checks {% #null-checks %} + +### Is null {% #is-null %} + +Returns documents where field value is null. + +{% multicode %} +```client-web +Query.isNull("name") +``` +```client-flutter +Query.isNull("name") +``` +```server-python +Query.is_null("name") +``` +```server-ruby +Query.is_null("name") +``` +```server-deno +Query.isNull("name") +``` +```server-php +Query::isNull("name") +``` +```client-apple +Query.isNull("name") +``` +```server-go +query.IsNull("name") +``` +```server-rust +Query::is_null("name").to_string() +``` +```http +{"method":"isNull","attribute":"name"} +``` +{% /multicode %} + +### Is not null {% #is-not-null %} + +Returns documents where field value is **not** null. + +{% multicode %} +```client-web +Query.isNotNull("name") +``` +```client-flutter +Query.isNotNull("name") +``` +```server-python +Query.is_not_null("name") +``` +```server-ruby +Query.is_not_null("name") +``` +```server-deno +Query.isNotNull("name") +``` +```server-php +Query::isNotNull("name") +``` +```client-apple +Query.isNotNull("name") +``` +```server-go +query.IsNotNull("name") +``` +```server-rust +Query::is_not_null("name").to_string() +``` +```http +{"method":"isNotNull","attribute":"name"} +``` +{% /multicode %} + +## String operations {% #string-operations %} + +### Starts with {% #starts-with %} + +Returns documents if a string field starts with a substring. + +{% multicode %} +```client-web +Query.startsWith("name", "Once upon a time") +``` +```client-flutter +Query.startsWith("name", "Once upon a time") +``` +```server-python +Query.starts_with("name", "Once upon a time") +``` +```server-ruby +Query.starts_with("name", "Once upon a time") +``` +```server-deno +Query.startsWith("name", "Once upon a time") +``` +```server-php +Query::startsWith("name", "Once upon a time") +``` +```client-apple +Query.startsWith("name", value: "Once upon a time") +``` +```server-go +query.StartsWith("name", "Once upon a time") +``` +```server-rust +Query::starts_with("name", "Once upon a time").to_string() +``` +```http +{"method":"startsWith","attribute":"name","values":["Once upon a time"]} +``` +{% /multicode %} + +### Not starts with {% #not-starts-with %} + +Returns documents if a string field does not start with a substring. + +{% multicode %} +```client-web +Query.notStartsWith("name", "Once upon a time") +``` +```client-flutter +Query.notStartsWith("name", "Once upon a time") +``` +```client-apple +Query.notStartsWith("name", value: "Once upon a time") +``` +```client-android-kotlin +Query.notStartsWith("name", "Once upon a time") +``` +```client-android-java +Query.notStartsWith("name", "Once upon a time") +``` +```server-python +Query.not_starts_with("name", "Once upon a time") +``` +```server-ruby +Query.not_starts_with("name", "Once upon a time") +``` +```server-deno +Query.notStartsWith("name", "Once upon a time") +``` +```server-nodejs +Query.notStartsWith("name", "Once upon a time") +``` +```server-php +Query::notStartsWith("name", "Once upon a time") +``` +```server-swift +Query.notStartsWith("name", value: "Once upon a time") +``` +```server-rust +Query::not_starts_with("name", "Once upon a time").to_string() +``` +```http +{"method":"notStartsWith","attribute":"name","values":["Once upon a time"]} +``` +{% /multicode %} + +### Ends with {% #ends-with %} + +Returns documents if a string field ends with a substring. + +{% multicode %} +```client-web +Query.endsWith("name", "happily ever after.") +``` +```client-flutter +Query.endsWith("name", "happily ever after.") +``` +```server-python +Query.ends_with("name", "happily ever after.") +``` +```server-ruby +Query.ends_with("name", "happily ever after.") +``` +```server-deno +Query.endsWith("name", "happily ever after.") +``` +```server-php +Query::endsWith("name", "happily ever after.") +``` +```client-apple +Query.endsWith("name", value: "happily ever after.") +``` +```server-go +query.EndsWith("name", "happily ever after.") +``` +```server-rust +Query::ends_with("name", "happily ever after.").to_string() +``` +```http +{"method":"endsWith","attribute":"name","values":["happily ever after."]} +``` +{% /multicode %} + +### Not ends with {% #not-ends-with %} + +Returns documents if a string field does not end with a substring. + +{% multicode %} +```client-web +Query.notEndsWith("name", "happily ever after.") +``` +```client-flutter +Query.notEndsWith("name", "happily ever after.") +``` +```client-apple +Query.notEndsWith("name", value: "happily ever after.") +``` +```client-android-kotlin +Query.notEndsWith("name", "happily ever after.") +``` +```client-android-java +Query.notEndsWith("name", "happily ever after.") +``` +```server-python +Query.not_ends_with("name", "happily ever after.") +``` +```server-ruby +Query.not_ends_with("name", "happily ever after.") +``` +```server-deno +Query.notEndsWith("name", "happily ever after.") +``` +```server-nodejs +Query.notEndsWith("name", "happily ever after.") +``` +```server-php +Query::notEndsWith("name", "happily ever after.") +``` +```server-swift +Query.notEndsWith("name", value: "happily ever after.") +``` +```server-rust +Query::not_ends_with("name", "happily ever after.").to_string() +``` +```http +{"method":"notEndsWith","attribute":"name","values":["happily ever after."]} +``` +{% /multicode %} + +### Contains {% #contains %} + +Returns documents if the array field contains the specified elements or if a string field contains the specified substring. + +{% multicode %} +```client-web +// For arrays +Query.contains("ingredients", ['apple', 'banana']) + +// For strings +Query.contains("name", "Tom") +``` +```client-flutter +// For arrays +Query.contains("ingredients", ['apple', 'banana']) + +// For strings +Query.contains("name", "Tom") +``` +```server-python +# For arrays +Query.contains("ingredients", ['apple', 'banana']) + +# For strings +Query.contains("name", "Tom") +``` +```server-ruby +# For arrays +Query.contains("ingredients", ['apple', 'banana']) + +# For strings +Query.contains("name", "Tom") +``` +```server-deno +// For arrays +Query.contains("ingredients", ['apple', 'banana']) + +// For strings +Query.contains("name", "Tom") +``` +```server-php +// For arrays +Query::contains("ingredients", ['apple', 'banana']) + +// For strings +Query::contains("name", "Tom") +``` +```client-apple +// For arrays +Query.contains("ingredients", value: ["apple", "banana"]) + +// For strings +Query.contains("name", value: "Tom") +```server-go +// For arrays +query.Contains("ingredients", []string{"apple", "banana"}) + +// For strings +query.Contains("name", "Tom") +``` +```server-rust +// For arrays +Query::contains("ingredients", Value::Array(vec![ + Value::String("apple".to_string()), + Value::String("banana".to_string()), +])).to_string() + +// For strings +Query::contains("name", "Tom").to_string() +``` +```http +# For arrays +{"method":"contains","attribute":"ingredients","values":["apple","banana"]} + +# For strings +{"method":"contains","attribute":"name","values":["Tom"]} +``` +{% /multicode %} + +### Not contains {% #not-contains %} + +Returns documents if the array field does not contain the specified +elements, or if a string field does not contain the specified +substring. + +{% multicode %} +```client-web +// For arrays +Query.notContains("ingredients", ['apple', 'banana']) + +// For strings +Query.notContains("name", "Tom") +``` +```client-flutter +// For arrays +Query.notContains("ingredients", ['apple', 'banana']) + +// For strings +Query.notContains("name", "Tom") +``` +```client-react-native +// For arrays +Query.notContains("ingredients", ['apple', 'banana']) + +// For strings +Query.notContains("name", "Tom") +``` +```client-apple +// For arrays +Query.notContains("ingredients", value: ['apple', 'banana']) + +// For strings +Query.notContains("name", value: "Tom") +``` +```client-android-kotlin +// For arrays +Query.notContains("ingredients", ['apple', 'banana']) + +// For strings +Query.notContains("name", "Tom") +``` +```client-android-java +// For arrays +Query.notContains("ingredients", Arrays.asList("apple", "banana")) + +// For strings +Query.notContains("name", "Tom") +``` +```server-python +# For arrays +Query.not_contains("ingredients", ['apple', 'banana']) + +# For strings +Query.not_contains("name", "Tom") +``` +```server-ruby +# For arrays +Query.not_contains("ingredients", ['apple', 'banana']) + +# For strings +Query.not_contains("name", "Tom") +``` +```server-deno +// For arrays +Query.notContains("ingredients", ['apple', 'banana']) + +// For strings +Query.notContains("name", "Tom") +``` +```server-nodejs +// For arrays +Query.notContains("ingredients", ['apple', 'banana']) + +// For strings +Query.notContains("name", "Tom") +``` +```server-php +// For arrays +Query::notContains("ingredients", ['apple', 'banana']) + +// For strings +Query::notContains("name", "Tom") +``` +```server-dotnet +// For arrays +Query.NotContains("ingredients", new List { "apple", "banana" }) + +// For strings +Query.NotContains("name", "Tom") +``` +```server-go +// For arrays +query.NotContains("ingredients", []string{"apple", "banana"}) + +// For strings +query.NotContains("name", "Tom") +```server-dart +// For arrays +Query.notContains("ingredients", ['apple', 'banana']) + +// For strings +Query.notContains("name", "Tom") +``` +```server-swift +// For arrays +Query.notContains("ingredients", value: ['apple', 'banana']) + +// For strings +Query.notContains("name", value: "Tom") +``` +```server-kotlin +// For arrays +Query.notContains("ingredients", listOf("apple", "banana")) + +// For strings +Query.notContains("name", "Tom") +``` +```server-rust +// For arrays +Query::not_contains("ingredients", Value::Array(vec![ + Value::String("apple".to_string()), + Value::String("banana".to_string()), +])).to_string() + +// For strings +Query::not_contains("name", "Tom").to_string() +``` +```http +# For arrays +{"method":"notContains","attribute":"ingredients","values":["apple","banana"]} + +# For strings +{"method":"notContains","attribute":"name","values":["Tom"]} +``` +{% /multicode %} + +### Search {% #search %} + +Searches string fields for provided keywords. Requires a [full-text index](/docs/products/databases/documentsdb/collections#indexes) on queried fields. + +{% multicode %} +```client-web +Query.search("text", "key words") +``` +```client-flutter +Query.search("text", "key words") +``` +```server-python +Query.search("text", "key words") +``` +```server-ruby +Query.search("text", "key words") +``` +```server-deno +Query.search("text", "key words") +``` +```server-php +Query::search("text", "key words") +``` +```client-apple +Query.search("text", value: "key words") +``` +```server-go +query.Search("text", "key words") +``` +```server-rust +Query::search("text", "key words").to_string() +``` +```http +{"method":"search","attribute":"text","values":["key words"]} +``` +{% /multicode %} + +### Not search {% #not-search %} + +Returns documents if a string field does not match the full-text search +query. Requires a [full-text index](/docs/products/databases/documentsdb/collections#indexes) +on queried fields. + +{% multicode %} +```client-web +Query.notSearch("text", "key words") +``` +```client-flutter +Query.notSearch("text", "key words") +``` +```client-apple +Query.notSearch("text", value: "key words") +``` +```client-android-kotlin +Query.notSearch("text", "key words") +``` +```client-android-java +Query.notSearch("text", "key words") +``` +```server-python +Query.not_search("text", "key words") +``` +```server-ruby +Query.not_search("text", "key words") +``` +```server-deno +Query.notSearch("text", "key words") +``` +```server-nodejs +Query.notSearch("text", "key words") +``` +```server-php +Query::notSearch("text", "key words") +``` +```server-swift +Query.notSearch("text", value: "key words") +``` +```server-rust +Query::not_search("text", "key words").to_string() +``` +```http +{"method":"notSearch","attribute":"text","values":["key words"]} +``` +{% /multicode %} + +## Logical operators {% #logical-operators %} + +### AND {% #and %} + +Returns document if it matches all of the nested sub-queries in the array passed in. + +{% multicode %} +```client-web +Query.and([ + Query.lessThan("size", 10), + Query.greaterThan("size", 5) +]) +``` +```client-flutter +Query.and([ + Query.lessThan("size", 10), + Query.greaterThan("size", 5) +]) +``` +```server-python +Query.and_queries([ + Query.less_than("size", 10), + Query.greater_than("size", 5) +]) +``` +```server-ruby +Query.and([ + Query.less_than("size", 10), + Query.greater_than("size", 5) +]) +``` +```server-deno +Query.and([ + Query.lessThan("size", 10), + Query.greaterThan("size", 5) +]) +``` +```server-php +Query::and([ + Query::lessThan("size", 10), + Query::greaterThan("size", 5) +]) +``` +```client-apple +Query.and([ + Query.lessThan("size", value: 10), + Query.greaterThan("size", value: 5) +]) +``` +```server-go +query.And([]string{ + query.LessThan("size", 10), + query.GreaterThan("size", 5), +}) +``` +```server-rust +Query::and(vec![ + Query::less_than("size", 10).to_string(), + Query::greater_than("size", 5).to_string(), +]).to_string() +``` +```http +{"method":"and","values":[{"method":"lessThan","attribute":"size","values":[10]},{"method":"greaterThan","attribute":"size","values":[5]}]} +``` +{% /multicode %} + +### OR {% #or %} + +Returns document if it matches any of the nested sub-queries in the array passed in. + +{% multicode %} +```client-web +Query.or([ + Query.lessThan("size", 5), + Query.greaterThan("size", 10) +]) +``` +```client-flutter +Query.or([ + Query.lessThan("size", 5), + Query.greaterThan("size", 10) +]) +``` +```server-python +Query.or_queries([ + Query.less_than("size", 5), + Query.greater_than("size", 10) +]) +``` +```server-ruby +Query.or([ + Query.less_than("size", 5), + Query.greater_than("size", 10) +]) +``` +```server-deno +Query.or([ + Query.lessThan("size", 5), + Query.greaterThan("size", 10) +]) +``` +```server-php +Query::or([ + Query::lessThan("size", 5), + Query::greaterThan("size", 10) +]) +``` +```client-apple +Query.or([ + Query.lessThan("size", value: 5), + Query.greaterThan("size", value: 10) +]) +``` +```server-go +query.Or([]string{ + query.LessThan("size", 5), + query.GreaterThan("size", 10), +}) +``` +```server-rust +Query::or(vec![ + Query::less_than("size", 5).to_string(), + Query::greater_than("size", 10).to_string(), +]).to_string() +``` +```http +{"method":"or","values":[{"method":"lessThan","attribute":"size","values":[5]},{"method":"greaterThan","attribute":"size","values":[10]}]} +``` +{% /multicode %} + +## Ordering {% #ordering %} + +### Order descending {% #order-desc %} + +Orders results in descending order by field. + +{% multicode %} +```client-web +Query.orderDesc("field") +``` +```client-flutter +Query.orderDesc("field") +``` +```server-python +Query.order_desc("field") +``` +```server-ruby +Query.order_desc("field") +``` +```server-nodejs +Query.orderDesc("field") +``` +```server-php +Query::orderDesc("field") +``` +```client-apple +Query.orderDesc("field") +``` +```server-go +query.OrderDesc("attribute") +``` +```server-rust +Query::order_desc("field").to_string() +``` +```http +{"method":"orderDesc","attribute":"field"} +``` +{% /multicode %} + +### Order ascending {% #order-asc %} + +Orders results in ascending order by field. + +{% multicode %} +```client-web +Query.orderAsc("field") +``` +```client-flutter +Query.orderAsc("field") +``` +```server-python +Query.order_asc("field") +``` +```server-ruby +Query.order_asc("field") +``` +```server-nodejs +Query.orderAsc("field") +``` +```server-php +Query::orderAsc("field") +``` +```client-apple +Query.orderAsc("field") +``` +```server-go +query.OrderAsc("attribute") +``` +```server-rust +Query::order_asc("field").to_string() +``` +```http +{"method":"orderAsc","attribute":"field"} +``` +{% /multicode %} + +## Pagination {% #pagination %} + +### Limit {% #limit %} + +Limits the number of results returned by the query. Used for [pagination](/docs/products/databases/documentsdb/pagination). + +{% multicode %} +```client-web +Query.limit(25) +``` +```client-flutter +Query.limit(25) +``` +```server-python +Query.limit(25) +``` +```server-ruby +Query.limit(25) +``` +```server-deno +Query.limit(25) +``` +```server-php +Query::limit(25) +``` +```client-apple +Query.limit(25) +``` +```server-go +query.Limit(25) +``` +```server-rust +Query::limit(25).to_string() +``` +```http +{"method":"limit","values":[25]} +``` +{% /multicode %} + +### Offset {% #offset %} + +Offset the results returned by skipping some of the results. Used for [pagination](/docs/products/databases/documentsdb/pagination). + +{% multicode %} +```client-web +Query.offset(0) +``` +```client-flutter +Query.offset(0) +``` +```server-python +Query.offset(0) +``` +```server-ruby +Query.offset(0) +``` +```server-deno +Query.offset(0) +``` +```server-php +Query::offset(0) +``` +```client-apple +Query.offset(0) +``` +```server-go +query.Offset(0) +``` +```server-rust +Query::offset(0).to_string() +``` +```http +{"method":"offset","values":[0]} +``` +{% /multicode %} + +### Cursor after {% #cursor-after %} + +Places the cursor after the specified resource ID. Used for [pagination](/docs/products/databases/documentsdb/pagination). + +{% multicode %} +```client-web +Query.cursorAfter("62a7...f620") +``` +```client-flutter +Query.cursorAfter("62a7...f620") +``` +```server-python +Query.cursor_after("62a7...f620") +``` +```server-ruby +Query.cursor_after("62a7...f620") +``` +```server-deno +Query.cursorAfter("62a7...f620") +``` +```server-php +Query::cursorAfter("62a7...f620") +``` +```client-apple +Query.cursorAfter("62a7...f620") +``` +```server-go +query.CursorAfter("62a7...f620") +``` +```server-rust +Query::cursor_after("62a7...f620").to_string() +``` +```http +{"method":"cursorAfter","values":["62a7...f620"]} +``` +{% /multicode %} + +### Cursor before {% #cursor-before %} + +Places the cursor before the specified resource ID. Used for [pagination](/docs/products/databases/documentsdb/pagination). + +{% multicode %} +```client-web +Query.cursorBefore("62a7...a600") +``` +```client-flutter +Query.cursorBefore("62a7...a600") +``` +```server-python +Query.cursor_before("62a7...a600") +``` +```server-ruby +Query.cursor_before("62a7...a600") +``` +```server-deno +Query.cursorBefore("62a7...a600") +``` +```server-php +Query::cursorBefore("62a7...a600") +``` +```client-apple +Query.cursorBefore("62a7...a600") +``` +```server-go +query.CursorBefore("62a7...a600") +``` +```server-rust +Query::cursor_before("62a7...a600").to_string() +``` +```http +{"method":"cursorBefore","values":["62a7...a600"]} +``` +{% /multicode %} + +# Time helpers {% #time-helpers %} + +Built-in helpers for filtering by creation and update timestamps using +ISO 8601 date-time strings (for example, "2025-01-01T00:00:00Z"). + +### Created before {% #created-before %} + +Returns documents created before the given date. + +{% multicode %} +```client-web +Query.createdBefore("2025-01-01T00:00:00Z") +``` +```client-flutter +Query.createdBefore("2025-01-01T00:00:00Z") +``` +```client-apple +Query.createdBefore("2025-01-01T00:00:00Z") +``` +```client-android-kotlin +Query.createdBefore("2025-01-01T00:00:00Z") +``` +```client-android-java +Query.createdBefore("2025-01-01T00:00:00Z") +``` +```server-python +Query.created_before("2025-01-01T00:00:00Z") +``` +```server-ruby +Query.created_before("2025-01-01T00:00:00Z") +``` +```server-deno +Query.createdBefore("2025-01-01T00:00:00Z") +``` +```server-nodejs +Query.createdBefore("2025-01-01T00:00:00Z") +``` +```server-php +Query::createdBefore("2025-01-01T00:00:00Z") +``` +```server-swift +Query.createdBefore("2025-01-01T00:00:00Z") +``` +```server-rust +Query::created_before("2025-01-01T00:00:00Z").to_string() +``` +```http +{"method":"createdBefore","values":["2025-01-01T00:00:00Z"]} +``` +{% /multicode %} + +### Created after {% #created-after %} + +Returns documents created after the given date. + +{% multicode %} +```client-web +Query.createdAfter("2025-01-01T00:00:00Z") +``` +```client-flutter +Query.createdAfter("2025-01-01T00:00:00Z") +``` +```client-apple +Query.createdAfter("2025-01-01T00:00:00Z") +``` +```client-android-kotlin +Query.createdAfter("2025-01-01T00:00:00Z") +``` +```client-android-java +Query.createdAfter("2025-01-01T00:00:00Z") +``` +```server-python +Query.created_after("2025-01-01T00:00:00Z") +``` +```server-ruby +Query.created_after("2025-01-01T00:00:00Z") +``` +```server-deno +Query.createdAfter("2025-01-01T00:00:00Z") +``` +```server-nodejs +Query.createdAfter("2025-01-01T00:00:00Z") +``` +```server-php +Query::createdAfter("2025-01-01T00:00:00Z") +``` +```server-swift +Query.createdAfter("2025-01-01T00:00:00Z") +``` +```server-rust +Query::created_after("2025-01-01T00:00:00Z").to_string() +``` +```http +{"method":"createdAfter","values":["2025-01-01T00:00:00Z"]} +``` +{% /multicode %} + +### Updated before {% #updated-before %} + +Returns documents updated before the given date. + +{% multicode %} +```client-web +Query.updatedBefore("2025-01-01T00:00:00Z") +``` +```client-flutter +Query.updatedBefore("2025-01-01T00:00:00Z") +``` +```client-apple +Query.updatedBefore("2025-01-01T00:00:00Z") +``` +```client-android-kotlin +Query.updatedBefore("2025-01-01T00:00:00Z") +``` +```client-android-java +Query.updatedBefore("2025-01-01T00:00:00Z") +``` +```server-python +Query.updated_before("2025-01-01T00:00:00Z") +``` +```server-ruby +Query.updated_before("2025-01-01T00:00:00Z") +``` +```server-deno +Query.updatedBefore("2025-01-01T00:00:00Z") +``` +```server-nodejs +Query.updatedBefore("2025-01-01T00:00:00Z") +``` +```server-php +Query::updatedBefore("2025-01-01T00:00:00Z") +``` +```server-swift +Query.updatedBefore("2025-01-01T00:00:00Z") +``` +```server-rust +Query::updated_before("2025-01-01T00:00:00Z").to_string() +``` +```http +{"method":"updatedBefore","values":["2025-01-01T00:00:00Z"]} +``` +{% /multicode %} + +### Updated after {% #updated-after %} + +Returns documents updated after the given date. + +{% multicode %} +```client-web +Query.updatedAfter("2025-01-01T00:00:00Z") +``` +```client-flutter +Query.updatedAfter("2025-01-01T00:00:00Z") +``` +```client-apple +Query.updatedAfter("2025-01-01T00:00:00Z") +``` +```client-android-kotlin +Query.updatedAfter("2025-01-01T00:00:00Z") +``` +```client-android-java +Query.updatedAfter("2025-01-01T00:00:00Z") +``` +```server-python +Query.updated_after("2025-01-01T00:00:00Z") +``` +```server-ruby +Query.updated_after("2025-01-01T00:00:00Z") +``` +```server-deno +Query.updatedAfter("2025-01-01T00:00:00Z") +``` +```server-nodejs +Query.updatedAfter("2025-01-01T00:00:00Z") +``` +```server-php +Query::updatedAfter("2025-01-01T00:00:00Z") +``` +```server-swift +Query.updatedAfter("2025-01-01T00:00:00Z") +``` +```server-rust +Query::updated_after("2025-01-01T00:00:00Z").to_string() +``` +```http +{"method":"updatedAfter","values":["2025-01-01T00:00:00Z"]} +``` +{% /multicode %} +# Complex queries {% #complex-queries %} + +You can create complex queries by combining AND and OR operations. For example, to find items that are either books under $20 or magazines under $10: + +{% multicode %} +```client-web +const results = await documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + Query.or([ + Query.and([ + Query.equal('category', ['books']), + Query.lessThan('price', 20) + ]), + Query.and([ + Query.equal('category', ['magazines']), + Query.lessThan('price', 10) + ]) + ]) + ] +}); +``` +```client-flutter +final results = await documentsDB.listDocuments( + '', + '', + [ + Query.or([ + Query.and([ + Query.equal('category', ['books']), + Query.lessThan('price', 20) + ]), + Query.and([ + Query.equal('category', ['magazines']), + Query.lessThan('price', 10) + ]) + ]) + ] +); +``` +```server-python +results = documentsDB.list_documents( + database_id='', + collection_id='', + queries=[ + Query.or_queries([ + Query.and_queries([ + Query.equal('category', ['books']), + Query.less_than('price', 20) + ]), + Query.and_queries([ + Query.equal('category', ['magazines']), + Query.less_than('price', 10) + ]) + ]) + ] +) +``` +```server-go +documents, err := documentsDB.ListDocuments( + "", + "", + documentsDB.WithListDocumentsQueries([]string{ + query.Or([]string{ + query.And([]string{ + query.Equal("category", []string{"books"}), + query.LessThan("price", 20), + }), + query.And([]string{ + query.Equal("category", []string{"magazines"}), + query.LessThan("price", 10), + }), + }), + }), +) +if err != nil { + log.Fatal(err) +} +``` +```server-rust +let documents = documents_db.list_documents( + "", + "", + Some(vec![ + Query::or(vec![ + Query::and(vec![ + Query::equal("category", Value::Array(vec![Value::String("books".to_string())])).to_string(), + Query::less_than("price", 20).to_string(), + ]).to_string(), + Query::and(vec![ + Query::equal("category", Value::Array(vec![Value::String("magazines".to_string())])).to_string(), + Query::less_than("price", 10).to_string(), + ]).to_string(), + ]).to_string(), + ]), + None, + None, + None, +).await?; +``` +```http +{"method":"or","values":[{"method":"and","values":[{"method":"equal","attribute":"category","values":["books"]},{"method":"lessThan","attribute":"price","values":[20]}]},{"method":"and","values":[{"method":"equal","attribute":"category","values":["magazines"]},{"method":"lessThan","attribute":"price","values":[10]}]}]} +``` +{% /multicode %} + +This example demonstrates how to combine `OR` and `AND` operations. The query uses `Query.or()` to match either condition: books under $20 OR magazines under $10. +Each condition within the OR is composed of two AND conditions - one for the category and one for the price threshold. The database will return documents that match either of these combined conditions. + diff --git a/src/routes/docs/products/databases/documentsdb/quick-start/+page.markdoc b/src/routes/docs/products/databases/documentsdb/quick-start/+page.markdoc new file mode 100644 index 00000000000..92c5f30cf8b --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/quick-start/+page.markdoc @@ -0,0 +1,243 @@ +--- +layout: article +title: Start with DocumentsDB +description: Get started with Appwrite DocumentsDB. Follow a step-by-step guide to create your first database, add a collection, and perform basic document operations. +--- + + +{% section #create-database step=1 title="Create database" %} +Head to your [Appwrite Console](https://cloud.appwrite.io/console/) and click **Create database**. Name it `Oscar` and choose **DocumentsDB** as the database type. +Optionally, add a custom database ID. Select your preferred tier, then click **Create database**. +{% /section %} + +{% section #create-collection step=2 title="Create collection" %} +In the `Oscar` database, click **Create collection** and name it `My books`. Optionally, add a custom collection ID. + +Collections are schemaless, so there are no columns to define. Each document holds its own fields as flexible JSON. + +Open the collection's **Security** tab. Under **Permissions**, add a new role **Any** and check **Create** and **Read**, so anyone can create and read documents. Click **Update** to save. +{% /section %} + + +{% section #create-documents step=3 title="Create documents" %} +To create a document use the `createDocument` method. + +In the **Settings** menu, find your project ID and replace `` in the example. + +Navigate to the `Oscar` database, copy the database ID, and replace ``. +Then, in the `My books` collection, copy the collection ID, and replace ``. + +{% multicode %} +```client-web +import { Client, ID, DocumentsDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const documentsDB = new DocumentsDB(client); + +const promise = documentsDB.createDocument({ + databaseId: '', + collectionId: '', + documentId: ID.unique(), + data: { title: "Hamlet" } +}); + +promise.then(function (response) { + console.log(response); +}, function (error) { + console.log(error); +}); +``` +```client-flutter +import 'package:appwrite/appwrite.dart'; + +void main() async { + final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + + final documentsDB = DocumentsDB(client); + + try { + final document = documentsDB.createDocument( + databaseId: '', + collectionId: '', + documentId: ID.unique(), + data: { "title": "Hamlet" } + ); + } on AppwriteException catch(e) { + print(e); + } +} +``` +```client-apple +import Appwrite +import AppwriteModels + +func main() async throws { + let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + let documentsDB = DocumentsDB(client) + + do { + let document = try await documentsDB.createDocument( + databaseId: "", + collectionId: "", + documentId: ID.unique(), + data: ["title": "Hamlet"] + ) + } catch { + print(error.localizedDescription) + } +} +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.ID +import io.appwrite.services.DocumentsDB + +suspend fun main() { + val client = Client(applicationContext) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + val documentsDB = DocumentsDB(client) + + try { + val document = documentsDB.createDocument( + databaseId = "", + collectionId = "", + documentId = ID.unique(), + data = mapOf("title" to "Hamlet"), + ) + } catch (e: Exception) { + Log.e("Appwrite", "Error: " + e.message) + } +} +``` +{% /multicode %} + +The response should look similar to this. + +```json +{ + "title": "Hamlet", + "$id": "6a423aa70000c39da0be", + "$sequence": "019f12b5-1c89-7094-8d12-5b7e7a09f025", + "$permissions": [], + "$createdAt": "2026-06-29T09:28:07.047+00:00", + "$updatedAt": "2026-06-29T09:28:07.047+00:00", + "$databaseId": "650125c64b3c25ce4bc4", + "$collectionId": "650125cff227cf9f95ad" +} +``` + +{% /section %} + +{% section #list-documents step=4 title="List documents" %} +To read and query data from your collection, use the `listDocuments` endpoint. + +Like the previous step, replace ``, ``, and `` with their respective IDs. +{% multicode %} +```client-web +import { Client, Query, DocumentsDB } from "appwrite"; + +const client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +const documentsDB = new DocumentsDB(client); + +const promise = documentsDB.listDocuments({ + databaseId: "", + collectionId: "", + queries: [ + Query.equal('title', 'Hamlet') + ] +}); + +promise.then(function (response) { + console.log(response); +}, function (error) { + console.log(error); +}); +``` +```client-flutter +import 'package:appwrite/appwrite.dart'; + +void main() async { + final client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + final documentsDB = DocumentsDB(client); + + try { + final documents = await documentsDB.listDocuments( + databaseId: '', + collectionId: '', + queries: [ + Query.equal('title', 'Hamlet') + ] + ); + } on AppwriteException catch(e) { + print(e); + } +} +``` +```client-apple +import Appwrite +import AppwriteModels + +func main() async throws{ + let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + let documentsDB = DocumentsDB(client) + + do { + let documents = try await documentsDB.listDocuments( + databaseId: "", + collectionId: "", + queries: [ + Query.equal("title", value: "Hamlet") + ] + ) + } catch { + print(error.localizedDescription) + } +} +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.DocumentsDB + +suspend fun main() { + val client = Client(applicationContext) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + val documentsDB = DocumentsDB(client) + + try { + val documents = documentsDB.listDocuments( + databaseId = "", + collectionId = "", + queries = listOf( + Query.equal("title", "Hamlet") + ) + ) + } catch (e: AppwriteException) { + Log.e("Appwrite", "Error: " + e.message) + } +} +``` + +{% /multicode %} +{% /section %} diff --git a/src/routes/docs/products/databases/documentsdb/timestamp-overrides/+page.markdoc b/src/routes/docs/products/databases/documentsdb/timestamp-overrides/+page.markdoc new file mode 100644 index 00000000000..0549b732ab4 --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/timestamp-overrides/+page.markdoc @@ -0,0 +1,1266 @@ +--- +layout: article +title: Timestamp overrides +description: Set custom $createdAt and $updatedAt timestamps for your documents when using server SDKs. +--- + +When creating or updating documents, Appwrite automatically sets `$createdAt` and `$updatedAt` timestamps. However, there are scenarios where you might need to set these timestamps manually, such as when migrating data from another system or backfilling historical records. + +{% info title="Server SDKs required" %} +To manually set `$createdAt` and `$updatedAt`, you must use a **server SDK** with an **API key**. These attributes can be passed inside the `data` parameter on any of the create, update, or upsert routes (single or bulk). +{% /info %} + +# Setting custom timestamps {% #setting-custom-timestamps %} + +You can override a document's timestamps by providing ISO 8601 strings (for example, `2025-08-10T12:34:56.000Z`) in the `data` payload. If these attributes are not provided, Appwrite will set them automatically. + +Custom timestamps work with all document operations: create, update, upsert, and their bulk variants. + +## Single document operations {% #single-document-operations %} + +When working with individual documents, you can set custom timestamps during create, update, and upsert operations. + +### Create with custom timestamps {% #create-custom %} + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const documentsDB = new sdk.DocumentsDB(client); + +await documentsDB.createDocument({ + databaseId: '', + collectionId: '', + documentId: sdk.ID.unique(), + data: { + '$createdAt': new Date('2025-08-10T12:34:56.000Z').toISOString(), + '$updatedAt': new Date('2025-08-10T12:34:56.000Z').toISOString(), + // ...your attributes + } +}); +``` +```server-php +use Appwrite\Client; +use Appwrite\ID; +use Appwrite\Services\DocumentsDB; + +$client = (new Client()) + ->setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$documentsDB = new DocumentsDB($client); + +$documentsDB->createDocument( + databaseId: '', + collectionId: '', + documentId: ID::unique(), + data: [ + '$createdAt' => (new DateTime(''))->format(DATE_ATOM), + '$updatedAt' => (new DateTime(''))->format(DATE_ATOM), + // ...your attributes + ] +); +``` +```server-swift +import Appwrite +import Foundation + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let documentsDB = DocumentsDB(client) + +let isoFormatter = ISO8601DateFormatter() +isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] +let customDate = isoFormatter.date(from: "") ?? Date() +let createdAt = isoFormatter.string(from: customDate) +let updatedAt = isoFormatter.string(from: customDate) + +do { + let created = try await documentsDB.createDocument( + databaseId: "", + collectionId: "", + documentId: "", + data: [ + "$createdAt": createdAt, + "$updatedAt": updatedAt, + // ...your attributes + ] + ) + print("Created:", created) +} catch { + print("Create error:", error) +} +``` +```server-python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB +from appwrite.id import ID +from datetime import datetime, timezone + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +documents_db = DocumentsDB(client) + +iso = datetime(2025, 8, 10, 12, 34, 56, tzinfo=timezone.utc).isoformat() + +documents_db.create_document( + database_id='', + collection_id='', + document_id=ID.unique(), + data={ + '$createdAt': iso, + '$updatedAt': iso, + # ...your attributes + } +) +``` +```server-ruby +require 'appwrite' +require 'time' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +documents_db = DocumentsDB.new(client) + +custom_date = Time.parse('2025-08-10T12:34:56.000Z').iso8601 + +documents_db.create_document( + database_id: '', + collection_id: '', + document_id: ID.unique(), + data: { + '$createdAt' => custom_date, + '$updatedAt' => custom_date, + # ...your attributes + } +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndpoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +DocumentsDB documentsDB = new DocumentsDB(client); + +string customDate = DateTimeOffset.Parse("2025-08-10T12:34:56.000Z").ToString("O"); + +await documentsDB.CreateDocument( + databaseId: "", + collectionId: "", + documentId: ID.Unique(), + data: new Dictionary + { + ["$createdAt"] = customDate, + ["$updatedAt"] = customDate, + // ...your attributes + } +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +DocumentsDB documentsDB = DocumentsDB(client); + +String customDate = DateTime.parse('2025-08-10T12:34:56.000Z').toIso8601String(); + +await documentsDB.createDocument( + databaseId: '', + collectionId: '', + documentId: ID.unique(), + data: { + '\$createdAt': customDate, + '\$updatedAt': customDate, + // ...your attributes + }, +); +``` +```rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use appwrite::id::ID; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.create_document( + "", + "", + &ID::unique(), + json!({ + "$createdAt": "2025-08-10T12:34:56.000Z", + "$updatedAt": "2025-08-10T12:34:56.000Z" + // ...your attributes + }), + None, + None, + ).await?; + + println!("Created: {:?}", result); + Ok(()) +} +``` +{% /multicode %} + +### Update with custom timestamps {% #update-custom %} + +When updating documents, you can also set a custom `$updatedAt` timestamp: + +{% multicode %} +```server-nodejs +await documentsDB.updateDocument({ + databaseId: '', + collectionId: '', + documentId: '', + data: { + '$updatedAt': new Date('2025-08-10T12:34:56.000Z').toISOString(), + // ...your attributes + } +}); +``` +```server-php +$documentsDB->updateDocument( + databaseId: '', + collectionId: '', + documentId: '', + data: [ + '$updatedAt' => (new DateTime(''))->format(DATE_ATOM), + // ...your attributes + ] +); +``` +```server-python +from datetime import datetime, timezone + +documents_db.update_document( + database_id='', + collection_id='', + document_id='', + data={ + '$updatedAt': datetime(2025, 8, 10, 12, 34, 56, tzinfo=timezone.utc).isoformat(), + # ...your attributes + } +) +``` +```server-swift +import Appwrite +import Foundation + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let documentsDB = DocumentsDB(client) + +let isoFormatter = ISO8601DateFormatter() +isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] +let updatedAt = isoFormatter.string(from: isoFormatter.date(from: "") ?? Date()) + +do { + let updated = try await documentsDB.updateDocument( + databaseId: "", + collectionId: "", + documentId: "", + data: [ + "$updatedAt": updatedAt, + // ...your attributes + ] + ) + print("Updated:", updated) +} catch { + print("Update error:", error) +} +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +documents_db = DocumentsDB.new(client) + +custom_date = Time.parse('').iso8601 + +documents_db.update_document( + database_id: '', + collection_id: '', + document_id: '', + data: { + '$updatedAt' => custom_date, + # ...your attributes + } +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndpoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +DocumentsDB documentsDB = new DocumentsDB(client); + +string customDate = DateTimeOffset.Parse("").ToString("O"); + +await documentsDB.UpdateDocument( + databaseId: "", + collectionId: "", + documentId: "", + data: new Dictionary + { + ["$updatedAt"] = customDate, + // ...your attributes + } +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +DocumentsDB documentsDB = DocumentsDB(client); + +String customDate = DateTime.parse('').toIso8601String(); + +await documentsDB.updateDocument( + databaseId: '', + collectionId: '', + documentId: '', + data: { + '\$updatedAt': customDate, + // ...your attributes + }, +); +``` +```rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.update_document( + "", + "", + "", + Some(json!({ + "$updatedAt": "2025-08-10T12:34:56.000Z" + // ...your attributes + })), + None, + None, + ).await?; + + println!("Updated: {:?}", result); + Ok(()) +} +``` +{% /multicode %} + +## Bulk operations {% #bulk-operations %} + +Custom timestamps also work with bulk operations, allowing you to set different timestamps for each document in the batch: + +### Bulk create {% #bulk-create %} + +{% multicode %} +```server-nodejs +await documentsDB.createDocuments({ + databaseId: '', + collectionId: '', + documents: [ + { + '$id': sdk.ID.unique(), + '$createdAt': new Date('2024-01-01T00:00:00.000Z').toISOString(), + '$updatedAt': new Date('2024-01-01T00:00:00.000Z').toISOString(), + // ...your attributes + }, + { + '$id': sdk.ID.unique(), + '$createdAt': new Date('2024-02-01T00:00:00.000Z').toISOString(), + '$updatedAt': new Date('2024-02-01T00:00:00.000Z').toISOString(), + // ...your attributes + } + ] +}); +``` +```server-python +documents_db.create_documents( + database_id='', + collection_id='', + documents=[ + { + '$id': ID.unique(), + '$createdAt': datetime(2024, 1, 1, tzinfo=timezone.utc).isoformat(), + '$updatedAt': datetime(2024, 1, 1, tzinfo=timezone.utc).isoformat(), + # ...your attributes + }, + { + '$id': ID.unique(), + '$createdAt': datetime(2024, 2, 1, tzinfo=timezone.utc).isoformat(), + '$updatedAt': datetime(2024, 2, 1, tzinfo=timezone.utc).isoformat(), + # ...your attributes + } + ] +) +``` +```server-php +use Appwrite\Client; +use Appwrite\ID; +use Appwrite\Services\DocumentsDB; + +$client = (new Client()) + ->setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$documentsDB = new DocumentsDB($client); + +$documentsDB->createDocuments( + databaseId: '', + collectionId: '', + documents: [ + [ + '$id' => ID::unique(), + '$createdAt' => (new DateTime(''))->format(DATE_ATOM), + '$updatedAt' => (new DateTime(''))->format(DATE_ATOM), + // ...your attributes + ], + [ + '$id' => ID::unique(), + '$createdAt' => (new DateTime(''))->format(DATE_ATOM), + '$updatedAt' => (new DateTime(''))->format(DATE_ATOM), + // ...your attributes + ], + ] +); +``` +```server-swift +import Appwrite +import Foundation + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let documentsDB = DocumentsDB(client) + +let isoFormatter = ISO8601DateFormatter() +isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] + +let first = isoFormatter.string(from: isoFormatter.date(from: "") ?? Date()) +let second = isoFormatter.string(from: isoFormatter.date(from: "") ?? Date()) + +do { + let bulkCreated = try await documentsDB.createDocuments( + databaseId: "", + collectionId: "", + documents: [ + [ + "$id": ID.unique(), + "$createdAt": first, + "$updatedAt": first, + // ...your attributes + ], + [ + "$id": ID.unique(), + "$createdAt": second, + "$updatedAt": second, + // ...your attributes + ] + ] + ) + print("Bulk create:", bulkCreated) +} catch { + print("Bulk create error:", error) +} +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +documents_db = DocumentsDB.new(client) + +first = Time.parse('').iso8601 +second = Time.parse('').iso8601 + +documents_db.create_documents( + database_id: '', + collection_id: '', + documents: [ + { + '$id' => ID.unique(), + '$createdAt' => first, + '$updatedAt' => first, + # ...your attributes + }, + { + '$id' => ID.unique(), + '$createdAt' => second, + '$updatedAt' => second, + # ...your attributes + } + ] +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndpoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +DocumentsDB documentsDB = new DocumentsDB(client); + +string first = DateTimeOffset.Parse("").ToString("O"); +string second = DateTimeOffset.Parse("").ToString("O"); + +await documentsDB.CreateDocuments( + databaseId: "", + collectionId: "", + documents: new List + { + new Dictionary + { + ["$id"] = ID.Unique(), + ["$createdAt"] = first, + ["$updatedAt"] = first, + // ...your attributes + }, + new Dictionary + { + ["$id"] = ID.Unique(), + ["$createdAt"] = second, + ["$updatedAt"] = second, + // ...your attributes + } + } +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +DocumentsDB documentsDB = DocumentsDB(client); + +String first = DateTime.parse('').toIso8601String(); +String second = DateTime.parse('').toIso8601String(); + +await documentsDB.createDocuments( + databaseId: '', + collectionId: '', + documents: [ + { + '\$id': ID.unique(), + '\$createdAt': first, + '\$updatedAt': first, + // ...your attributes + }, + { + '\$id': ID.unique(), + '\$createdAt': second, + '\$updatedAt': second, + // ...your attributes + } + ], +); +``` +```rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use appwrite::id::ID; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.create_documents( + "", + "", + vec![ + json!({ + "$id": ID::unique(), + "$createdAt": "2024-01-01T00:00:00.000Z", + "$updatedAt": "2024-01-01T00:00:00.000Z" + // ...your attributes + }), + json!({ + "$id": ID::unique(), + "$createdAt": "2024-02-01T00:00:00.000Z", + "$updatedAt": "2024-02-01T00:00:00.000Z" + // ...your attributes + }), + ], + None, + ).await?; + + println!("Bulk create: {:?}", result); + Ok(()) +} +``` +{% /multicode %} + +### Bulk upsert {% #bulk-upsert %} + +{% multicode %} +```server-nodejs +await documentsDB.upsertDocuments({ + databaseId: '', + collectionId: '', + documents: [ + { + '$id': '', + '$createdAt': new Date('2024-01-01T00:00:00.000Z').toISOString(), + '$updatedAt': new Date('2025-01-01T00:00:00.000Z').toISOString(), + // ...your attributes + } + ] +}); +``` +```server-python +documents_db.upsert_documents( + database_id='', + collection_id='', + documents=[ + { + '$id': '', + '$createdAt': datetime(2024, 1, 1, tzinfo=timezone.utc).isoformat(), + '$updatedAt': datetime(2025, 1, 1, tzinfo=timezone.utc).isoformat(), + # ...your attributes + } + ] +) +``` +```server-php +use Appwrite\Client; +use Appwrite\ID; +use Appwrite\Services\DocumentsDB; + +$client = (new Client()) + ->setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$documentsDB = new DocumentsDB($client); + +$documentsDB->upsertDocuments( + databaseId: '', + collectionId: '', + documents: [ + [ + '$id' => '', + '$createdAt' => (new DateTime(''))->format(DATE_ATOM), + '$updatedAt' => (new DateTime(''))->format(DATE_ATOM), + // ...your attributes + ], + ] +); +``` +```server-swift +import Appwrite +import Foundation + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let documentsDB = DocumentsDB(client) + +let isoFormatter = ISO8601DateFormatter() +isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] +let createdAt = isoFormatter.string(from: isoFormatter.date(from: "") ?? Date()) +let updatedAt = isoFormatter.string(from: isoFormatter.date(from: "") ?? Date()) + +do { + let bulkUpserted = try await documentsDB.upsertDocuments( + databaseId: "", + collectionId: "", + documents: [ + [ + "$id": "", + "$createdAt": createdAt, + "$updatedAt": updatedAt, + // ...your attributes + ] + ] + ) + print("Bulk upsert:", bulkUpserted) +} catch { + print("Bulk upsert error:", error) +} +``` +```server-ruby +require 'appwrite' +require 'time' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +documents_db = DocumentsDB.new(client) + +custom_date = Time.parse('').iso8601 + +documents_db.upsert_documents( + database_id: '', + collection_id: '', + documents: [ + { + '$id' => '', + '$createdAt' => custom_date, + '$updatedAt' => custom_date, + # ...your attributes + } + ] +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndpoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +DocumentsDB documentsDB = new DocumentsDB(client); + +string createdAt = DateTimeOffset.Parse("").ToString("O"); +string updatedAt = DateTimeOffset.Parse("").ToString("O"); + +await documentsDB.UpsertDocuments( + databaseId: "", + collectionId: "", + documents: new List + { + new Dictionary + { + ["$id"] = "", + ["$createdAt"] = createdAt, + ["$updatedAt"] = updatedAt, + // ...your attributes + } + } +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +DocumentsDB documentsDB = DocumentsDB(client); + +String createdAt = DateTime.parse('').toIso8601String(); +String updatedAt = DateTime.parse('').toIso8601String(); + +await documentsDB.upsertDocuments( + databaseId: '', + collectionId: '', + documents: [ + { + '\$id': '', + '\$createdAt': createdAt, + '\$updatedAt': updatedAt, + // ...your attributes + } + ], +); +``` +```rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.upsert_documents( + "", + "", + vec![ + json!({ + "$id": "", + "$createdAt": "2024-01-01T00:00:00.000Z", + "$updatedAt": "2025-01-01T00:00:00.000Z" + // ...your attributes + }), + ], + None, + ).await?; + + println!("Bulk upsert: {:?}", result); + Ok(()) +} +``` +{% /multicode %} + +# Common use cases {% #use-cases %} + +Custom timestamps are particularly useful in several scenarios: + +## Data migration {% #data-migration %} +When migrating existing data from another system, you can preserve the original +creation and modification times: + +{% multicode %} +```server-nodejs +await documentsDB.createDocument({ + databaseId: '', + collectionId: 'blog_posts', + documentId: sdk.ID.unique(), + data: { + '$createdAt': '', + '$updatedAt': '', + title: '', + content: '<CONTENT>' + } +}); +``` +```server-php +$documentsDB->createDocument( + databaseId: '<DATABASE_ID>', + collectionId: 'blog_posts', + documentId: ID::unique(), + data: [ + '$createdAt' => '<ORIGINAL_CREATED_AT_ISO>', + '$updatedAt' => '<LAST_MODIFIED_ISO>', + 'title' => '<TITLE>', + 'content' => '<CONTENT>' + ] +); +``` +```server-swift +let _ = try await documentsDB.createDocument( + databaseId: "<DATABASE_ID>", + collectionId: "blog_posts", + documentId: ID.unique(), + data: [ + "$createdAt": "<ORIGINAL_CREATED_AT_ISO>", + "$updatedAt": "<LAST_MODIFIED_ISO>", + "title": "<TITLE>", + "content": "<CONTENT>" + ] +) +``` +```server-python +documents_db.create_document( + database_id='<DATABASE_ID>', + collection_id='blog_posts', + document_id=ID.unique(), + data={ + '$createdAt': '<ORIGINAL_CREATED_AT_ISO>', + '$updatedAt': '<LAST_MODIFIED_ISO>', + 'title': '<TITLE>', + 'content': '<CONTENT>' + } +) +``` +```server-ruby +documents_db.create_document( + database_id: '<DATABASE_ID>', + collection_id: 'blog_posts', + document_id: ID.unique(), + data: { + '$createdAt' => '<ORIGINAL_CREATED_AT_ISO>', + '$updatedAt' => '<LAST_MODIFIED_ISO>', + 'title' => '<TITLE>', + 'content' => '<CONTENT>' + } +) +``` +```server-dotnet +await documentsDB.CreateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "blog_posts", + documentId: ID.Unique(), + data: new Dictionary<string, object> + { + ["$createdAt"] = "<ORIGINAL_CREATED_AT_ISO>", + ["$updatedAt"] = "<LAST_MODIFIED_ISO>", + ["title"] = "<TITLE>", + ["content"] = "<CONTENT>" + } +); +``` +```server-dart +await documentsDB.createDocument( + databaseId: '<DATABASE_ID>', + collectionId: 'blog_posts', + documentId: ID.unique(), + data: { + '\$createdAt': '<ORIGINAL_CREATED_AT_ISO>', + '\$updatedAt': '<LAST_MODIFIED_ISO>', + 'title': '<TITLE>', + 'content': '<CONTENT>' + }, +); +``` +```rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use appwrite::id::ID; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<API_KEY>"); + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.create_document( + "<DATABASE_ID>", + "blog_posts", + &ID::unique(), + json!({ + "$createdAt": "<ORIGINAL_CREATED_AT_ISO>", + "$updatedAt": "<LAST_MODIFIED_ISO>", + "title": "<TITLE>", + "content": "<CONTENT>" + }), + None, + None, + ).await?; + + println!("Created: {:?}", result); + Ok(()) +} +``` +{% /multicode %} + +## Backdating records {% #backdating %} +For historical data entry or when creating records that represent past events: + +{% multicode %} +```server-nodejs +await documentsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: 'transactions', + documentId: sdk.ID.unique(), + data: { + '$createdAt': '2023-12-31T23:59:59.000Z', + '$updatedAt': '2023-12-31T23:59:59.000Z', + amount: 1000, + type: 'year-end-bonus' + } +}); +``` +```server-php +$documentsDB->createDocument( + databaseId: '<DATABASE_ID>', + collectionId: 'transactions', + documentId: ID::unique(), + data: [ + '$createdAt' => '2023-12-31T23:59:59.000Z', + '$updatedAt' => '2023-12-31T23:59:59.000Z', + 'amount' => 1000, + 'type' => 'year-end-bonus' + ] +); +``` +```server-swift +let _ = try await documentsDB.createDocument( + databaseId: "<DATABASE_ID>", + collectionId: "transactions", + documentId: ID.unique(), + data: [ + "$createdAt": "2023-12-31T23:59:59.000Z", + "$updatedAt": "2023-12-31T23:59:59.000Z", + "amount": 1000, + "type": "year-end-bonus" + ] +) +``` +```server-python +documents_db.create_document( + database_id='<DATABASE_ID>', + collection_id='transactions', + document_id=ID.unique(), + data={ + '$createdAt': '2023-12-31T23:59:59.000Z', + '$updatedAt': '2023-12-31T23:59:59.000Z', + 'amount': 1000, + 'type': 'year-end-bonus' + } +) +``` +```server-ruby +documents_db.create_document( + database_id: '<DATABASE_ID>', + collection_id: 'transactions', + document_id: ID.unique(), + data: { + '$createdAt' => '2023-12-31T23:59:59.000Z', + '$updatedAt' => '2023-12-31T23:59:59.000Z', + 'amount' => 1000, + 'type' => 'year-end-bonus' + } +) +``` +```server-dotnet +await documentsDB.CreateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "transactions", + documentId: ID.Unique(), + data: new Dictionary<string, object> + { + ["$createdAt"] = "2023-12-31T23:59:59.000Z", + ["$updatedAt"] = "2023-12-31T23:59:59.000Z", + ["amount"] = 1000, + ["type"] = "year-end-bonus" + } +); +``` +```server-dart +await documentsDB.createDocument( + databaseId: '<DATABASE_ID>', + collectionId: 'transactions', + documentId: ID.unique(), + data: { + '\$createdAt': '2023-12-31T23:59:59.000Z', + '\$updatedAt': '2023-12-31T23:59:59.000Z', + 'amount': 1000, + 'type': 'year-end-bonus' + }, +); +``` +```rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use appwrite::id::ID; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<API_KEY>"); + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.create_document( + "<DATABASE_ID>", + "transactions", + &ID::unique(), + json!({ + "$createdAt": "2023-12-31T23:59:59.000Z", + "$updatedAt": "2023-12-31T23:59:59.000Z", + "amount": 1000, + "type": "year-end-bonus" + }), + None, + None, + ).await?; + + println!("Created: {:?}", result); + Ok(()) +} +``` +{% /multicode %} + +## Synchronization {% #synchronization %} +When synchronizing data between systems while maintaining timestamp consistency: + +{% multicode %} +```server-nodejs +await documentsDB.upsertDocument({ + databaseId: '<DATABASE_ID>', + collectionId: 'users', + documentId: '<DOCUMENT_ID_OR_NEW_ID>', + data: { + '$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>', + profile: '<PROFILE_DATA>' + } +}); +``` +```server-php +$documentsDB->upsertDocument( + databaseId: '<DATABASE_ID>', + collectionId: 'users', + documentId: '<DOCUMENT_ID_OR_NEW_ID>', + data: [ + '$updatedAt' => '<EXTERNAL_LAST_MODIFIED_ISO>', + 'profile' => '<PROFILE_DATA>' + ] +); +``` +```server-swift +let _ = try await documentsDB.upsertDocument( + databaseId: "<DATABASE_ID>", + collectionId: "users", + documentId: "<DOCUMENT_ID_OR_NEW_ID>", + data: [ + "$updatedAt": "<EXTERNAL_LAST_MODIFIED_ISO>", + "profile": "<PROFILE_DATA>" + ] +) +``` +```server-python +documents_db.upsert_document( + database_id='<DATABASE_ID>', + collection_id='users', + document_id='<DOCUMENT_ID_OR_NEW_ID>', + data={ + '$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>', + 'profile': '<PROFILE_DATA>' + } +) +``` +```server-ruby +documents_db.upsert_document( + database_id: '<DATABASE_ID>', + collection_id: 'users', + document_id: '<DOCUMENT_ID_OR_NEW_ID>', + data: { + '$updatedAt' => '<EXTERNAL_LAST_MODIFIED_ISO>', + 'profile' => '<PROFILE_DATA>' + } +) +``` +```server-dotnet +await documentsDB.UpsertDocument( + databaseId: "<DATABASE_ID>", + collectionId: "users", + documentId: "<DOCUMENT_ID_OR_NEW_ID>", + data: new Dictionary<string, object> + { + ["$updatedAt"] = "<EXTERNAL_LAST_MODIFIED_ISO>", + ["profile"] = "<PROFILE_DATA>" + } +); +``` +```server-dart +await documentsDB.upsertDocument( + databaseId: '<DATABASE_ID>', + collectionId: 'users', + documentId: '<DOCUMENT_ID_OR_NEW_ID>', + data: { + '\$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>', + 'profile': '<PROFILE_DATA>' + }, +); +``` +```rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<API_KEY>"); + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.upsert_document( + "<DATABASE_ID>", + "users", + "<DOCUMENT_ID_OR_NEW_ID>", + Some(json!({ + "$updatedAt": "<EXTERNAL_LAST_MODIFIED_ISO>", + "profile": "<PROFILE_DATA>" + })), + None, + None, + ).await?; + + println!("Upserted: {:?}", result); + Ok(()) +} +``` +{% /multicode %} + +{% info title="Timestamp format and usage" %} +- Values must be valid ISO 8601 date-time strings (UTC recommended). Using `toISOString()` (JavaScript) or `datetime.isoformat()` (Python) is a good default. +- You can set either or both attributes as needed. If omitted, Appwrite sets them automatically. +{% /info %} diff --git a/src/routes/docs/products/databases/documentsdb/transactions/+page.markdoc b/src/routes/docs/products/databases/documentsdb/transactions/+page.markdoc new file mode 100644 index 00000000000..be64977d0ad --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/transactions/+page.markdoc @@ -0,0 +1,1274 @@ +--- +layout: article +title: Transactions +description: Stage multiple database operations and commit them atomically. Group changes across databases and collections with ordering, isolation, and conflict detection. +--- + +Transactions let you stage multiple database operations and apply them together, atomically. Use transactions to keep related changes consistent, even when they span multiple databases and collections. + +# How transactions work {% #how-transactions-work %} + +1. Call the [createTransaction](#create-a-transaction) method to create a transaction. This will return a transaction model, including its ID. +2. Stage operations by passing the `transactionId` parameter to supported document, bulk, and atomic numeric methods. You can stage many operations at once with the [createOperations](#create-operations) method. +3. Call the [updateTransaction](#update-transaction) method to commit or roll back. + +On commit, Appwrite replays all staged logs in order inside a real database transaction. Staged operations see earlier staged changes (read your own writes). If any affected document changed outside your transaction, the commit fails with a conflict. + +{% info title="Scope and limitations" %} +You can stage operations across any database and collection within the same transaction. Schema operations (for example, creating or deleting indexes) are not included in transactions. +{% /info %} + +# Limits {% #limits %} + +The maximum number of operations you can stage per transaction depends on your plan: + +| Plan | Max operations per transaction | +|------|-------------------------------| +| Free | 100 | +| Pro | 1,000 | + +# Create a transaction {% #create-a-transaction %} + +Call the `createTransaction` method to begin. It returns a transaction model that includes `$id`. Pass this ID as `transactionId` to subsequent operations. + +{% multicode %} +```client-web +import { Client, DocumentsDB } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>'); + +const documentsDB = new DocumentsDB(client); + +const tx = await documentsDB.createTransaction(); +// tx.$id is your transactionId +``` +```client-react-native +import { Client, DocumentsDB } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>'); + +const documentsDB = new DocumentsDB(client); + +const tx = await documentsDB.createTransaction(); +// tx.$id is your transactionId +``` +```client-flutter +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>'); + +final documentsDB = DocumentsDB(client); + +final tx = await documentsDB.createTransaction(); +// tx.$id is your transactionId +``` +```client-apple +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + +let documentsDB = DocumentsDB(client) + +let tx = try await documentsDB.createTransaction() +// tx.$id is your transactionId +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB + +val client = Client(applicationContext) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + +val documentsDB = DocumentsDB(client) + +val tx = documentsDB.createTransaction() +// tx.$id is your transactionId +``` +```client-android-java +import io.appwrite.Client; +import io.appwrite.services.DocumentsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>"); + +DocumentsDB documentsDB = new DocumentsDB(client); + +// Create a transaction (asynchronous) +documentsDB.createTransaction(new CoroutineCallback<>((tx, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + System.out.println(tx); +})); +``` +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<API_KEY>'); + +const documentsDB = new sdk.DocumentsDB(client); + +const tx = await documentsDB.createTransaction(); +// tx.$id is your transactionId +``` +```server-deno +import * as sdk from 'npm:node-appwrite'; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<API_KEY>'); + +const documentsDB = new sdk.DocumentsDB(client); + +const tx = await documentsDB.createTransaction(); +// tx.$id is your transactionId +``` +```server-python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<API_KEY>') + +documents_db = DocumentsDB(client) + +tx = documents_db.create_transaction() +# tx.$id is your transactionId +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\DocumentsDB; + +$client = new Client(); + +$client + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<API_KEY>') +; + +$documentsDB = new DocumentsDB($client); + +$tx = $documentsDB->createTransaction(); +// $tx->\$id is your transactionId +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<API_KEY>') + +documents_db = DocumentsDB.new(client) + +tx = documents_db.create_transaction +# tx['$id'] is your transactionId +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +var client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<API_KEY>"); + +var documentsDB = new DocumentsDB(client); + +var tx = await documentsDB.CreateTransaction(); +// tx.$id is your transactionId +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +void main() async { + Client client = Client(); + DocumentsDB documentsDB = DocumentsDB(client); + + client + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<API_KEY>'); + + final tx = await documentsDB.createTransaction(); + // tx contains the transaction ID +} +``` +```server-go +package main + +import ( + "log" + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<API_KEY>"), + ) + + documentsDB := appwrite.NewDocumentsDB(client) + + tx, err := documentsDB.CreateTransaction() + if err != nil { log.Fatal(err) } + _ = tx +} +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<API_KEY>") + +let documentsDB = DocumentsDB(client) + +let tx = try await documentsDB.createTransaction() +// tx.$id is your transactionId +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<API_KEY>") + +val documentsDB = DocumentsDB(client) + +val tx = documentsDB.createTransaction() +// tx.$id is your transactionId +``` +```server-java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<API_KEY>"); + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.createTransaction(new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + System.out.println(result); +})); +``` +```server-rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<API_KEY>"); + + let documents_db = DocumentsDB::new(&client); + + let tx = documents_db.create_transaction(None).await?; + // tx.id is your transaction_id + + Ok(()) +} +``` +{% /multicode %} + +# Stage operations {% #stage-operations %} + +Add the `transactionId` parameter to supported methods to stage them instead of immediately persisting. + +When you pass `transactionId`, Appwrite writes the operation to an internal staging area. The target collection is not modified until you commit the transaction. + +## Stage single operations {% #stage-single-operations %} + +Create, update, upsert, delete, and atomic numeric operations accept `transactionId`, as well as their bulk versions (createDocuments, updateDocuments, upsertDocuments, deleteDocuments). + +{% multicode %} +```client-web +// Create inside a transaction +await documentsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { name: 'Walter' }, + transactionId: tx.$id +}); + +// Increment inside a transaction +await documentsDB.incrementDocumentAttribute({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + attribute: 'credits', + value: 1, + transactionId: tx.$id +}); +``` +```client-flutter +// Create inside a transaction +await documentsDB.createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { 'name': 'Walter' }, + transactionId: tx.$id +); + +// Increment inside a transaction +await documentsDB.incrementDocumentAttribute( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + attribute: 'credits', + value: 1, + transactionId: tx.$id +); +``` +```client-apple +// Create inside a transaction +try await documentsDB.createDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + data: ["name": "Walter"], + transactionId: tx.$id +) + +// Increment inside a transaction +try await documentsDB.incrementDocumentAttribute( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + attribute: "credits", + value: 1, + transactionId: tx.$id +) +``` +```server-kotlin +// Create inside a transaction +documentsDB.createDocument( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = "<DOCUMENT_ID>", + data = mapOf("name" to "Walter"), + transactionId = tx.$id +) + +// Increment inside a transaction +documentsDB.incrementDocumentAttribute( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = "<DOCUMENT_ID>", + attribute = "credits", + value = 1, + transactionId = tx.$id +) +``` +```server-java +// Create inside a transaction (asynchronous) +documentsDB.createDocument( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + Map.of("name", "Walter"), + "<TRANSACTION_ID>", + new CoroutineCallback<>((document, error) -> { + if (error != null) { + error.printStackTrace(); + return null; + } + System.out.println(document); + return null; + }) +); + +// Increment inside a transaction (asynchronous) +documentsDB.incrementDocumentAttribute( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + "credits", + 1, + "<TRANSACTION_ID>", + new CoroutineCallback<>((document, error) -> { + if (error != null) { + error.printStackTrace(); + return null; + } + System.out.println(document); + return null; + }) +); +``` +```client-react-native +// Create inside a transaction +await documentsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { name: 'Walter' }, + transactionId: tx.$id +}); + +// Increment inside a transaction +await documentsDB.incrementDocumentAttribute({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + attribute: 'credits', + value: 1, + transactionId: tx.$id +}); +``` +```server-nodejs +// Update inside a transaction +await documentsDB.updateDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { plan: 'pro' }, + transactionId: tx.$id +}); + +// Delete inside a transaction +await documentsDB.deleteDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + transactionId: tx.$id +}); +``` +```server-python +# Upsert inside a transaction +documents_db.upsert_document( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = '<DOCUMENT_ID>', + data = { 'name': 'Walter' }, + transaction_id = tx.id +) + +# Decrement inside a transaction +documents_db.decrement_document_attribute( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = '<DOCUMENT_ID>', + attribute = 'credits', + value = 1, + transaction_id = tx.id +) +``` +```server-php +// Create inside a transaction +$documentsDB->createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: ['name' => 'Walter'], + transactionId: $tx['$id'] +); + +// Increment inside a transaction +$documentsDB->incrementDocumentAttribute( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + attribute: 'credits', + value: 1, + transactionId: $tx['$id'] +); +``` +```server-ruby +# Create inside a transaction +documents_db.create_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: '<DOCUMENT_ID>', + data: { 'name' => 'Walter' }, + transaction_id: tx['$id'] +) + +# Increment inside a transaction +documents_db.increment_document_attribute( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: '<DOCUMENT_ID>', + attribute: 'credits', + value: 1, + transaction_id: tx['$id'] +) +``` +```server-dotnet +// Create inside a transaction +await documentsDB.CreateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + data: new Dictionary<string, object> { ["name"] = "Walter" }, + transactionId: tx.Id +); + +// Increment inside a transaction +await documentsDB.IncrementDocumentAttribute( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + attribute: "credits", + value: 1, + transactionId: tx.Id +); +``` +```server-dart +// Create inside a transaction +await documentsDB.createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { 'name': 'Walter' }, + transactionId: tx.Id +); + +// Increment inside a transaction +await documentsDB.incrementDocumentAttribute( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + attribute: 'credits', + value: 1, + transactionId: tx.Id +); +``` +```server-deno +// Create inside a transaction +await documentsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { name: 'Walter' }, + transactionId: tx.$id +}); + +// Increment inside a transaction +await documentsDB.incrementDocumentAttribute({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + attribute: 'credits', + value: 1, + transactionId: tx.$id +}); +``` +```server-swift +// Create inside a transaction +try await documentsDB.createDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + data: ["name": "Walter"], + transactionId: tx.$id +) + +// Increment inside a transaction +try await documentsDB.incrementDocumentAttribute( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + attribute: "credits", + value: 1, + transactionId: tx.$id +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<API_KEY>"); + + let documents_db = DocumentsDB::new(&client); + + let tx = documents_db.create_transaction(None).await?; + + // Update inside a transaction + documents_db.update_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + Some(json!({ "plan": "pro" })), + None, + Some(&tx.id), + ).await?; + + // Delete inside a transaction + documents_db.delete_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + Some(&tx.id), + ).await?; + + Ok(()) +} +``` +{% /multicode %} + +## Stage many with createOperations {% #create-operations %} + +Use the `createOperations` method to stage multiple operations across databases and collections in a single request. Provide an array of operation objects: + +```json +[ + { + "action": "create|update|upsert|increment|decrement|delete|bulkCreate|bulkUpdate|bulkUpsert|bulkDelete", + "databaseId": "<DATABASE_ID>", + "collectionId": "<COLLECTION_ID>", + "documentId": "<DOCUMENT_ID>", + "data": {} + } +] +``` + +### Provide data for each action (createOperations) {% #provide-data-for-each-action %} + +### Create, update, and upsert {% #create-update-upsert %} +Pass a raw data object. +```json +{ "name": "Walter" } +``` + +### Increment and decrement {% #increment-decrement %} +Pass a value and optionally `min`/`max` bounds. +```json +{ "value": 1, "min": 0, "max": 1000, "attribute": "<ATTRIBUTE_NAME>" } +``` + +### Bulk create and bulk upsert {% #bulk-create-upsert %} +Pass an array of raw data objects. +```json +[{ "$id": "123", "name": "Walter" }] +``` + +### Bulk update {% #bulk-update %} +Pass queries and the data to apply. +```json +{ "queries": [{"method": "equal", "attribute": "status", "values": ["draft"]}], "data": { "status": "published" } } +``` + +### Bulk delete {% #bulk-delete %} +Pass queries to select documents to delete. +```json +{ "queries": [{"method": "equal", "attribute": "archived", "values": [true]}] } +``` + +{% multicode %} +```server-nodejs +// Stage multiple operations at once +await documentsDB.createOperations({ + transactionId: tx.$id, + operations: [ + { + action: 'create', + databaseId: '<DB_A>', + collectionId: '<COLLECTION_1>', + documentId: 'u1', + data: { name: 'Walter' } + }, + { + action: 'increment', + databaseId: '<DB_B>', + collectionId: '<COLLECTION_2>', + documentId: 'u2', + data: { value: 1, min: 0, attribute: 'credits' } + } + ] +}); +``` +```server-python +documents_db.create_operations( + transaction_id = tx.id, + operations = [ + { + 'action': 'create', + 'databaseId': '<DB_A>', + 'collectionId': '<COLLECTION_1>', + 'documentId': 'u1', + 'data': { 'name': 'Walter' } + }, + { + 'action': 'increment', + 'databaseId': '<DB_B>', + 'collectionId': '<COLLECTION_2>', + 'documentId': 'u2', + 'data': { 'value': 1, 'min': 0, 'attribute': 'credits' } + } + ] +) +``` +```client-web +await documentsDB.createOperations({ + transactionId: tx.$id, + operations: [ + { + action: 'create', + databaseId: '<DB_A>', + collectionId: '<COLLECTION_1>', + documentId: 'u1', + data: { name: 'Walter' } + }, + { + action: 'increment', + databaseId: '<DB_B>', + collectionId: '<COLLECTION_2>', + documentId: 'u2', + data: { value: 1, min: 0, attribute: 'credits' } + } + ] +}); +``` +```client-flutter +await documentsDB.createOperations( + transactionId: tx.$id, + operations: [ + { + 'action': 'create', + 'databaseId': '<DB_A>', + 'collectionId': '<COLLECTION_1>', + 'documentId': 'u1', + 'data': { 'name': 'Walter' } + }, + { + 'action': 'increment', + 'databaseId': '<DB_B>', + 'collectionId': '<COLLECTION_2>', + 'documentId': 'u2', + 'data': { 'value': 1, 'min': 0, 'attribute': 'credits' } + } + ], +); +``` +```client-apple +try await documentsDB.createOperations( + transactionId: tx.$id, + operations: [ + [ + "action": "create", + "databaseId": "<DB_A>", + "collectionId": "<COLLECTION_1>", + "documentId": "u1", + "data": ["name": "Walter"] + ], + [ + "action": "increment", + "databaseId": "<DB_B>", + "collectionId": "<COLLECTION_2>", + "documentId": "u2", + "data": ["value": 1, "min": 0, "attribute": "credits"] + ] + ] +) +``` +```server-kotlin +documentsDB.createOperations( + transactionId = tx.$id, + operations = listOf( + mapOf( + "action" to "create", + "databaseId" to "<DB_A>", + "collectionId" to "<COLLECTION_1>", + "documentId" to "u1", + "data" to mapOf("name" to "Walter") + ), + mapOf( + "action" to "increment", + "databaseId" to "<DB_B>", + "collectionId" to "<COLLECTION_2>", + "documentId" to "u2", + "data" to mapOf("value" to 1, "min" to 0, "attribute" to "credits") + ) + ) +) +``` +```server-java +// Stage multiple operations at once (asynchronous) +List<Map<String, Object>> operations = Arrays.asList( + Map.of( + "action", "create", + "databaseId", "<DB_A>", + "collectionId", "<COLLECTION_1>", + "documentId", "u1", + "data", Map.of("name", "Walter") + ), + Map.of( + "action", "increment", + "databaseId", "<DB_B>", + "collectionId", "<COLLECTION_2>", + "documentId", "u2", + "data", Map.of("value", 1, "min", 0, "attribute", "credits") + ) +); + +documentsDB.createOperations( + "<TRANSACTION_ID>", + operations, + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return null; + } + System.out.println(result); + return null; + }) +); +``` +```client-react-native +await documentsDB.createOperations({ + transactionId: tx.$id, + operations: [ + { + action: 'create', + databaseId: '<DB_A>', + collectionId: '<COLLECTION_1>', + documentId: 'u1', + data: { name: 'Walter' } + }, + { + action: 'increment', + databaseId: '<DB_B>', + collectionId: '<COLLECTION_2>', + documentId: 'u2', + data: { value: 1, min: 0, attribute: 'credits' } + } + ] +}); +``` +```server-deno +await documentsDB.createOperations({ + transactionId: tx.$id, + operations: [ + { + action: 'create', + databaseId: '<DB_A>', + collectionId: '<COLLECTION_1>', + documentId: 'u1', + data: { name: 'Walter' } + }, + { + action: 'increment', + databaseId: '<DB_B>', + collectionId: '<COLLECTION_2>', + documentId: 'u2', + data: { value: 1, min: 0, attribute: 'credits' } + } + ] +}); +``` +```server-php +$documentsDB->createOperations( + transactionId: $tx['$id'], + operations: [ + [ + 'action' => 'create', + 'databaseId' => '<DB_A>', + 'collectionId' => '<COLLECTION_1>', + 'documentId' => 'u1', + 'data' => [ 'name' => 'Walter' ] + ], + [ + 'action' => 'increment', + 'databaseId' => '<DB_B>', + 'collectionId' => '<COLLECTION_2>', + 'documentId' => 'u2', + 'data' => [ 'value' => 1, 'min' => 0, 'attribute' => 'credits' ] + ] + ] +); +``` +```server-ruby +documents_db.create_operations( + transaction_id: tx['$id'], + operations: [ + { + 'action' => 'create', + 'databaseId' => '<DB_A>', + 'collectionId' => '<COLLECTION_1>', + 'documentId' => 'u1', + 'data' => { 'name' => 'Walter' } + }, + { + 'action' => 'increment', + 'databaseId' => '<DB_B>', + 'collectionId' => '<COLLECTION_2>', + 'documentId' => 'u2', + 'data' => { 'value' => 1, 'min' => 0, 'attribute' => 'credits' } + } + ] +) +``` +```server-dotnet +await documentsDB.CreateOperations( + transactionId: tx.Id, + operations: new List<Dictionary<string, object>> + { + new Dictionary<string, object> + { + ["action"] = "create", + ["databaseId"] = "<DB_A>", + ["collectionId"] = "<COLLECTION_1>", + ["documentId"] = "u1", + ["data"] = new Dictionary<string, object> { ["name"] = "Walter" } + }, + new Dictionary<string, object> + { + ["action"] = "increment", + ["databaseId"] = "<DB_B>", + ["collectionId"] = "<COLLECTION_2>", + ["documentId"] = "u2", + ["data"] = new Dictionary<string, object> { ["value"] = 1, ["min"] = 0, ["attribute"] = "credits" } + } + } +); +``` +```server-dart +await documentsDB.createOperations( + transactionId: tx.Id, + operations: [ + { + 'action': 'create', + 'databaseId': '<DB_A>', + 'collectionId': '<COLLECTION_1>', + 'documentId': 'u1', + 'data': { 'name': 'Walter' } + }, + { + 'action': 'increment', + 'databaseId': '<DB_B>', + 'collectionId': '<COLLECTION_2>', + 'documentId': 'u2', + 'data': { 'value': 1, 'min': 0, 'attribute': 'credits' } + } + ] +); +``` +```server-swift +try await documentsDB.createOperations( + transactionId: tx.$id, + operations: [ + [ + "action": "create", + "databaseId": "<DB_A>", + "collectionId": "<COLLECTION_1>", + "documentId": "u1", + "data": ["name": "Walter"] + ], + [ + "action": "increment", + "databaseId": "<DB_B>", + "collectionId": "<COLLECTION_2>", + "documentId": "u2", + "data": ["value": 1, "min": 0, "attribute": "credits"] + ] + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<API_KEY>"); + + let documents_db = DocumentsDB::new(&client); + + let tx = documents_db.create_transaction(None).await?; + + // Stage multiple operations at once + documents_db.create_operations( + &tx.id, + Some(vec![ + json!({ + "action": "create", + "databaseId": "<DB_A>", + "collectionId": "<COLLECTION_1>", + "documentId": "u1", + "data": { "name": "Walter" } + }), + json!({ + "action": "increment", + "databaseId": "<DB_B>", + "collectionId": "<COLLECTION_2>", + "documentId": "u2", + "data": { "value": 1, "min": 0, "attribute": "credits" } + }), + ]), + ).await?; + + Ok(()) +} +``` +{% /multicode %} + +# Commit or roll back {% #commit-or-rollback %} + +When you are done staging operations, call the `updateTransaction` method to finalize the transaction. + +{% multicode %} +```client-web +// Commit +await documentsDB.updateTransaction({ + transactionId: tx.$id, + commit: true +}); + +// Or roll back +await documentsDB.updateTransaction({ + transactionId: tx.$id, + rollback: true +}); +``` +```client-flutter +// Commit +await documentsDB.updateTransaction( + transactionId: tx.$id, + commit: true +); + +// Roll back +await documentsDB.updateTransaction( + transactionId: tx.$id, + rollback: true +); +``` +```client-apple +// Commit +try await documentsDB.updateTransaction( + transactionId: tx.$id, + commit: true +) + +// Roll back +try await documentsDB.updateTransaction( + transactionId: tx.$id, + rollback: true +) +``` +```server-kotlin +// Commit +documentsDB.updateTransaction( + transactionId = tx.$id, + commit = true +) + +// Roll back +documentsDB.updateTransaction( + transactionId = tx.$id, + rollback = true +) +``` +```server-java +// Commit (asynchronous) +documentsDB.updateTransaction( + "<TRANSACTION_ID>", + true, + false, + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return null; + } + System.out.println(result); + return null; + }) +); + +// Roll back (asynchronous) +documentsDB.updateTransaction( + "<TRANSACTION_ID>", + false, + true, + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return null; + } + System.out.println(result); + return null; + }) +); +``` +```client-react-native +// Commit +await documentsDB.updateTransaction({ + transactionId: tx.$id, + commit: true +}); + +// Roll back +await documentsDB.updateTransaction({ + transactionId: tx.$id, + rollback: true +}); +``` +```server-nodejs +// Commit +await documentsDB.updateTransaction({ + transactionId: tx.$id, + commit: true +}); + +// Roll back +await documentsDB.updateTransaction({ + transactionId: tx.$id, + rollback: true +}); +``` +```server-python +# Commit +documents_db.update_transaction( + transaction_id = tx.id, + commit = True +) + +# Roll back +documents_db.update_transaction( + transaction_id = tx.id, + rollback = True +) +``` +```server-php +// Commit +$documentsDB->updateTransaction( + transactionId: $tx['$id'], + commit: true +); + +// Roll back +$documentsDB->updateTransaction( + transactionId: $tx['$id'], + rollback: true +); +``` +```server-ruby +# Commit +documents_db.update_transaction( + transaction_id: tx['$id'], + commit: true +) + +# Roll back +documents_db.update_transaction( + transaction_id: tx['$id'], + rollback: true +) +``` +```server-dotnet +// Commit +await documentsDB.UpdateTransaction( + transactionId: tx.Id, + commit: true +); + +// Roll back +await documentsDB.UpdateTransaction( + transactionId: tx.Id, + rollback: true +); +``` +```server-dart +// Commit +await documentsDB.updateTransaction( + transactionId: tx.Id, + commit: true +); + +// Roll back +await documentsDB.updateTransaction( + transactionId: tx.Id, + rollback: true +); +``` +```server-rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<API_KEY>"); + + let documents_db = DocumentsDB::new(&client); + + // Commit + documents_db.update_transaction( + "<TRANSACTION_ID>", + Some(true), + None, + ).await?; + + // Roll back + documents_db.update_transaction( + "<TRANSACTION_ID>", + None, + Some(true), + ).await?; + + Ok(()) +} +``` +{% /multicode %} + +# Handle conflicts {% #handle-conflicts %} + +On commit, Appwrite verifies that documents affected by your transaction haven't changed externally since they were staged. If a conflicting change is detected, the commit fails with a conflict error. Resolve the conflict (for example, refetch and re-stage) and try again. + +{% info title="Best practices" %} +Keep transactions short-lived to reduce the likelihood of conflicts. Stage related updates in the order they must be applied. Prefer `createOperations` when you need to stage many changes across multiple collections. +{% /info %} + +{% arrow_link href="/docs/references" %} +Explore the API references +{% /arrow_link %} diff --git a/src/routes/docs/products/databases/mysql/+layout.svelte b/src/routes/docs/products/databases/mysql/+layout.svelte new file mode 100644 index 00000000000..6db5d3b4c4a --- /dev/null +++ b/src/routes/docs/products/databases/mysql/+layout.svelte @@ -0,0 +1,152 @@ +<script lang="ts"> + import Docs from '$lib/layouts/Docs.svelte'; + import Sidebar, { type NavParent, type NavTree } from '$lib/layouts/Sidebar.svelte'; + + let { children } = $props(); + + const parent: NavParent = { + href: '/docs/products/databases', + label: 'MySQL' + }; + + const navigation: NavTree = [ + { + label: 'Getting started', + items: [ + { + label: 'Overview', + href: '/docs/products/databases/mysql' + }, + { + label: 'Quick start', + href: '/docs/products/databases/mysql/quick-start' + } + ] + }, + { + label: 'Connecting', + items: [ + { + label: 'Connections', + href: '/docs/products/databases/mysql/connections' + }, + { + label: 'Connection pooling', + href: '/docs/products/databases/mysql/connection-pooling' + } + ] + }, + { + label: 'Manage', + items: [ + { + label: 'Backups', + href: '/docs/products/databases/mysql/backups' + }, + { + label: 'Branches', + href: '/docs/products/databases/mysql/branches' + }, + { + label: 'High availability', + href: '/docs/products/databases/mysql/high-availability' + }, + { + label: 'Scaling', + href: '/docs/products/databases/mysql/scaling' + }, + { + label: 'Network security', + href: '/docs/products/databases/mysql/network-security' + }, + { + label: 'Monitoring', + href: '/docs/products/databases/mysql/monitoring' + }, + { + label: 'Maintenance', + href: '/docs/products/databases/mysql/maintenance' + } + ] + }, + { + label: 'Integrations', + items: [ + { + label: 'Node.js drivers', + href: '/docs/products/databases/mysql/integrations/drivers' + }, + { + label: 'Prisma', + href: '/docs/products/databases/mysql/integrations/prisma' + }, + { + label: 'Drizzle', + href: '/docs/products/databases/mysql/integrations/drizzle' + }, + { + label: 'Auth.js', + href: '/docs/products/databases/mysql/integrations/auth-js' + }, + { + label: 'Better Auth', + href: '/docs/products/databases/mysql/integrations/better-auth' + }, + { + label: 'Laravel', + href: '/docs/products/databases/mysql/integrations/laravel' + }, + { + label: 'Rails', + href: '/docs/products/databases/mysql/integrations/rails' + }, + { + label: 'Django', + href: '/docs/products/databases/mysql/integrations/django' + }, + { + label: 'FastAPI', + href: '/docs/products/databases/mysql/integrations/fastapi' + }, + { + label: 'Spring Boot', + href: '/docs/products/databases/mysql/integrations/spring-boot' + }, + { + label: 'EF Core', + href: '/docs/products/databases/mysql/integrations/ef-core' + }, + { + label: 'GORM', + href: '/docs/products/databases/mysql/integrations/gorm' + }, + { + label: 'Next.js', + href: '/docs/products/databases/mysql/integrations/nextjs' + }, + { + label: 'dbt', + href: '/docs/products/databases/mysql/integrations/dbt' + }, + { + label: 'Metabase', + href: '/docs/products/databases/mysql/integrations/metabase' + }, + { + label: 'Grafana', + href: '/docs/products/databases/mysql/integrations/grafana' + }, + { + label: 'Retool', + href: '/docs/products/databases/mysql/integrations/retool' + } + ] + } + ]; +</script> + +<Docs variant="two-side-navs"> + <Sidebar {navigation} {parent} /> + + {@render children()} +</Docs> diff --git a/src/routes/docs/products/databases/mysql/+page.markdoc b/src/routes/docs/products/databases/mysql/+page.markdoc new file mode 100644 index 00000000000..b026e6c750d --- /dev/null +++ b/src/routes/docs/products/databases/mysql/+page.markdoc @@ -0,0 +1,104 @@ +--- +layout: article +title: MySQL +description: Run a native MySQL database provisioned for your project and connect to it directly with standard MySQL clients. +--- + +Appwrite native MySQL databases give you a managed MySQL instance provisioned for your project. You pick the compute specification, and Appwrite provisions the engine in your project's region with its own storage, networking, and credentials, exposed through a per-database public hostname secured with TLS. + +Native databases are different from Appwrite databases like [TablesDB](/docs/products/databases/tablesdb), [DocumentsDB](/docs/products/databases/documentsdb), and [VectorsDB](/docs/products/databases/vectorsdb), which are accessed through Appwrite SDKs and platform APIs. A native MySQL database gives you the raw engine. You connect with the `mysql` client or any MySQL driver and use the full feature set of MySQL, with no Appwrite abstraction layer in between. + +{% info title="Looking for Appwrite data APIs?" %} +If you want Appwrite SDKs, platform permissions, and serverless scaling for app data, start with [Appwrite Databases](/docs/products/databases). Native MySQL is for teams who want a managed MySQL engine they can talk to directly with their own ORM, migrations, and drivers. +{% /info %} + +# Supported versions {% #versions %} + +Appwrite manages the database container, storage, backups, and networking. You bring the application. + +| Engine | Default version | Other supported versions | Default port | +|------------|-----------------|--------------------------|--------------| +| MySQL | 8.4 | 8.0 | 3306 | + +The version is selected on create and can be upgraded later. Upgrades run online by provisioning a second instance on the new version, streaming data over with logical replication, and moving traffic once replication is caught up. + +A new database starts in a `provisioning` state and becomes `ready` within a few minutes. Large specifications or high-availability configurations take longer to schedule; poll the database status or check the databases list until it reports `ready`. + +# Regions {% #regions %} + +A native database lives in the same region as the project that owns it. There is no per-database region selector. Each database gets a unique hostname in the form `db-<hash>.<region>.appwrite.center`, and data does not leave the region. + +# Feature overview {% #features %} + +{% cards %} +{% cards_item href="/docs/products/databases/mysql/connections" title="Connections" %} +Connect with the `mysql` client or any driver. Credentials are rotatable through the API. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/connection-pooling" title="Connection pooling" %} +Per-database connection pooler with automatic read/write split when high availability is enabled. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/backups" title="Backups" %} +Scheduled backups, manual backups, restores, and point-in-time recovery. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/branches" title="Branches" %} +Spin up an ephemeral copy of your database in seconds from a storage snapshot. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/high-availability" title="High availability" %} +Up to five synchronous, semi-synchronous, or asynchronous replicas with automatic failover. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/scaling" title="Scaling" %} +Resize compute online and grow storage automatically as your data grows. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/network-security" title="Network security" %} +TLS by default, optional IP allowlists, and a dedicated hostname per database. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/monitoring" title="Monitoring" %} +Poll live health, connection, replica, and storage information from your pipelines and monitoring. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/maintenance" title="Maintenance" %} +Maintenance windows, online version upgrades, and pause and resume. +{% /cards_item %} +{% /cards %} + +# Specifications and pricing {% #specifications %} + +Each database runs against one of the following compute specifications, billed monthly: + +| Tier | Specification | CPU | Memory | Storage | Bandwidth | Connections | Price | +|-----------------|----------------|---------|--------|---------|-----------|-------------|---------| +| Starter | `s-1vcpu-1gb` | 1 core | 1 GB | 10 GB | 50 GB | 100 | $10/mo | +| Standard | `s-2vcpu-2gb` | 2 cores | 2 GB | 25 GB | 200 GB | 200 | $20/mo | +| Standard Plus | `s-2vcpu-4gb` | 2 cores | 4 GB | 50 GB | 400 GB | 500 | $49/mo | +| Professional | `s-4vcpu-8gb` | 4 cores | 8 GB | 200 GB | 1 TB | 1,000 | $85/mo | +| Business | `s-4vcpu-16gb` | 4 cores | 16 GB | 500 GB | 2 TB | 2,000 | $160/mo | +| Business Plus | `s-4vcpu-32gb` | 4 cores | 32 GB | 1 TB | 5 TB | 4,000 | $299/mo | +| Enterprise | `s-8vcpu-32gb` | 8 cores | 32 GB | 2 TB | 7.5 TB | 5,000 | $425/mo | +| Enterprise Plus | `s-8vcpu-64gb` | 8 cores | 64 GB | 3 TB | 10 TB | 10,000 | $699/mo | + +The Storage and Bandwidth columns are the monthly allowances included with each tier. Usage beyond them is billed as overage, and optional features are billed as add-ons on top of the tier price: + +| Add-on | Price | +|----------------------------|---------------------------------| +| Storage overage | $0.125 per GB per month | +| Bandwidth overage | $0.08 per GB per month | +| High availability replica | 50% of the tier price, per replica | +| Point-in-time recovery | 20% of the tier price | + +You can [resize between tiers](/docs/products/databases/mysql/scaling) as your workload changes. + +# Limits {% #limits %} + +The following limits apply per database: + +| Limit | Value | +|--------------------------------|-----------------------------------------------------------| +| Databases per project | 10 | +| High availability replicas | 0 - 5 | +| IP allowlist entries | 100 | +| Backup retention | 1 - 365 days | +| Point-in-time recovery window | 1 - 35 days | +| Max simultaneous connections | 10,000 platform cap, lower on smaller specifications | + +# Billing and plan requirements {% #plans %} + +Native databases are available on paid plans, and a payment method must be attached to your organization. Each database is billed by the hour against its [compute specification](#specifications), with separate line items for overages and add-ons. See [pricing](/pricing) for details. diff --git a/src/routes/docs/products/databases/mysql/backups/+page.markdoc b/src/routes/docs/products/databases/mysql/backups/+page.markdoc new file mode 100644 index 00000000000..410eb7980c3 --- /dev/null +++ b/src/routes/docs/products/databases/mysql/backups/+page.markdoc @@ -0,0 +1,1371 @@ +--- +layout: article +title: Backups +description: Scheduled backups, manual backups, restores, and point-in-time recovery for your MySQL database. +--- + +Native databases are backed up automatically. Backups are stored off the database instance and restorable from the API. For finer recovery granularity than scheduled backups, enable point-in-time recovery. + +# Automatic backups {% #automatic %} + +Every database gets a default backup policy when it is provisioned, so you have scheduled backups from day one. You can adjust the default policy, or add more policies with different schedules and retention windows. + +# Backup policies {% #policies %} + +A policy defines a schedule (cron expression) and a retention period in days. Create one with: + +{% multicode %} + +```server-nodejs +import { Client, Mysql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.createBackupPolicy({ + databaseId: '<DATABASE_ID>', + policyId: 'hourly', + name: 'Hourly', + schedule: '0 * * * *', + retention: 30, +}); +``` + +```server-deno +import { Client, Mysql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.createBackupPolicy({ + databaseId: '<DATABASE_ID>', + policyId: 'hourly', + name: 'Hourly', + schedule: '0 * * * *', + retention: 30, +}); +``` + +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Mysql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$mysql = new Mysql($client); + +$mysql->createBackupPolicy( + databaseId: '<DATABASE_ID>', + policyId: 'hourly', + name: 'Hourly', + schedule: '0 * * * *', + retention: 30, +); +``` + +```server-python +from appwrite.client import Client +from appwrite.services.mysql import Mysql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +mysql = Mysql(client) + +mysql.create_backup_policy( + database_id='<DATABASE_ID>', + policy_id='hourly', + name='Hourly', + schedule='0 * * * *', + retention=30, +) +``` + +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +mysql = Mysql.new(client) + +mysql.create_backup_policy( + database_id: '<DATABASE_ID>', + policy_id: 'hourly', + name: 'Hourly', + schedule: '0 * * * *', + retention: 30, +) +``` + +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Mysql mysql = new Mysql(client); + +await mysql.CreateBackupPolicy( + databaseId: "<DATABASE_ID>", + policyId: "hourly", + name: "Hourly", + schedule: "0 * * * *", + retention: 30 +); +``` + +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Mysql mysql = Mysql(client); + +await mysql.createBackupPolicy( + databaseId: '<DATABASE_ID>', + policyId: 'hourly', + name: 'Hourly', + schedule: '0 * * * *', + retention: 30, +); +``` + +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Mysql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val mysql = Mysql(client) + +mysql.createBackupPolicy( + databaseId = "<DATABASE_ID>", + policyId = "hourly", + name = "Hourly", + schedule = "0 * * * *", + retention = 30, +) +``` + +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let mysql = Mysql(client) + +_ = try await mysql.createBackupPolicy( + databaseId: "<DATABASE_ID>", + policyId: "hourly", + name: "Hourly", + schedule: "0 * * * *", + retention: 30 +) +``` + +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewMysql(client) + + _, err := service.CreateBackupPolicy("<DATABASE_ID>", "hourly", "Hourly", "0 * * * *", 30) + if err != nil { + panic(err) + } +} +``` + +```server-rust +use appwrite::client::Client; +use appwrite::services::mysql::Mysql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let mysql = Mysql::new(&client); + + mysql.create_backup_policy("<DATABASE_ID>", "hourly", "Hourly", "0 * * * *", 30, None, None).await?; + + Ok(()) +} +``` + +```bash +curl -X POST \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + -H "Content-Type: application/json" \ + -d '{ + "policyId": "hourly", + "name": "Hourly", + "schedule": "0 * * * *", + "retention": 30 + }' \ + https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>/backups/policies +``` + +{% /multicode %} + +| Parameter | Value | Description | +| ----------- | ----------------------- | ------------------------------------------ | +| `policyId` | custom ID or `unique()` | Policy identifier | +| `name` | text | Display name | +| `schedule` | cron expression | When backups run, for example `0 3 * * *` | +| `retention` | 1 - 365 days | How long backups from this policy are kept | + +List, update, and delete policies with `listBackupPolicies`, `updateBackupPolicy`, and `deleteBackupPolicy`. The number of policies you can create depends on your plan. + +# Manual backups {% #manual %} + +Take an on-demand backup before a risky change: + +{% multicode %} + +```server-nodejs +import { Client, Mysql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +const backup = await mysql.createBackup({ + databaseId: '<DATABASE_ID>', +}); +``` + +```server-deno +import { Client, Mysql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +const backup = await mysql.createBackup({ + databaseId: '<DATABASE_ID>', +}); +``` + +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Mysql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$mysql = new Mysql($client); + +$backup = $mysql->createBackup( + databaseId: '<DATABASE_ID>', +); +``` + +```server-python +from appwrite.client import Client +from appwrite.services.mysql import Mysql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +mysql = Mysql(client) + +backup = mysql.create_backup( + database_id='<DATABASE_ID>', +) +``` + +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +mysql = Mysql.new(client) + +backup = mysql.create_backup( + database_id: '<DATABASE_ID>', +) +``` + +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Mysql mysql = new Mysql(client); + +var backup = await mysql.CreateBackup( + databaseId: "<DATABASE_ID>" +); +``` + +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Mysql mysql = Mysql(client); + +final backup = await mysql.createBackup( + databaseId: '<DATABASE_ID>', +); +``` + +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Mysql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val mysql = Mysql(client) + +val backup = mysql.createBackup( + databaseId = "<DATABASE_ID>", +) +``` + +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let mysql = Mysql(client) + +let backup = try await mysql.createBackup( + databaseId: "<DATABASE_ID>" +) +``` + +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewMysql(client) + + result, err := service.CreateBackup("<DATABASE_ID>") + if err != nil { + panic(err) + } + _ = result +} +``` + +```server-rust +use appwrite::client::Client; +use appwrite::services::mysql::Mysql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let mysql = Mysql::new(&client); + + let backup = mysql.create_backup("<DATABASE_ID>", None).await?; + + Ok(()) +} +``` + +```bash +curl -X POST \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>/backups +``` + +{% /multicode %} + +The backup runs asynchronously with status `pending` until it completes. List backups and check their status with `listBackups`, or fetch one with `getBackup`. + +# Restore from a backup {% #restore %} + +Restoring replaces the database's current data with the backup's contents. The database status moves to `restoring` and returns to `ready` when the restore completes; connections are unavailable during the restore. + +{% multicode %} + +```server-nodejs +import { Client, Mysql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.createRestoration({ + databaseId: '<DATABASE_ID>', + type: 'backup', + backupId: '<BACKUP_ID>', +}); +``` + +```server-deno +import { Client, Mysql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.createRestoration({ + databaseId: '<DATABASE_ID>', + type: 'backup', + backupId: '<BACKUP_ID>', +}); +``` + +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Mysql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$mysql = new Mysql($client); + +$mysql->createRestoration( + databaseId: '<DATABASE_ID>', + type: 'backup', + backupId: '<BACKUP_ID>', +); +``` + +```server-python +from appwrite.client import Client +from appwrite.services.mysql import Mysql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +mysql = Mysql(client) + +mysql.create_restoration( + database_id='<DATABASE_ID>', + type='backup', + backup_id='<BACKUP_ID>', +) +``` + +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +mysql = Mysql.new(client) + +mysql.create_restoration( + database_id: '<DATABASE_ID>', + type: 'backup', + backup_id: '<BACKUP_ID>', +) +``` + +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Mysql mysql = new Mysql(client); + +await mysql.CreateRestoration( + databaseId: "<DATABASE_ID>", + type: "backup", + backupId: "<BACKUP_ID>" +); +``` + +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Mysql mysql = Mysql(client); + +await mysql.createRestoration( + databaseId: '<DATABASE_ID>', + type: 'backup', + backupId: '<BACKUP_ID>', +); +``` + +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Mysql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val mysql = Mysql(client) + +mysql.createRestoration( + databaseId = "<DATABASE_ID>", + type = "backup", + backupId = "<BACKUP_ID>", +) +``` + +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let mysql = Mysql(client) + +_ = try await mysql.createRestoration( + databaseId: "<DATABASE_ID>", + type: "backup", + backupId: "<BACKUP_ID>" +) +``` + +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/mysql" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewMysql(client) + + _, err := service.CreateRestoration( + "<DATABASE_ID>", + mysql.WithCreateRestorationType("backup"), + mysql.WithCreateRestorationBackupId("<BACKUP_ID>"), + ) + if err != nil { + panic(err) + } +} +``` + +```server-rust +use appwrite::client::Client; +use appwrite::services::mysql::Mysql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let mysql = Mysql::new(&client); + + mysql.create_restoration("<DATABASE_ID>", Some("backup"), Some("<BACKUP_ID>"), None).await?; + + Ok(()) +} +``` + +```bash +curl -X POST \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "backup", + "backupId": "<BACKUP_ID>" + }' \ + https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>/restorations +``` + +{% /multicode %} + +Track progress with `getRestoration` or the database status. + +{% info title="Restores overwrite current data" %} +Everything written after the backup was taken is lost when you restore it. If you need the current state too, take a manual backup first, or use a [branch](/docs/products/databases/mysql/branches) to inspect data without touching the live database. +{% /info %} + +# Point-in-time recovery {% #pitr %} + +Scheduled backups recover to fixed snapshots. Point-in-time recovery (PITR) continuously archives the binary log, so you can restore to any moment inside the retention window, for example the second before a bad migration ran. + +Enable PITR when creating the database, or through the API: + +{% multicode %} + +```server-nodejs +import { Client, Mysql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.update({ + databaseId: '<DATABASE_ID>', + pitr: true, + pitrRetentionDays: 7, +}); +``` + +```server-deno +import { Client, Mysql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.update({ + databaseId: '<DATABASE_ID>', + pitr: true, + pitrRetentionDays: 7, +}); +``` + +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Mysql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$mysql = new Mysql($client); + +$mysql->update( + databaseId: '<DATABASE_ID>', + pitr: true, + pitrRetentionDays: 7, +); +``` + +```server-python +from appwrite.client import Client +from appwrite.services.mysql import Mysql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +mysql = Mysql(client) + +mysql.update( + database_id='<DATABASE_ID>', + pitr=True, + pitr_retention_days=7, +) +``` + +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +mysql = Mysql.new(client) + +mysql.update( + database_id: '<DATABASE_ID>', + pitr: true, + pitr_retention_days: 7, +) +``` + +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Mysql mysql = new Mysql(client); + +await mysql.Update( + databaseId: "<DATABASE_ID>", + pitr: true, + pitrRetentionDays: 7 +); +``` + +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Mysql mysql = Mysql(client); + +await mysql.update( + databaseId: '<DATABASE_ID>', + pitr: true, + pitrRetentionDays: 7, +); +``` + +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Mysql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val mysql = Mysql(client) + +mysql.update( + databaseId = "<DATABASE_ID>", + pitr = true, + pitrRetentionDays = 7, +) +``` + +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let mysql = Mysql(client) + +_ = try await mysql.update( + databaseId: "<DATABASE_ID>", + pitr: true, + pitrRetentionDays: 7 +) +``` + +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/mysql" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewMysql(client) + + _, err := service.Update( + "<DATABASE_ID>", + mysql.WithUpdatePitr(true), + mysql.WithUpdatePitrRetentionDays(7), + ) + if err != nil { + panic(err) + } +} +``` + +```server-rust +use appwrite::client::Client; +use appwrite::services::mysql::Mysql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let mysql = Mysql::new(&client); + + mysql.update("<DATABASE_ID>", None, None, None, None, None, None, None, None, Some(true), Some(7), None, None, None, None, None, None, None, None, None, None).await?; + + Ok(()) +} +``` + +```bash +curl -X PATCH \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + -H "Content-Type: application/json" \ + -d '{ + "pitr": true, + "pitrRetentionDays": 7 + }' \ + https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID> +``` + +{% /multicode %} + +`pitrRetentionDays` accepts 1 to 35 days. PITR is billed as an add-on on top of your specification; see [pricing](/pricing). + +## Check the recovery window {% #pitr-windows %} + +You can retrieve the time ranges you can restore to. Right after enabling PITR, no recovery window is available yet; the window opens once continuous archiving has captured its first segment. + +{% multicode %} + +```server-nodejs +import { Client, Mysql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +const windows = await mysql.getPitr({ + databaseId: '<DATABASE_ID>', +}); +``` + +```server-deno +import { Client, Mysql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +const windows = await mysql.getPitr({ + databaseId: '<DATABASE_ID>', +}); +``` + +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Mysql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$mysql = new Mysql($client); + +$windows = $mysql->getPitr( + databaseId: '<DATABASE_ID>', +); +``` + +```server-python +from appwrite.client import Client +from appwrite.services.mysql import Mysql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +mysql = Mysql(client) + +windows = mysql.get_pitr( + database_id='<DATABASE_ID>', +) +``` + +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +mysql = Mysql.new(client) + +windows = mysql.get_pitr( + database_id: '<DATABASE_ID>', +) +``` + +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Mysql mysql = new Mysql(client); + +var windows = await mysql.GetPitr( + databaseId: "<DATABASE_ID>" +); +``` + +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Mysql mysql = Mysql(client); + +final windows = await mysql.getPitr( + databaseId: '<DATABASE_ID>', +); +``` + +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Mysql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val mysql = Mysql(client) + +val windows = mysql.getPitr( + databaseId = "<DATABASE_ID>", +) +``` + +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let mysql = Mysql(client) + +let windows = try await mysql.getPitr( + databaseId: "<DATABASE_ID>" +) +``` + +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewMysql(client) + + result, err := service.GetPitr("<DATABASE_ID>") + if err != nil { + panic(err) + } + _ = result +} +``` + +```server-rust +use appwrite::client::Client; +use appwrite::services::mysql::Mysql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let mysql = Mysql::new(&client); + + let windows = mysql.get_pitr("<DATABASE_ID>").await?; + + Ok(()) +} +``` + +```bash +curl -X GET \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>/pitr +``` + +{% /multicode %} + +## Restore to a point in time {% #pitr-restore %} + +Pass a Unix timestamp inside the recovery window: + +{% multicode %} + +```server-nodejs +import { Client, Mysql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.createRestoration({ + databaseId: '<DATABASE_ID>', + type: 'pitr', + targetTime: 1767225600, +}); +``` + +```server-deno +import { Client, Mysql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.createRestoration({ + databaseId: '<DATABASE_ID>', + type: 'pitr', + targetTime: 1767225600, +}); +``` + +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Mysql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$mysql = new Mysql($client); + +$mysql->createRestoration( + databaseId: '<DATABASE_ID>', + type: 'pitr', + targetTime: 1767225600, +); +``` + +```server-python +from appwrite.client import Client +from appwrite.services.mysql import Mysql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +mysql = Mysql(client) + +mysql.create_restoration( + database_id='<DATABASE_ID>', + type='pitr', + target_time=1767225600, +) +``` + +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +mysql = Mysql.new(client) + +mysql.create_restoration( + database_id: '<DATABASE_ID>', + type: 'pitr', + target_time: 1767225600, +) +``` + +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Mysql mysql = new Mysql(client); + +await mysql.CreateRestoration( + databaseId: "<DATABASE_ID>", + type: "pitr", + targetTime: 1767225600 +); +``` + +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Mysql mysql = Mysql(client); + +await mysql.createRestoration( + databaseId: '<DATABASE_ID>', + type: 'pitr', + targetTime: 1767225600, +); +``` + +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Mysql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val mysql = Mysql(client) + +mysql.createRestoration( + databaseId = "<DATABASE_ID>", + type = "pitr", + targetTime = 1767225600, +) +``` + +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let mysql = Mysql(client) + +_ = try await mysql.createRestoration( + databaseId: "<DATABASE_ID>", + type: "pitr", + targetTime: 1767225600 +) +``` + +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/mysql" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewMysql(client) + + _, err := service.CreateRestoration( + "<DATABASE_ID>", + mysql.WithCreateRestorationType("pitr"), + mysql.WithCreateRestorationTargetTime(1767225600), + ) + if err != nil { + panic(err) + } +} +``` + +```server-rust +use appwrite::client::Client; +use appwrite::services::mysql::Mysql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let mysql = Mysql::new(&client); + + mysql.create_restoration("<DATABASE_ID>", Some("pitr"), None, Some(1767225600)).await?; + + Ok(()) +} +``` + +```bash +curl -X POST \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "pitr", + "targetTime": 1767225600 + }' \ + https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>/restorations +``` + +{% /multicode %} + +Like a backup restore, a PITR restore is in-place: the database is unavailable while restoring and everything after the target time is discarded. + +# Limits {% #limits %} + +| Limit | Value | +| ---------------- | -------------- | +| Backup retention | 1 - 365 days | +| PITR retention | 1 - 35 days | +| Backup policies | Plan-dependent | diff --git a/src/routes/docs/products/databases/mysql/branches/+page.markdoc b/src/routes/docs/products/databases/mysql/branches/+page.markdoc new file mode 100644 index 00000000000..2b897b0430e --- /dev/null +++ b/src/routes/docs/products/databases/mysql/branches/+page.markdoc @@ -0,0 +1,716 @@ +--- +layout: article +title: Branches +description: Spin up an ephemeral, isolated copy of your MySQL database in seconds from a storage snapshot. Use branches for previews, migrations, and testing. +--- + +A branch is a short-lived, isolated copy of your database. It has its own hostname and reuses the parent's credentials, because it is a snapshot copy of the parent's storage volume taken at a point in time. Branches are not replicas: once created, they diverge from the parent and never sync back. + +{% info title="Branches do not merge back" %} +There is no branch merge operation. Use a branch to validate a migration, data repair, or application change, then intentionally cut application traffic over to the validated database or copy the data you want back with engine-native tools. Appwrite does not reconcile two diverged database histories for you. +{% /info %} + +Use cases: + +- **Preview environments**: one branch per pull request, destroyed when the PR closes +- **Test migrations**: apply a destructive `ALTER` against the branch first, observe the behavior, then run it against the source +- **Reproduce a bug**: branch the database, attach a debugger, throw the branch away when done +- **Heavy analytical queries**: `EXPLAIN ANALYZE` experiments against a branch cannot slow down the primary + +# How it works {% #how %} + +Creating a branch takes a fast `CHECKPOINT` on the parent, snapshots the parent's storage volume with a near-instant copy-on-write operation, and provisions a branch instance from the snapshot on the same engine version with its own isolated storage. Branch compute is fixed and lightweight, enough to validate a change rather than carry production load, and is not configurable. The parent and the branch share storage at the moment of branching; storage cost grows as the two diverge. + +# Create a branch {% #create %} + +{% multicode %} + +```server-nodejs +import { Client, Mysql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.createBranch({ + databaseId: '<DATABASE_ID>', + branchId: 'preview', + ttl: 86400, +}); +``` + +```server-deno +import { Client, Mysql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.createBranch({ + databaseId: '<DATABASE_ID>', + branchId: 'preview', + ttl: 86400, +}); +``` + +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Mysql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$mysql = new Mysql($client); + +$mysql->createBranch( + databaseId: '<DATABASE_ID>', + branchId: 'preview', + ttl: 86400, +); +``` + +```server-python +from appwrite.client import Client +from appwrite.services.mysql import Mysql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +mysql = Mysql(client) + +mysql.create_branch( + database_id='<DATABASE_ID>', + branch_id='preview', + ttl=86400, +) +``` + +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +mysql = Mysql.new(client) + +mysql.create_branch( + database_id: '<DATABASE_ID>', + branch_id: 'preview', + ttl: 86400, +) +``` + +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Mysql mysql = new Mysql(client); + +await mysql.CreateBranch( + databaseId: "<DATABASE_ID>", + branchId: "preview", + ttl: 86400 +); +``` + +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Mysql mysql = Mysql(client); + +await mysql.createBranch( + databaseId: '<DATABASE_ID>', + branchId: 'preview', + ttl: 86400, +); +``` + +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Mysql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val mysql = Mysql(client) + +mysql.createBranch( + databaseId = "<DATABASE_ID>", + branchId = "preview", + ttl = 86400, +) +``` + +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let mysql = Mysql(client) + +_ = try await mysql.createBranch( + databaseId: "<DATABASE_ID>", + branchId: "preview", + ttl: 86400 +) +``` + +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/mysql" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewMysql(client) + + _, err := service.CreateBranch( + "<DATABASE_ID>", + mysql.WithCreateBranchBranchId("preview"), + mysql.WithCreateBranchTtl(86400), + ) + if err != nil { + panic(err) + } +} +``` + +```server-rust +use appwrite::client::Client; +use appwrite::services::mysql::Mysql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let mysql = Mysql::new(&client); + + mysql.create_branch("<DATABASE_ID>", Some("preview"), Some(86400)).await?; + + Ok(()) +} +``` + +```bash +curl -X POST \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <YOUR_API_KEY>" \ + -H "Content-Type: application/json" \ + -d '{ + "branchId": "preview", + "ttl": 86400 + }' \ + https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>/branches +``` + +{% /multicode %} + +Both fields are optional: + +| Field | Default | Purpose | +| ---------- | ------------------ | ------------------------------------------------------------------- | +| `branchId` | auto-generated | Custom ID (`a-z`, `A-Z`, `0-9`, `.`, `-`, `_`, max 36 chars) | +| `ttl` | `86400` (24 hours) | Lifetime in seconds before the branch expires (min 300, max 604800) | + +The call is asynchronous and returns immediately while the branch provisions in the background. When the TTL elapses, the branch and its storage are removed automatically. + +# List branches and connect {% #list %} + +Each entry carries its metadata and connection details, so there is no separate credentials call: + +{% multicode %} + +```server-nodejs +import { Client, Mysql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +const branches = await mysql.listBranches({ + databaseId: '<DATABASE_ID>', +}); +``` + +```server-deno +import { Client, Mysql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +const branches = await mysql.listBranches({ + databaseId: '<DATABASE_ID>', +}); +``` + +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Mysql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$mysql = new Mysql($client); + +$branches = $mysql->listBranches( + databaseId: '<DATABASE_ID>', +); +``` + +```server-python +from appwrite.client import Client +from appwrite.services.mysql import Mysql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +mysql = Mysql(client) + +branches = mysql.list_branches( + database_id='<DATABASE_ID>', +) +``` + +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +mysql = Mysql.new(client) + +branches = mysql.list_branches( + database_id: '<DATABASE_ID>', +) +``` + +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Mysql mysql = new Mysql(client); + +var branches = await mysql.ListBranches( + databaseId: "<DATABASE_ID>" +); +``` + +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Mysql mysql = Mysql(client); + +final branches = await mysql.listBranches( + databaseId: '<DATABASE_ID>', +); +``` + +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Mysql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val mysql = Mysql(client) + +val branches = mysql.listBranches( + databaseId = "<DATABASE_ID>", +) +``` + +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let mysql = Mysql(client) + +let branches = try await mysql.listBranches( + databaseId: "<DATABASE_ID>" +) +``` + +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewMysql(client) + + result, err := service.ListBranches("<DATABASE_ID>") + if err != nil { + panic(err) + } + _ = result +} +``` + +```server-rust +use appwrite::client::Client; +use appwrite::services::mysql::Mysql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let mysql = Mysql::new(&client); + + let branches = mysql.list_branches("<DATABASE_ID>").await?; + + Ok(()) +} +``` + +```bash +curl -X GET \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <YOUR_API_KEY>" \ + https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>/branches +``` + +{% /multicode %} + +A branch gets its own hostname, and reuses the parent's username and password because it is a snapshot copy of the parent's storage. The port is the standard `3306`; branches have no connection pooler. Connect with the branch's `connectionString` straight from the response: + +```bash +mysql --defaults-extra-file=<(printf '[client]\npassword=%s\n' "$BRANCH_PASSWORD") -h <branch host> -P 3306 -u admin -D <branch database> +``` + +# Delete a branch {% #delete %} + +Deleting a branch removes the branch's instance, its storage volume, and the underlying snapshot. There is no soft delete: once the branch is gone, the data is gone. + +{% multicode %} + +```server-nodejs +import { Client, Mysql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.deleteBranch({ + databaseId: '<DATABASE_ID>', + branchId: 'preview', +}); +``` + +```server-deno +import { Client, Mysql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.deleteBranch({ + databaseId: '<DATABASE_ID>', + branchId: 'preview', +}); +``` + +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Mysql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$mysql = new Mysql($client); + +$mysql->deleteBranch( + databaseId: '<DATABASE_ID>', + branchId: 'preview', +); +``` + +```server-python +from appwrite.client import Client +from appwrite.services.mysql import Mysql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +mysql = Mysql(client) + +mysql.delete_branch( + database_id='<DATABASE_ID>', + branch_id='preview', +) +``` + +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +mysql = Mysql.new(client) + +mysql.delete_branch( + database_id: '<DATABASE_ID>', + branch_id: 'preview', +) +``` + +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Mysql mysql = new Mysql(client); + +await mysql.DeleteBranch( + databaseId: "<DATABASE_ID>", + branchId: "preview" +); +``` + +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Mysql mysql = Mysql(client); + +await mysql.deleteBranch( + databaseId: '<DATABASE_ID>', + branchId: 'preview', +); +``` + +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Mysql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val mysql = Mysql(client) + +mysql.deleteBranch( + databaseId = "<DATABASE_ID>", + branchId = "preview", +) +``` + +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let mysql = Mysql(client) + +_ = try await mysql.deleteBranch( + databaseId: "<DATABASE_ID>", + branchId: "preview" +) +``` + +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewMysql(client) + + _, err := service.DeleteBranch("<DATABASE_ID>", "preview") + if err != nil { + panic(err) + } +} +``` + +```server-rust +use appwrite::client::Client; +use appwrite::services::mysql::Mysql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let mysql = Mysql::new(&client); + + mysql.delete_branch("<DATABASE_ID>", "preview").await?; + + Ok(()) +} +``` + +```bash +curl -X DELETE \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <YOUR_API_KEY>" \ + https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>/branches/preview +``` + +{% /multicode %} + +# Billing {% #billing %} + +A branch runs on fixed, lightweight compute, so its cost is dominated by storage. The snapshot is free at the moment of branching; storage cost accumulates as the branch's data diverges from the parent. There is no separate branch line item, branches roll into your regular database storage and compute totals. + +# Use case: per-PR preview database {% #ci-example %} + +A CI pipeline that branches on every pull request and tears down on close: + +```yaml +name: preview-database + +on: + pull_request: + types: [opened, reopened, closed] + +jobs: + branch: + if: github.event.action != 'closed' + runs-on: ubuntu-latest + steps: + - name: Create branch + run: | + curl -X POST \ + -H "X-Appwrite-Project: ${{ vars.APPWRITE_PROJECT_ID }}" \ + -H "X-Appwrite-Key: ${{ secrets.APPWRITE_API_KEY }}" \ + -H "Content-Type: application/json" \ + -d '{"branchId": "pr-${{ github.event.number }}", "ttl": 604800}' \ + https://<REGION>.cloud.appwrite.io/v1/mysql/${{ vars.DATABASE_ID }}/branches || true + + teardown: + if: github.event.action == 'closed' + runs-on: ubuntu-latest + steps: + - name: Delete branch + run: | + curl -X DELETE \ + -H "X-Appwrite-Project: ${{ vars.APPWRITE_PROJECT_ID }}" \ + -H "X-Appwrite-Key: ${{ secrets.APPWRITE_API_KEY }}" \ + https://<REGION>.cloud.appwrite.io/v1/mysql/${{ vars.DATABASE_ID }}/branches/pr-${{ github.event.number }} +``` + +The `|| true` on the create call makes the workflow idempotent: if the branch already exists, the call is a no-op. diff --git a/src/routes/docs/products/databases/mysql/connection-pooling/+page.markdoc b/src/routes/docs/products/databases/mysql/connection-pooling/+page.markdoc new file mode 100644 index 00000000000..9a2719a9239 --- /dev/null +++ b/src/routes/docs/products/databases/mysql/connection-pooling/+page.markdoc @@ -0,0 +1,274 @@ +--- +layout: article +title: Connection pooling +description: Configure the per-database connection pooler to serve many short-lived clients, with automatic read/write splitting when high availability is enabled. +--- + +MySQL creates one backend process per connection, which makes each connection relatively expensive. Serverless functions, edge runtimes, and horizontally scaled application servers can easily exhaust the connection limit of your specification. The connection pooler sits in front of your database and multiplexes many client connections onto a small pool of server connections. + +The pooler runs next to your database and is reachable on port `6033` on the same hostname. Your application connects to the pooler exactly like it would connect to MySQL directly, same credentials, same TLS. The pooler is available on specifications that run on dedicated compute; the smallest specifications run on shared capacity and do not include it. + +# Pool modes {% #modes %} + +| Mode | Behavior | Use for | +|---------------|-----------------------------------------------------------------------------|------------------------------------------------| +| `transaction` | A server connection is assigned for the duration of a transaction, then returned to the pool | Serverless and most applications (default) | +| `session` | A server connection is held for the entire client session | Session-level features: server-side prepared statements, user variables, temporary tables | + +Transaction mode gives the highest connection multiplexing but does not support session-level state. If your framework relies on session state such as server-side prepared statements or temporary tables, use session mode. + +# Configure the pooler {% #configure %} + +Read the current pooler configuration with `getPooler`, and tune the pool mode and sizes with `updatePooler`. All parameters are optional; omitted values keep their current setting. + +{% multicode %} +```server-nodejs +import { Client, Mysql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +const pooler = await mysql.updatePooler({ + databaseId: '<DATABASE_ID>', + mode: 'transaction', + maxConnections: 500, + defaultPoolSize: 25, +}); +``` +```server-deno +import { Client, Mysql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +const pooler = await mysql.updatePooler({ + databaseId: '<DATABASE_ID>', + mode: 'transaction', + maxConnections: 500, + defaultPoolSize: 25, +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Mysql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$mysql = new Mysql($client); + +$pooler = $mysql->updatePooler( + databaseId: '<DATABASE_ID>', + mode: 'transaction', + maxConnections: 500, + defaultPoolSize: 25, +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.mysql import Mysql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +mysql = Mysql(client) + +pooler = mysql.update_pooler( + database_id='<DATABASE_ID>', + mode='transaction', + max_connections=500, + default_pool_size=25, +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +mysql = Mysql.new(client) + +pooler = mysql.update_pooler( + database_id: '<DATABASE_ID>', + mode: 'transaction', + max_connections: 500, + default_pool_size: 25, +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Mysql mysql = new Mysql(client); + +var pooler = await mysql.UpdatePooler( + databaseId: "<DATABASE_ID>", + mode: "transaction", + maxConnections: 500, + defaultPoolSize: 25 +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Mysql mysql = Mysql(client); + +final pooler = await mysql.updatePooler( + databaseId: '<DATABASE_ID>', + mode: 'transaction', + maxConnections: 500, + defaultPoolSize: 25, +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Mysql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val mysql = Mysql(client) + +val pooler = mysql.updatePooler( + databaseId = "<DATABASE_ID>", + mode = "transaction", + maxConnections = 500, + defaultPoolSize = 25, +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let mysql = Mysql(client) + +let pooler = try await mysql.updatePooler( + databaseId: "<DATABASE_ID>", + mode: "transaction", + maxConnections: 500, + defaultPoolSize: 25 +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/mysql" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + mysqlService := appwrite.NewMysql(client) + + result, err := mysqlService.UpdatePooler( + "<DATABASE_ID>", + mysql.WithUpdatePoolerMode("transaction"), + mysql.WithUpdatePoolerMaxConnections(500), + mysql.WithUpdatePoolerDefaultPoolSize(25), + ) + if err != nil { + panic(err) + } + _ = result +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::Mysql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let mysql = Mysql::new(&client); + + let pooler = mysql.update_pooler("<DATABASE_ID>", Some("transaction"), Some(500), Some(25), None, None, None, None, None).await?; + + Ok(()) +} +``` +```bash +curl -X PATCH \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <YOUR_API_KEY>" \ + -H "Content-Type: application/json" \ + -d '{ + "mode": "transaction", + "maxConnections": 500, + "defaultPoolSize": 25 + }' \ + https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>/pooler +``` +{% /multicode %} + +| Parameter | Range | Description | +|----------------------|----------------|--------------------------------------------------------------------------------| +| `mode` | `transaction`, `session` | How long a server connection stays assigned to a client | +| `maxConnections` | 10 - 10,000 | Maximum pooled client connections. Cannot exceed the connection cap of your specification | +| `defaultPoolSize` | 1 - 1,000 | Server connections per user in the pool | +| `readWriteSplitting` | boolean | Route `SELECT`s to replicas, writes and locked reads to the primary. Defaults to on when high availability is enabled | + +# Connect through the pooler {% #connect %} + +Take your normal connection string and change the port to `6033`: + +```bash +mysql://admin:<password>@db-<hash>.<region>.appwrite.center:6033/<database> +``` + +Point your application's runtime traffic at the pooler port. Keep migrations and long-lived administrative sessions on the direct port `3306`, schema changes and tools like `mysqldump` expect session semantics and can misbehave in transaction mode. + +# Read/write splitting {% #read-write-splitting %} + +When [high availability](/docs/products/databases/mysql/high-availability) is enabled, the pooler can route read-only statements to replicas and everything else to the primary. `SELECT ... FOR UPDATE` and statements inside explicit transactions go to the primary. Replicas replicate asynchronously by default, so a read that immediately follows a write can be stale; use `sync` or `quorum` replication mode if you need read-your-writes consistency through the pooler. + +# Sizing guidance {% #sizing %} + +A useful starting point for `defaultPoolSize` is `4 x CPU cores` of your specification, and it rarely helps to go above your specification's connection cap divided by the number of databases sharing the workload. Watch the connection metrics and increase the pool only when clients queue for a connection. + +# When not to use the pooler {% #when-not %} + +Skip the pooler when you have a small, fixed number of long-lived application servers that each maintain their own driver-level pool, and their combined connection count fits comfortably in your specification's limit. Adding a pooler in that setup only adds a hop. diff --git a/src/routes/docs/products/databases/mysql/connections/+page.markdoc b/src/routes/docs/products/databases/mysql/connections/+page.markdoc new file mode 100644 index 00000000000..a8556130322 --- /dev/null +++ b/src/routes/docs/products/databases/mysql/connections/+page.markdoc @@ -0,0 +1,509 @@ +--- +layout: article +title: Connections +description: Connect to your MySQL database with the mysql client or any standard driver. Retrieve connection details and rotate the primary password. +--- + +A native MySQL database exposes a MySQL endpoint over TLS. You connect to it the same way you would connect to any MySQL server: with the `mysql` client, any driver in any language, or any ORM. + +# Get connection details with the API {% #credentials %} + +The connection details are returned on the database object itself. Fetch the database with an API key that has the `databases.read` scope: + +{% multicode %} +```server-nodejs +import { Client, Mysql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +const database = await mysql.get({ + databaseId: '<DATABASE_ID>', +}); + +console.log(database.connectionString); +``` +```server-deno +import { Client, Mysql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +const database = await mysql.get({ + databaseId: '<DATABASE_ID>', +}); + +console.log(database.connectionString); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Mysql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$mysql = new Mysql($client); + +$database = $mysql->get(databaseId: '<DATABASE_ID>'); + +echo $database['connectionString']; +``` +```server-python +from appwrite.client import Client +from appwrite.services.mysql import Mysql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +mysql = Mysql(client) + +database = mysql.get(database_id='<DATABASE_ID>') + +print(database.connection_string) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +mysql = Mysql.new(client) + +database = mysql.get(database_id: '<DATABASE_ID>') + +puts database.connection_string +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Mysql mysql = new Mysql(client); + +var database = await mysql.Get(databaseId: "<DATABASE_ID>"); + +Console.WriteLine(database.ConnectionString); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Mysql mysql = Mysql(client); + +final database = await mysql.get( + databaseId: '<DATABASE_ID>', +); + +print(database.connectionString); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Mysql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val mysql = Mysql(client) + +val database = mysql.get( + databaseId = "<DATABASE_ID>", +) + +println(database.connectionString) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let mysql = Mysql(client) + +let database = try await mysql.get( + databaseId: "<DATABASE_ID>" +) + +print(database.connectionString) +``` +```server-go +package main + +import ( + "fmt" + + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + mysql := appwrite.NewMysql(client) + + database, err := mysql.Get("<DATABASE_ID>") + if err != nil { + panic(err) + } + fmt.Println(database.ConnectionString) +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::mysql::Mysql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let mysql = Mysql::new(&client); + + let database = mysql.get("<DATABASE_ID>").await?; + + println!("{}", database.connection_string); + + Ok(()) +} +``` +```bash +curl -X GET \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <YOUR_API_KEY>" \ + https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID> +``` +{% /multicode %} + +The response includes the connection fields alongside the database configuration: + +```json +{ + "$id": "<DATABASE_ID>", + "name": "main", + "engine": "mysql", + "version": "8.4", + "status": "ready", + "hostname": "db-<hash>.<region>.appwrite.center", + "connectionPort": 3306, + "connectionUser": "admin", + "connectionPassword": "<password>", + "connectionString": "mysql://admin:<password>@db-<hash>.<region>.appwrite.center:3306/<database>" +} +``` + +The primary user is `admin` and the database name is generated per database. + +# Connect with the mysql client {% #mysql-client %} + +Pass the individual connection values to the `mysql` command-line client and enter the password when prompted: + +```bash +mysql -h db-<hash>.<region>.appwrite.center -P 3306 -u admin -p -D <database> +``` + +# Rotate the primary password {% #rotate %} + +If your password is compromised, or your security policy requires regular rotation, you can issue a new password for the primary user. The change is applied atomically in the engine, and the response carries the new connection details. Existing sessions stay alive until they disconnect, then have to authenticate with the new password. The API key needs the `databases.write` scope. + +{% multicode %} +```server-nodejs +import { Client, Mysql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +const database = await mysql.updateCredentials({ + databaseId: '<DATABASE_ID>', +}); +``` +```server-deno +import { Client, Mysql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +const database = await mysql.updateCredentials({ + databaseId: '<DATABASE_ID>', +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Mysql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$mysql = new Mysql($client); + +$database = $mysql->updateCredentials(databaseId: '<DATABASE_ID>'); +``` +```server-python +from appwrite.client import Client +from appwrite.services.mysql import Mysql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +mysql = Mysql(client) + +database = mysql.update_credentials(database_id='<DATABASE_ID>') +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +mysql = Mysql.new(client) + +database = mysql.update_credentials(database_id: '<DATABASE_ID>') +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Mysql mysql = new Mysql(client); + +var database = await mysql.UpdateCredentials(databaseId: "<DATABASE_ID>"); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Mysql mysql = Mysql(client); + +final database = await mysql.updateCredentials( + databaseId: '<DATABASE_ID>', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Mysql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val mysql = Mysql(client) + +val database = mysql.updateCredentials( + databaseId = "<DATABASE_ID>", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let mysql = Mysql(client) + +let database = try await mysql.updateCredentials( + databaseId: "<DATABASE_ID>" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + mysql := appwrite.NewMysql(client) + + _, err := mysql.UpdateCredentials("<DATABASE_ID>") + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::mysql::Mysql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let mysql = Mysql::new(&client); + + let database = mysql.update_credentials("<DATABASE_ID>").await?; + + Ok(()) +} +``` +```bash +curl -X PATCH \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <YOUR_API_KEY>" \ + https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>/credentials +``` +{% /multicode %} + +# TLS {% #tls %} + +Connections on Appwrite Cloud are encrypted with TLS, terminated at the edge and forwarded to your database over the internal network. The connection string returned on the database object carries the right SSL settings for your environment, so drivers need no extra configuration. + +For IP allowlists and other network controls, see [network security](/docs/products/databases/mysql/network-security). + +# Connecting from an application {% #drivers %} + +There is nothing Appwrite-specific about the driver setup. A few example snippets: + +{% multicode %} +```server-nodejs +import mysql from 'mysql2/promise'; + +const connection = await mysql.createConnection(process.env.DATABASE_URL); + +const [rows] = await connection.query('SELECT NOW() AS now'); +console.log(rows); + +await connection.end(); +``` +```server-python +import os +import mysql.connector + +conn = mysql.connector.connect( + host=os.environ['DB_HOST'], + port=3306, + user='admin', + password=os.environ['DB_PASSWORD'], + database=os.environ['DB_NAME'], +) +cur = conn.cursor() +cur.execute('SELECT NOW()') +print(cur.fetchone()) +conn.close() +``` +```server-php +<?php + +$pdo = new PDO( + sprintf('mysql:host=%s;port=3306;dbname=%s', getenv('DB_HOST'), getenv('DB_NAME')), + 'admin', + getenv('DB_PASSWORD') +); + +$rows = $pdo->query('SELECT NOW()')->fetchAll(); +print_r($rows); +``` +```server-go +package main + +import ( + "database/sql" + "fmt" + "os" + + _ "github.com/go-sql-driver/mysql" +) + +func main() { + db, err := sql.Open("mysql", os.Getenv("MYSQL_DSN")) + if err != nil { + panic(err) + } + defer db.Close() + + var now string + if err := db.QueryRow("SELECT NOW()").Scan(&now); err != nil { + panic(err) + } + fmt.Println(now) +} +``` +```server-rust +use sqlx::mysql::MySqlPoolOptions; +use sqlx::Row; + +#[tokio::main] +async fn main() -> Result<(), sqlx::Error> { + let url = std::env::var("DATABASE_URL").expect("DATABASE_URL"); + let pool = MySqlPoolOptions::new().connect(&url).await?; + + let row = sqlx::query("SELECT NOW() AS now").fetch_one(&pool).await?; + let now: chrono::DateTime<chrono::Utc> = row.get("now"); + println!("{now}"); + + Ok(()) +} +``` +{% /multicode %} + +Set the environment variables from the connection details on the database object. Once you can run a query, you can use any tool that talks the MySQL wire protocol: MySQL Workbench, DataGrip, your ORM of choice, your migration tool of choice. Appwrite gets out of the way. diff --git a/src/routes/docs/products/databases/mysql/high-availability/+page.markdoc b/src/routes/docs/products/databases/mysql/high-availability/+page.markdoc new file mode 100644 index 00000000000..9a644f549ed --- /dev/null +++ b/src/routes/docs/products/databases/mysql/high-availability/+page.markdoc @@ -0,0 +1,631 @@ +--- +layout: article +title: High availability +description: Run up to five read replicas with asynchronous, synchronous, or quorum replication and automatic failover for your MySQL database. +--- + +A single database instance is a single point of failure. High availability (HA) adds streaming replicas next to your primary: they replicate continuously, serve read traffic through the [connection pooler](/docs/products/databases/mysql/connection-pooling), and take over automatically when the primary becomes unhealthy. + +High availability requires a specification that runs on dedicated compute; the smallest specifications run on shared capacity and do not support replicas. + +# How it works {% #how-it-works %} + +Replicas receive changes from the primary through MySQL binary log replication. Each replica is a full copy of the database on its own compute. When the primary fails, the most caught-up replica is promoted to primary and the hostname is repointed, your application keeps connecting to the same host and port. + +# Replication modes {% #sync-modes %} + +| Mode | Behavior | Trade-off | +|----------|----------------------------------------------------------------------------------|--------------------------------------------------| +| `async` | The primary commits without waiting for replicas | Fastest writes; a failover can lose the last moments of writes | +| `sync` | The primary waits for one replica to confirm each commit | No data loss on single failure; slightly higher write latency | +| `quorum` | The primary waits for a majority of replicas to confirm each commit | Strongest durability; highest write latency | + +`async` is the default. For production workloads that cannot lose acknowledged writes, use `sync` with at least two replicas. + +# Enable high availability {% #enable %} + +Set the replica count and replication mode on the database through the API: + +{% multicode %} +```server-nodejs +import { Client, Mysql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.update({ + databaseId: '<DATABASE_ID>', + replicas: 2, + syncMode: 'sync', +}); +``` +```server-deno +import { Client, Mysql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.update({ + databaseId: '<DATABASE_ID>', + replicas: 2, + syncMode: 'sync', +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Mysql; + +$client = (new Client()) + ->setEndpoint('https://cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$mysql = new Mysql($client); + +$mysql->update( + databaseId: '<DATABASE_ID>', + replicas: 2, + syncMode: 'sync', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.mysql import Mysql + +client = Client() +client.set_endpoint('https://cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +mysql = Mysql(client) + +mysql.update( + database_id='<DATABASE_ID>', + replicas=2, + sync_mode='sync', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +mysql = Mysql.new(client) + +mysql.update( + database_id: '<DATABASE_ID>', + replicas: 2, + sync_mode: 'sync', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Mysql mysql = new Mysql(client); + +await mysql.Update( + databaseId: "<DATABASE_ID>", + replicas: 2, + syncMode: "sync" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Mysql mysql = Mysql(client); + +await mysql.update( + databaseId: '<DATABASE_ID>', + replicas: 2, + syncMode: 'sync', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Mysql + +val client = Client() + .setEndpoint("https://cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val mysql = Mysql(client) + +mysql.update( + databaseId = "<DATABASE_ID>", + replicas = 2, + syncMode = "sync", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let mysql = Mysql(client) + +_ = try await mysql.update( + databaseId: "<DATABASE_ID>", + replicas: 2, + syncMode: "sync" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/mysql" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewMysql(client) + + _, err := service.Update( + "<DATABASE_ID>", + mysql.WithUpdateReplicas(2), + mysql.WithUpdateSyncMode("sync"), + ) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::mysql::Mysql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let mysql = Mysql::new(&client); + + mysql.update("<DATABASE_ID>", None, None, None, Some(2), Some("sync"), None, None, None, None, None, None, None, None, None, None, None, None, None, None, None).await?; + + Ok(()) +} +``` +```bash +curl -X PATCH \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <YOUR_API_KEY>" \ + -H "Content-Type: application/json" \ + -d '{ + "replicas": 2, + "syncMode": "sync" + }' \ + https://cloud.appwrite.io/v1/mysql/<DATABASE_ID> +``` +{% /multicode %} + +Adding replicas provisions them online; the primary keeps serving traffic while each replica seeds from a snapshot and catches up. Setting `replicas` back to `0` disables HA. + +# Check replication status {% #status %} + +You can check each replica's role, health, and replication lag: + +{% multicode %} +```server-nodejs +import { Client, Mysql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +const replicas = await mysql.getReplicas({ + databaseId: '<DATABASE_ID>', +}); +``` +```server-deno +import { Client, Mysql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +const replicas = await mysql.getReplicas({ + databaseId: '<DATABASE_ID>', +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Mysql; + +$client = (new Client()) + ->setEndpoint('https://cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$mysql = new Mysql($client); + +$replicas = $mysql->getReplicas( + databaseId: '<DATABASE_ID>', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.mysql import Mysql + +client = Client() +client.set_endpoint('https://cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +mysql = Mysql(client) + +replicas = mysql.get_replicas( + database_id='<DATABASE_ID>', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +mysql = Mysql.new(client) + +replicas = mysql.get_replicas( + database_id: '<DATABASE_ID>', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Mysql mysql = new Mysql(client); + +var replicas = await mysql.GetReplicas( + databaseId: "<DATABASE_ID>" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Mysql mysql = Mysql(client); + +final replicas = await mysql.getReplicas( + databaseId: '<DATABASE_ID>', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Mysql + +val client = Client() + .setEndpoint("https://cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val mysql = Mysql(client) + +val replicas = mysql.getReplicas( + databaseId = "<DATABASE_ID>", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let mysql = Mysql(client) + +let replicas = try await mysql.getReplicas( + databaseId: "<DATABASE_ID>" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewMysql(client) + + result, err := service.GetReplicas("<DATABASE_ID>") + if err != nil { + panic(err) + } + _ = result +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::mysql::Mysql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let mysql = Mysql::new(&client); + + let replicas = mysql.get_replicas("<DATABASE_ID>").await?; + + Ok(()) +} +``` +```bash +curl -X GET \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <YOUR_API_KEY>" \ + https://cloud.appwrite.io/v1/mysql/<DATABASE_ID>/replicas +``` +{% /multicode %} + +# Automatic failover {% #automatic-failover %} + +Appwrite continuously health-checks the primary. When it becomes unresponsive, the platform promotes the replica with the least replication lag, repoints the database hostname, and marks the old primary for replacement. Your application reconnects to the same hostname; a well-configured driver pool retries and recovers without intervention. + +With `async` replication, writes that had not yet reached the promoted replica are lost in a failover. Use `sync` or `quorum` if that is unacceptable. + +# Manual failover {% #manual-failover %} + +Trigger a failover yourself, for example to test your application's recovery behavior. Optionally pass `targetReplicaId` to promote a specific replica. + +{% multicode %} +```server-nodejs +import { Client, Mysql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.createFailover({ + databaseId: '<DATABASE_ID>', +}); +``` +```server-deno +import { Client, Mysql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.createFailover({ + databaseId: '<DATABASE_ID>', +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Mysql; + +$client = (new Client()) + ->setEndpoint('https://cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$mysql = new Mysql($client); + +$mysql->createFailover( + databaseId: '<DATABASE_ID>', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.mysql import Mysql + +client = Client() +client.set_endpoint('https://cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +mysql = Mysql(client) + +mysql.create_failover( + database_id='<DATABASE_ID>', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +mysql = Mysql.new(client) + +mysql.create_failover( + database_id: '<DATABASE_ID>', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Mysql mysql = new Mysql(client); + +await mysql.CreateFailover( + databaseId: "<DATABASE_ID>" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Mysql mysql = Mysql(client); + +await mysql.createFailover( + databaseId: '<DATABASE_ID>', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Mysql + +val client = Client() + .setEndpoint("https://cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val mysql = Mysql(client) + +mysql.createFailover( + databaseId = "<DATABASE_ID>", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let mysql = Mysql(client) + +_ = try await mysql.createFailover( + databaseId: "<DATABASE_ID>" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewMysql(client) + + _, err := service.CreateFailover("<DATABASE_ID>") + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::mysql::Mysql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let mysql = Mysql::new(&client); + + mysql.create_failover("<DATABASE_ID>", None).await?; + + Ok(()) +} +``` +```bash +curl -X POST \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <YOUR_API_KEY>" \ + https://cloud.appwrite.io/v1/mysql/<DATABASE_ID>/failovers +``` +{% /multicode %} + +# Reading from replicas {% #reads %} + +Replicas serve read traffic when [read/write splitting](/docs/products/databases/mysql/connection-pooling#read-write-splitting) is enabled on the connection pooler. With `async` replication, a read that immediately follows a write can return stale data. Route reads that must see the latest write to the primary, or use `sync` replication. + +# Limits and billing {% #limits %} + +- Up to 5 replicas per database; the maximum depends on your plan. +- Each replica runs on the same specification as the primary and is billed as an add-on. See [pricing](/pricing). +- Replicas live in the same region as the primary. diff --git a/src/routes/docs/products/databases/mysql/integrations/auth-js/+page.markdoc b/src/routes/docs/products/databases/mysql/integrations/auth-js/+page.markdoc new file mode 100644 index 00000000000..e22e5c24910 --- /dev/null +++ b/src/routes/docs/products/databases/mysql/integrations/auth-js/+page.markdoc @@ -0,0 +1,222 @@ +--- +layout: article +title: Auth.js +description: Use an Appwrite native MySQL database as the backing store for Auth.js (NextAuth.js). Persist users, accounts, and sessions through the Prisma adapter. +--- + +[Auth.js](https://authjs.dev/) (formerly NextAuth.js) persists users, accounts, sessions, and verification tokens through a database adapter. When you configure an adapter, those records live in your database, which makes database sessions, account linking, and email sign-in possible. An Appwrite [native MySQL database](/docs/products/databases/mysql) is a standard MySQL engine, so Auth.js works through the same ORM adapters you use with other MySQL databases. + +{% info title="Before you start" %} +You'll need a native MySQL database in a `ready` state and its credentials. See [native MySQL databases](/docs/products/databases/mysql) to create one with the create-database wizard, then use [Connections](/docs/products/databases/mysql/connections#credentials) to read the hostname, password, database name, and connection string with `mysql.get()`. +{% /info %} + +# Choose an adapter {% #adapter %} + +Auth.js talks to your database through an adapter. For a native MySQL database, use the adapter that matches your ORM: + +- **Prisma** through [`@auth/prisma-adapter`](https://authjs.dev/getting-started/adapters/prisma). This page shows the Prisma setup with Prisma's MySQL provider and `@prisma/adapter-mariadb`. +- **Drizzle** through [`@auth/drizzle-adapter`](https://authjs.dev/getting-started/adapters/drizzle). Use Drizzle's MySQL schema and a `mysql2` database instance, then pass `DrizzleAdapter(db)` to Auth.js. See the [Drizzle guide](/docs/products/databases/mysql/integrations/drizzle) for the MySQL driver and migration setup. + +# Install packages {% #install %} + +Install Auth.js, the Prisma adapter, Prisma Client, Prisma's MySQL driver adapter, and the MariaDB driver used by the adapter: + +```bash +npm install next-auth@beta @auth/prisma-adapter @prisma/client @prisma/adapter-mariadb mariadb dotenv +npm install -D prisma typescript tsx @types/node +``` + +# Set the connection strings {% #connection-strings %} + +Store the Appwrite connection string in environment variables and do not commit it. Prisma uses `sslaccept=strict` for TLS with certificate verification on MySQL connections: + +```env +DATABASE_URL="mysql://admin:<password>@db-<hash>.<region>.appwrite.center:3306/<database>?sslaccept=strict" +DIRECT_URL="mysql://admin:<password>@db-<hash>.<region>.appwrite.center:3306/<database>?sslaccept=strict" + +# Runtime pooler, if your database specification includes connection pooling +# DATABASE_URL="mysql://admin:<password>@db-<hash>.<region>.appwrite.center:6033/<database>?sslaccept=strict" +``` + +`DATABASE_URL` is the runtime connection used by Prisma Client. `DIRECT_URL` is the direct MySQL connection used by Prisma CLI commands for migrations and introspection. Keep migrations on port `3306`; if you use the [connection pooler](/docs/products/databases/mysql/connection-pooling) for runtime traffic, only move `DATABASE_URL` to port `6033`. + +# Create the adapter schema {% #schema %} + +Auth.js expects four Prisma models: `User`, `Account`, `Session`, and `VerificationToken`. Add them to `prisma/schema.prisma` with the MySQL provider. The table names below use an `auth_js_` prefix so they stay separate from your application tables. + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" +} + +datasource db { + provider = "mysql" +} + +model User { + id String @id @default(cuid()) + name String? + email String? @unique + emailVerified DateTime? + image String? + accounts Account[] + sessions Session[] + + @@map("auth_js_users") +} + +model Account { + id String @id @default(cuid()) + userId String + type String + provider String + providerAccountId String + refresh_token String? @db.Text + access_token String? @db.Text + expires_at Int? + token_type String? + scope String? + id_token String? @db.Text + session_state String? + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([provider, providerAccountId]) + @@map("auth_js_accounts") +} + +model Session { + id String @id @default(cuid()) + sessionToken String @unique + userId String + expires DateTime + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@map("auth_js_sessions") +} + +model VerificationToken { + identifier String + token String + expires DateTime + + @@unique([identifier, token]) + @@map("auth_js_verification_tokens") +} +``` + +Configure Prisma CLI commands in `prisma.config.ts`. The CLI uses `DIRECT_URL` because migrations need a direct MySQL session. + +```ts +import 'dotenv/config'; +import { defineConfig, env } from 'prisma/config'; + +export default defineConfig({ + schema: 'prisma/schema.prisma', + migrations: { + path: 'prisma/migrations' + }, + datasource: { + url: env('DIRECT_URL') + } +}); +``` + +# Run the migration {% #migrate %} + +Create a migration file from the Auth.js schema: + +```bash +mkdir -p prisma/migrations/20260708160000_authjs_init +npx prisma migrate diff --from-empty --to-schema prisma/schema.prisma --script --output prisma/migrations/20260708160000_authjs_init/migration.sql +``` + +Apply committed migrations through the direct MySQL connection: + +```bash +npx prisma migrate deploy +``` + +Generate Prisma Client after you install dependencies or change `prisma/schema.prisma`: + +```bash +npx prisma generate +``` + +Prisma's `migrate dev` command uses a shadow database to detect schema drift. Use it against a local MySQL database, a separate Appwrite database, or an Appwrite branch, then apply committed migrations to this database with `migrate deploy`. + +# Wire the adapter into Auth.js {% #config %} + +Create one Prisma Client with Prisma's MySQL driver adapter: + +```ts +import 'dotenv/config'; +import { PrismaMariaDb } from '@prisma/adapter-mariadb'; +import { PrismaClient } from '../generated/prisma/client'; + +const connectionString = process.env.DATABASE_URL; +if (!connectionString) throw new Error('DATABASE_URL is required'); + +const adapter = new PrismaMariaDb(connectionString); + +export const prisma = new PrismaClient({ adapter }); +``` + +Pass the Prisma Client to your Auth.js config through the `adapter` key: + +```ts +import NextAuth from 'next-auth'; +import { PrismaAdapter } from '@auth/prisma-adapter'; +import { prisma } from './prisma'; + +export const { handlers, auth, signIn, signOut } = NextAuth({ + adapter: PrismaAdapter(prisma), + session: { strategy: 'database' }, + providers: [ + // your providers, e.g. GitHub, Google, Resend + ] +}); +``` + +# Database vs JWT sessions {% #sessions %} + +Auth.js has two session strategies: + +- **`database`**: a session row is written to the `Session` model and only an opaque session ID is stored in an `HttpOnly` cookie. Each request looks the session up in the native MySQL database, and sessions can be revoked server-side. +- **`jwt`**: session state lives in a signed cookie, and the database is not read on the session path. + +When you set `strategy: "database"`, keep the `Session` model in your Prisma schema. With `strategy: "jwt"`, the adapter still persists users and linked accounts, so account linking and user management continue to use the database. + +# Pool connections from serverless {% #pooling %} + +On serverless and edge platforms, each running instance opens its own database connections. On specifications that include the [connection pooler](/docs/products/databases/mysql/connection-pooling), route runtime traffic through port `6033` by setting `DATABASE_URL` to the pooler URL while keeping `DIRECT_URL` on port `3306`. + +The pooler defaults to transaction mode, which gives the highest connection multiplexing. Transaction mode does not keep a backend connection across statements, so session-level features such as user variables, temporary tables, and session-scoped prepared statements need session mode or a direct connection. + +# Use a branch for previews {% #branches %} + +[Branches](/docs/products/databases/mysql/branches) are isolated copies of a database with their own hostname and connection string. They are useful for pull-request previews and integration-test jobs that sign users in and out against throwaway data: + +1. Create a branch from the API and read its `connectionString`. +2. Export it as `DIRECT_URL`, and use the pooled variant as `DATABASE_URL` if the branch has the pooler enabled. +3. Run `npx prisma migrate deploy` and your auth flow against the branch. +4. Delete the branch when the job finishes. + +Because a branch starts from a storage snapshot, the Auth.js tables and data match the source database at branch time, so preview sign-ins behave like production without touching it. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/mysql/integrations/prisma" title="Prisma" %} +Datasource, driver adapter, and migrations for Prisma. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/integrations/drizzle" title="Drizzle" %} +Driver connection, schema definitions, and drizzle-kit migrations for MySQL. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/connection-pooling" title="Connection pooler" %} +Pool modes, ports, and serverless connection handling. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/integrations/better-auth" title="Better Auth" %} +The same pattern for Better Auth on a native MySQL database. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/mysql/integrations/better-auth/+page.markdoc b/src/routes/docs/products/databases/mysql/integrations/better-auth/+page.markdoc new file mode 100644 index 00000000000..cc9ffe323ad --- /dev/null +++ b/src/routes/docs/products/databases/mysql/integrations/better-auth/+page.markdoc @@ -0,0 +1,139 @@ +--- +layout: article +title: Better Auth +description: Use an Appwrite native MySQL database as the database for Better Auth. Configure mysql2, run schema generation and migrations over a direct connection, and store users and sessions in MySQL. +--- + +[Better Auth](https://www.better-auth.com/) is a framework-agnostic authentication library for TypeScript that stores users, sessions, accounts, and verification records in your database. An Appwrite [native MySQL database](/docs/products/databases/mysql) gives Better Auth a standard MySQL engine, so you can use `mysql2`, run the Better Auth CLI, and keep auth data in your Appwrite project. + +{% info title="Before you start" %} +You'll need a native MySQL database in a `ready` state and its credentials. See [native MySQL databases](/docs/products/databases/mysql) to create one and [Connections](/docs/products/databases/mysql/connections) to retrieve the connection string. The primary user is `admin`, and the database name is generated per database. +{% /info %} + +# Set the connection strings {% #connection-strings %} + +Better Auth uses one database URL at runtime and another for schema work. Use the pooler URL for application traffic when your specification includes the pooler, and use the direct engine URL for schema generation and migrations. + +```env +# Runtime: pooled, transaction mode +DATABASE_URL="mysql://admin:<password>@db-<hash>.<region>.appwrite.center:6033/<database>" + +# Schema generation and migrations: direct connection to the engine +DIRECT_URL="mysql://admin:<password>@db-<hash>.<region>.appwrite.center:3306/<database>" + +MYSQL_SSL="true" +BETTER_AUTH_URL="https://example.com" +BETTER_AUTH_SECRET="replace-with-a-long-random-secret" +``` + +The MySQL pooler runs on port `6033`, and the engine runs on port `3306`. The smallest shared-capacity specifications do not include the pooler, so use the direct URL for runtime traffic on those specifications. Connections on Appwrite Cloud are encrypted with TLS. The `MYSQL_SSL` variable below lets local development use an unencrypted port-forward while Cloud uses TLS verification. See [Connections](/docs/products/databases/mysql/connections) for connection fields and [Connection pooling](/docs/products/databases/mysql/connection-pooling#modes) for pool modes. + +# Install Better Auth and mysql2 {% #install %} + +Install Better Auth with the MySQL driver: + +```bash +npm install better-auth mysql2 +``` + +# Configure Better Auth {% #configure %} + +Pass a `mysql2/promise` pool to Better Auth. Better Auth drives that pool through its built-in Kysely adapter, so the Better Auth CLI can generate and apply the schema for you. + +The example below gives Better Auth tables a `better_auth_` prefix. Keep those model names if you want the auth tables grouped together, or choose names that match your schema conventions. + +```ts +import { betterAuth } from 'better-auth'; +import { createPool } from 'mysql2/promise'; + +if (!process.env.DATABASE_URL) { + throw new Error('DATABASE_URL is required'); +} + +const ssl = + process.env.MYSQL_SSL === 'true' + ? { rejectUnauthorized: true } + : undefined; + +export const auth = betterAuth({ + baseURL: process.env.BETTER_AUTH_URL, + secret: process.env.BETTER_AUTH_SECRET, + database: createPool({ + uri: process.env.DATABASE_URL, + timezone: 'Z', + ssl + }), + emailAndPassword: { enabled: true }, + user: { modelName: 'better_auth_user' }, + session: { modelName: 'better_auth_session' }, + account: { modelName: 'better_auth_account' }, + verification: { modelName: 'better_auth_verification' } +}); +``` + +`timezone: 'Z'` keeps timestamp values consistent between your application and MySQL. Leave mysql2's default `FOUND_ROWS` behavior enabled, because Better Auth relies on matched-row counts for some updates. + +# Generate and migrate the schema {% #migrate %} + +The [Better Auth CLI](https://www.better-auth.com/docs/concepts/cli) reads your `auth` config and creates the tables it needs. Run schema commands with the direct URL so DDL runs over a session connection: + +```bash +DATABASE_URL="$DIRECT_URL" npx auth@latest generate --config ./auth.ts --yes +``` + +Then apply the schema: + +```bash +DATABASE_URL="$DIRECT_URL" npx auth@latest migrate --config ./auth.ts --yes +``` + +`migrate` is available for Better Auth's built-in Kysely adapter. If you use the Better Auth Prisma or Drizzle adapter, run `generate` to produce that ORM's schema, then apply it with the ORM migration tool over `DIRECT_URL`. See the [Prisma](/docs/products/databases/mysql/integrations/prisma) and [Drizzle](/docs/products/databases/mysql/integrations/drizzle) guides for their MySQL configuration. + +# Create a user {% #example %} + +After the schema exists, mount Better Auth's handler in your framework and call the API methods from your server code: + +```ts +import { auth } from './auth'; + +const result = await auth.api.signUpEmail({ + body: { + email: 'ada@example.com', + password: 'a-strong-password', + name: 'Ada Lovelace' + } +}); + +console.log(result.user.id); +``` + +On a long-running server, create the `auth` instance once at module scope and reuse it. On serverless, keep a single instance per module scope so warm invocations reuse the mysql2 pool. + +# Pooling and session state {% #pooling %} + +Auth workloads, including sign-up, login, and session lookups, are short database transactions. Pointing runtime traffic at port `6033` lets many application instances share a smaller backend connection pool when the pooler is available. + +The pooler's transaction mode does not keep a backend connection across statements. Better Auth's built-in MySQL path works through Kysely and mysql2 text queries, which fit transaction pooling. If your surrounding application code uses session-bound features such as server-side prepared statements, user variables, or temporary tables, use the direct port or switch the pooler to session mode. See [pooler modes](/docs/products/databases/mysql/connection-pooling#modes) for the trade-offs. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/mysql/connections" title="Connections" %} +Retrieve credentials and rotate the primary password. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/connection-pooling" title="Connection pooling" %} +Pool modes, ports, and read/write splitting for serverless workloads. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/integrations/prisma" title="Prisma" %} +Configure Prisma with pooled and direct MySQL URLs. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/integrations/drizzle" title="Drizzle" %} +Configure Drizzle ORM with mysql2 and Drizzle Kit migrations. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/branches" title="Branches" %} +Ephemeral database copies for preview environments and CI. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/integrations/auth-js" title="Auth.js" %} +Use a native MySQL database as the Auth.js database. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/mysql/integrations/dbt/+page.markdoc b/src/routes/docs/products/databases/mysql/integrations/dbt/+page.markdoc new file mode 100644 index 00000000000..ec03efb9275 --- /dev/null +++ b/src/routes/docs/products/databases/mysql/integrations/dbt/+page.markdoc @@ -0,0 +1,113 @@ +--- +layout: article +title: dbt +description: Run dbt Core transformations against an Appwrite native MySQL database with the community dbt-mysql adapter. +--- + +Use dbt Core with an Appwrite native MySQL database through the community [`dbt-mysql`](https://docs.getdbt.com/docs/local/connect-data-platform/mysql-setup) adapter. Appwrite exposes standard MySQL on port `3306`, so dbt connects with the same host, username, password, and database name you use with other MySQL clients. + +dbt compiles your models into `CREATE TABLE` and `CREATE VIEW` statements, then runs them in dependency order to build transformed tables and views inside the MySQL database you configure. + +{% info title="Adapter support" %} +dbt Labs lists MySQL as a community adapter. The published [`dbt-mysql`](https://pypi.org/project/dbt-mysql/) package is experimental and is not dbt-supported, so pin and test the adapter version you deploy. +{% /info %} + +{% info title="Before you start" %} +You'll need a native MySQL database in a `ready` state and its credentials. See [MySQL](/docs/products/databases/mysql) to create one and [Connections](/docs/products/databases/mysql/connections) to retrieve them. The primary user is `admin`, the database name is generated for each database, and the engine listens on port `3306`. +{% /info %} + +# Install the adapter {% #install %} + +Install dbt Core and the MySQL adapter in the Python environment where you run dbt: + +```bash +python -m pip install dbt-mysql +``` + +# Choose where dbt builds {% #build-location %} + +In MySQL, dbt's `schema` setting is the MySQL database name. Set `schema` to the generated database name from your Appwrite connection details. + +Use a naming convention such as a `dbt_` prefix for dbt models and seeds, for example `dbt_orders` and `dbt_customer_revenue`, to keep transformed tables separate from application tables in the same database. For stricter isolation, use another native MySQL database or validate changes against a branch. + +# Configure profiles.yml {% #profiles %} + +dbt reads connection details from `~/.dbt/profiles.yml` or the directory in `DBT_PROFILES_DIR`. Configure a `mysql` target against the database host on port `3306`: + +```yaml +analytics: + target: dev + outputs: + dev: + type: mysql + server: db-<hash>.<region>.appwrite.center + port: 3306 + username: admin + password: "{{ env_var('APPWRITE_DB_PASSWORD') }}" + schema: <database> + threads: 4 + charset: utf8mb4 + collation: utf8mb4_0900_ai_ci +``` + +The `dbt-mysql` adapter uses `server`, `username`, `password`, and `schema`. Read the password from an environment variable with `env_var` rather than committing it. The `schema` value is the generated MySQL database name from your Appwrite connection string. + +# Test the connection {% #debug %} + +`dbt debug` validates your project files and opens a connection to confirm the credentials and host are correct: + +```bash +dbt debug +``` + +A successful run reports `Connection test: OK connection ok`. If it fails, recheck the host, port, password, and database name. + +# Run transformations {% #run %} + +Build your models into the configured MySQL database: + +```bash +dbt run +``` + +`dbt run` executes models only, materializing each as a table or view. Use `dbt build` to run models, tests, seeds, and snapshots together in DAG order: + +```bash +dbt build +``` + +# Choose the right connection {% #connection %} + +dbt opens one database connection per thread and runs DDL during model builds. Point dbt at a connection that preserves the session for each thread: + +- **Direct engine port (`3306`)** is the recommended target. Each thread gets a backend session with the DDL privileges dbt needs. This is what the `profiles.yml` above uses. +- **Session-mode pooler** can work when the pooler is available for your database specification. Connect on the pooler port (`6033`) and use `session` mode. + +Avoid the transaction-mode pooler for dbt. Transaction mode returns the backend connection to the pool after each transaction, which can break workflows that rely on session state. See the [connection pooling](/docs/products/databases/mysql/connection-pooling#modes) page for the mode trade-offs. + +# Size threads to your connection budget {% #threads %} + +The `threads` setting controls how many models dbt builds in parallel, and dbt opens one connection per thread. A `threads: 8` run can hold up to eight backend connections at once. dbt also respects model dependencies, so it never runs more models concurrently than your DAG allows. + +Pick a `threads` value that fits the connection budget for your database specification, and leave headroom for application traffic sharing the same database. Start at `4` and raise it only while connections stay within budget. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/mysql/connections" title="Connections" %} +Retrieve credentials and rotate the primary password. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/connection-pooling" title="Connection pooler" %} +Pool modes and ports. Use session mode for dbt. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/branches" title="Branches" %} +Ephemeral database copies for CI and preview environments. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/network-security" title="Network" %} +TLS behavior, certificate verification, and IP allowlists. +{% /cards_item %} +{% /cards %} + +{% arrow_link href="https://docs.getdbt.com/docs/local/connect-data-platform/mysql-setup" %} +dbt MySQL adapter reference +{% /arrow_link %} diff --git a/src/routes/docs/products/databases/mysql/integrations/django/+page.markdoc b/src/routes/docs/products/databases/mysql/integrations/django/+page.markdoc new file mode 100644 index 00000000000..419751ce299 --- /dev/null +++ b/src/routes/docs/products/databases/mysql/integrations/django/+page.markdoc @@ -0,0 +1,151 @@ +--- +layout: article +title: Django +description: Use Django's ORM with an Appwrite native MySQL database. Configure the DATABASES setting, run migrations against the direct MySQL port, and choose the right pooler mode for Django connections. +--- + +A native MySQL database is a standard MySQL engine, so Django's ORM works against it with no Appwrite-specific configuration. Point the `DATABASES` setting at the credentials from the [Connections](/docs/products/databases/mysql/connections) page, then use migrations, models, and the rest of Django as you would against any MySQL server. + +{% info title="Before you start" %} +You'll need a native MySQL database in a `ready` state and its credentials. See [native MySQL databases](/docs/products/databases/mysql) to create one with the create-database wizard, then use [`mysql.get()`](/docs/products/databases/mysql/connections#credentials) to read the hostname, port, username, password, and generated database name. The primary username is `admin`. +{% /info %} + +# Install a driver {% #driver %} + +Django talks to MySQL through a DB API driver. Django's current recommended driver is [mysqlclient](https://pypi.org/project/mysqlclient/): + +```bash +pip install mysqlclient +``` + +`mysqlclient` builds against MySQL client libraries. If installation fails, install the MySQL development headers and `pkg-config` for your operating system, then run the same command again. + +Django's MySQL backend uses the `ENGINE` value `django.db.backends.mysql`. + +# Configure `DATABASES` {% #databases %} + +In `settings.py`, point the `default` connection at your native MySQL database. Appwrite Cloud requires TLS on the public hostname, and the certificate is signed by a public certificate authority. Read every value from the environment so credentials stay out of source control: + +```python +import os + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.mysql", + "NAME": os.environ["DB_NAME"], + "USER": os.environ["DB_USER"], + "PASSWORD": os.environ["DB_PASSWORD"], + "HOST": os.environ["DB_HOST"], + "PORT": os.environ["DB_PORT"], + "OPTIONS": { + "charset": "utf8mb4", + "ssl_mode": os.environ.get("DB_SSL_MODE", "REQUIRED"), + }, + } +} +``` + +The MySQL backend passes `OPTIONS` to `mysqlclient`. `charset` keeps client encoding aligned with Django's UTF-8 expectations, and `ssl_mode` controls TLS for the MySQL connection. Use `REQUIRED` for encrypted Cloud connections. For full certificate and hostname verification, set `DB_SSL_MODE=VERIFY_IDENTITY`. See [Network security](/docs/products/databases/mysql/network-security) for TLS and IP allowlist guidance. + +Populate the environment with values from `mysql.get()` in the [API credentials flow](/docs/products/databases/mysql/connections#credentials): + +```env +DB_NAME=<database> +DB_USER=admin +DB_PASSWORD=<password> +DB_HOST=db-<hash>.<region>.appwrite.center +DB_PORT=3306 +DB_SSL_MODE=REQUIRED +``` + +Port `3306` is the direct MySQL port. Keep migrations on this port, and see [pooling](#pooling) below for when to add the pooler. + +# Run migrations {% #migrate %} + +Generate migrations from your models, then apply them against the direct MySQL port: + +```bash +python manage.py makemigrations +python manage.py migrate +``` + +`migrate` needs a session connection with DDL privileges. The primary `admin` user owns the database and can run schema changes. Always run `migrate` on the direct MySQL port, not through the transaction-mode pooler. + +# Define a model {% #model %} + +Models map to tables in your native MySQL database. Define one in an app's `models.py`: + +```python +from django.db import models + +class Article(models.Model): + title = models.CharField(max_length=200) + body = models.TextField() + created_at = models.DateTimeField(auto_now_add=True) + + class Meta: + db_table = "django_articles" + ordering = ["-created_at"] +``` + +Run `makemigrations` and `migrate` again to create the table, then query it through the ORM: + +```python +from blog.models import Article + +Article.objects.create(title="Hello", body="First post") + +recent = Article.objects.order_by("-created_at")[:10] +``` + +# Persistent connections and pooling {% #pooling %} + +By default Django opens a new connection per request (`CONN_MAX_AGE = 0`). On a long-running WSGI server (Gunicorn, uWSGI), you can reuse connections by raising it. Each worker thread keeps its own connection, so the database must allow at least as many connections as you run worker threads: + +```python +DATABASES["default"]["CONN_MAX_AGE"] = 60 +DATABASES["default"]["CONN_HEALTH_CHECKS"] = True +``` + +`CONN_HEALTH_CHECKS` revalidates a reused connection once per request, reducing errors after an engine restart. Do not enable persistent connections under the development server, because it spawns a thread per request and gains nothing. Keep persistent connections disabled under ASGI. + +{% info title="Persistent connections need a session" %} +A worker holding a connection across requests behaves like a long-lived session. Connect it to the direct MySQL port or the **session-mode** pooler. The default **transaction-mode** pooler can hand statements to different backend connections, which makes it a better fit for short-lived runtimes that open and close a connection per request or invocation. +{% /info %} + +If you front the database with the transaction-mode [connection pooler](/docs/products/databases/mysql/connection-pooling), point `HOST` and `PORT` at the MySQL pooler on port `6033` and keep `CONN_MAX_AGE = 0`: + +```env +DB_HOST=db-<hash>.<region>.appwrite.center +DB_PORT=6033 +``` + +Transaction mode does not preserve session state between statements. Use the direct port or **session mode** for migrations, temporary tables, user variables, named locks, and other session-scoped behavior. See the [pooler](/docs/products/databases/mysql/connection-pooling#modes) page for the trade-offs. + +# Use a branch for previews and CI {% #branches %} + +[Branches](/docs/products/databases/mysql/branches) are instant, isolated copies of a database with their own hostname and connection details. They're ideal for running migrations against throwaway data in a pull-request preview or an integration-test job: + +1. Create a branch from the API and read its connection details. +2. Export them as the `DB_*` environment variables your settings read. +3. Run `python manage.py migrate` and your test suite against the branch. +4. Delete the branch when the job finishes. + +Because a branch starts from a storage snapshot, its schema and data match the source database at branch time, so migrations run against realistic data without touching production. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/mysql/connections" title="Connections" %} +Retrieve credentials, rotate the password, and connect with common clients. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/connection-pooling" title="Connection pooler" %} +Pool modes, ports, and read/write splitting for serverless workloads. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/branches" title="Branches" %} +Ephemeral database copies for preview environments and CI. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/network-security" title="Network security" %} +TLS, certificate verification, mTLS, and IP allowlists. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/mysql/integrations/drivers/+page.markdoc b/src/routes/docs/products/databases/mysql/integrations/drivers/+page.markdoc new file mode 100644 index 00000000000..5312292c136 --- /dev/null +++ b/src/routes/docs/products/databases/mysql/integrations/drivers/+page.markdoc @@ -0,0 +1,225 @@ +--- +layout: article +title: Node.js drivers +description: Connect to an Appwrite native MySQL database from Node.js with mysql2 or MariaDB Connector/Node.js. Configure pools, TLS verification, serverless connection management, and troubleshooting. +--- + +A native MySQL database works with standard Node.js drivers that speak the MySQL wire protocol. The [Connections](/docs/products/databases/mysql/connections#drivers) page shows the smallest query. This guide covers driver pools, TLS verification, serverless connection management, and common connection errors. + +{% info title="Before you start" %} +You'll need a native MySQL database in a `ready` state and the connection values returned by the database object: host, port, username, password, and database name. Store the password in an environment variable and keep it out of source control. +{% /info %} + +# Raw driver, ORM, or SQL API? {% #choosing %} + +There are three common ways to query a native MySQL database from Node.js. Pick by workload: + +| Approach | Use it when | +|------------------------------|----------------------------------------------------------------------------------------------| +| **Raw driver** (this page) | You want a connection pool you control, hot-path queries, or a thin data layer with little overhead. | +| **ORM** ([Prisma](/docs/products/databases/mysql/integrations/prisma), [Drizzle](/docs/products/databases/mysql/integrations/drizzle)) | You want migrations, a typed schema, and query building. The ORM still uses a MySQL driver underneath. | +| **[SQL API](/docs/products/databases/mysql/quick-start#first-queries)** | You're on a runtime that cannot open TCP sockets, or you're scripting a query over HTTPS. | + +The rest of this page is for long-running or serverless Node.js servers that can open TCP connections. + +# mysql2 promise API {% #mysql2-promise %} + +Install mysql2 with `npm install mysql2`. For a long-running server, create one promise pool at module scope and reuse it for every request. The pool opens connections lazily up to `connectionLimit`. + +```js +import mysql from 'mysql2/promise'; +import process from 'node:process'; + +const pool = mysql.createPool({ + host: 'db-<hash>.<region>.appwrite.center', + port: 3306, + user: 'admin', + password: process.env.DB_PASSWORD, + database: '<database>', + ssl: { rejectUnauthorized: true }, + waitForConnections: true, + connectionLimit: 10, + idleTimeout: 30000, + enableKeepAlive: true, +}); + +await pool.query(` + CREATE TABLE IF NOT EXISTS drivers_events ( + id INT AUTO_INCREMENT PRIMARY KEY, + email VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) +`); + +const [result] = await pool.execute( + 'INSERT INTO drivers_events (email) VALUES (?)', + ['ada@example.com'] +); + +const [rows] = await pool.execute( + 'SELECT id, email FROM drivers_events WHERE id = ?', + [result.insertId] +); + +console.log(rows[0]); + +await pool.end(); +``` + +Use `execute` for parameterized statements on the direct MySQL port. mysql2 prepares these statements on the server and caches them per connection, which is a good fit for a stable application pool. + +# mysql2 callback API {% #mysql2-callback %} + +Use the callback API when your application already follows callback patterns. Keep the pool shared, and close it only during process shutdown. + +```js +import mysql from 'mysql2'; +import process from 'node:process'; + +const pool = mysql.createPool({ + host: 'db-<hash>.<region>.appwrite.center', + port: 3306, + user: 'admin', + password: process.env.DB_PASSWORD, + database: '<database>', + ssl: { rejectUnauthorized: true }, + waitForConnections: true, + connectionLimit: 10, + idleTimeout: 30000, + enableKeepAlive: true, +}); + +pool.query(` + CREATE TABLE IF NOT EXISTS drivers_events_callback ( + id INT AUTO_INCREMENT PRIMARY KEY, + email VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) +`, (createErr) => { + if (createErr) throw createErr; + + pool.execute( + 'INSERT INTO drivers_events_callback (email) VALUES (?)', + ['grace@example.com'], + (insertErr, result) => { + if (insertErr) throw insertErr; + + pool.execute( + 'SELECT id, email FROM drivers_events_callback WHERE id = ?', + [result.insertId], + (selectErr, rows) => { + if (selectErr) throw selectErr; + console.log(rows[0]); + pool.end(); + } + ); + } + ); +}); +``` + +# MariaDB Connector/Node.js {% #mariadb %} + +MariaDB Connector/Node.js is a maintained JavaScript driver for MariaDB and MySQL databases. Install it with `npm install mariadb`. Its default API is promise-based and it exposes a pool with `connectionLimit`. + +```js +import mariadb from 'mariadb'; +import process from 'node:process'; + +const pool = mariadb.createPool({ + host: 'db-<hash>.<region>.appwrite.center', + port: 3306, + user: 'admin', + password: process.env.DB_PASSWORD, + database: '<database>', + ssl: true, + connectionLimit: 10, + connectTimeout: 5000, +}); + +let connection; + +try { + connection = await pool.getConnection(); + + await connection.query(` + CREATE TABLE IF NOT EXISTS drivers_events_mariadb ( + id INT AUTO_INCREMENT PRIMARY KEY, + email VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + `); + + const result = await connection.query( + 'INSERT INTO drivers_events_mariadb (email) VALUES (?)', + ['lin@example.com'] + ); + + const rows = await connection.query( + 'SELECT id, email FROM drivers_events_mariadb WHERE id = ?', + [result.insertId] + ); + + console.log(rows[0]); +} finally { + if (connection) connection.release(); + await pool.end(); +} +``` + +The connector uses Node.js trusted root CAs when `ssl: true` is enabled. If your runtime image does not ship with a current trust store, pass a CA bundle with `ssl: { ca: fs.readFileSync('./ca-bundle.crt') }`. + +# TLS and CA verification {% #tls %} + +Connections on Appwrite Cloud are encrypted with TLS, terminated at the edge and forwarded to your database over the internal network. Use certificate verification in production: + +| Driver | TLS option | +|--------------------------|-------------------------------------------------| +| `mysql2` | `ssl: { rejectUnauthorized: true }` | +| `mariadb` | `ssl: true` | +| Custom CA bundle | Add `ca` inside the driver's `ssl` object | + +The database hostname uses a certificate signed by a public CA, so Node.js can verify it with the built-in trust store in standard runtimes. Use a custom CA bundle only for images that do not include one, or when your organization requires a pinned bundle. + +For mTLS, where the client also presents a certificate, see the [Network security](/docs/products/databases/mysql/network-security) page. Add the client `key` and `cert` alongside `ca` in the same `ssl` object. + +# Pool sizing {% #pool-sizing %} + +Pool sizing is a connection budget. Each connection in `connectionLimit`, across every running instance, counts against your specification's connection cap. A single app server with `connectionLimit: 10` is usually modest. Ten replicas with `connectionLimit: 50` each can open 500 connections and exhaust smaller specifications. + +Size each driver pool to the connection cap divided by replica count, then leave headroom for migrations, background workers, and admin tools. If you need more client concurrency than the engine can accept directly, route runtime traffic through the [connection pooler](/docs/products/databases/mysql/connection-pooling). + +# Serverless connection management {% #serverless %} + +On Lambda, Cloud Run, Vercel, and similar platforms, every cold start is a fresh instance with its own pool. Keep the pool at module scope so warm invocations reuse it, and keep each pool small, usually `connectionLimit: 1` or `2`. + +Appwrite's MySQL pooler is ProxySQL on port `6033`. It is designed for high fan-out runtime traffic and defaults to transaction mode. In transaction mode, use text queries with placeholders through `query`, and keep server-side prepared statements, temporary tables, user variables, and named locks on the direct port (`3306`) or a session-mode pooler. + +Run migrations, schema changes, and dump or restore tools on the direct port. These workflows rely on a stable session and DDL privileges. + +# Troubleshooting {% #troubleshooting %} + +| Symptom | Cause and fix | +|----------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------| +| `self-signed certificate` / `unable to verify the first certificate` | TLS is reaching the wrong host, or the runtime has a stale or incomplete CA bundle. Verify the database hostname and supply a current public CA bundle only when the runtime lacks one. | +| `ER_CON_COUNT_ERROR` / connection attempts rejected at the proxy | Aggregate pool size across all instances exceeds the database specification's connection cap. Lower each pool's `connectionLimit`, or use the [connection pooler](/docs/products/databases/mysql/connection-pooling) on port `6033` with small per-instance pools. | +| `Unknown prepared statement handler` or prepared statement errors on port `6033` | A prepared statement cache is being used through the transaction-mode pooler. Use `query` with placeholders for pooled runtime traffic, or use the direct port or [session mode](/docs/products/databases/mysql/connection-pooling#modes). | +| `ETIMEDOUT` / `connect ETIMEDOUT` | The database may be cold-starting on smaller specifications or blocked by an [IP allowlist](/docs/products/databases/mysql/network-security#ip-allowlist). Confirm your egress IP is allowed and raise `connectTimeout` to absorb cold starts. | +| Connections drop after a period of inactivity | The proxy closes idle connections after its network idle timeout. Keep mysql2 `idleTimeout` below that window, and enable keep-alive where the driver supports it. | + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/mysql/connections" title="Connections" %} +Retrieve credentials, rotate the password, and connect with common clients. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/connection-pooling" title="Connection pooler" %} +Pool modes, ports, and read/write splitting for serverless workloads. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/quick-start#first-queries" title="SQL API" %} +Run SQL over HTTPS with no TCP connection for edge runtimes and scripts. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/network-security" title="Network security" %} +TLS, certificate verification, mTLS, and IP allowlists. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/mysql/integrations/drizzle/+page.markdoc b/src/routes/docs/products/databases/mysql/integrations/drizzle/+page.markdoc new file mode 100644 index 00000000000..3f1caeb70de --- /dev/null +++ b/src/routes/docs/products/databases/mysql/integrations/drizzle/+page.markdoc @@ -0,0 +1,172 @@ +--- +layout: article +title: Drizzle +description: Use Drizzle ORM with an Appwrite native MySQL database. Configure mysql2, run migrations against the direct connection, and pool runtime traffic from serverless environments. +--- + +Appwrite's native MySQL database is a standard MySQL engine, so [Drizzle ORM](https://orm.drizzle.team/) works against it with no Appwrite-specific configuration. Point Drizzle's mysql2 driver at the connection string from the [Connections](/docs/products/databases/mysql/connections) page and use Drizzle Kit, the query builder, and the rest of the toolchain as you would against any MySQL server. + +{% info title="Before you start" %} +You'll need a native MySQL database in a `ready` state and its credentials. See [native MySQL databases](/docs/products/databases/mysql) to create one and [Connections](/docs/products/databases/mysql/connections) to retrieve the connection string. The primary user is `admin`, and the database name is generated per database. +{% /info %} + +# Set the connection string {% #connection-string %} + +Fetch the connection details with [`mysql.get()`](/docs/products/databases/mysql/connections#credentials), then put the returned connection string in your environment. Never commit it: + +```env +DATABASE_URL="mysql://admin:<password>@db-<hash>.<region>.appwrite.center:3306/<database>" +``` + +Connections on Appwrite Cloud are encrypted with TLS. If your runtime or mysql2 configuration does not infer TLS from the connection string, configure mysql2 to require TLS. See [Network security](/docs/products/databases/mysql/network-security) for TLS and network controls. + +# Install and configure the driver {% #driver %} + +Drizzle talks to MySQL through `mysql2`: + +```bash +npm install drizzle-orm mysql2 +npm install -D drizzle-kit +``` + +Import `drizzle` from `drizzle-orm/mysql2` and hand it the connection string: + +```ts +import { drizzle } from 'drizzle-orm/mysql2'; + +export const db = drizzle(process.env.DATABASE_URL!); +``` + +Define your tables in a schema file Drizzle Kit can read. Use the MySQL column builders from `drizzle-orm/mysql-core`: + +```ts +import { int, mysqlTable, timestamp, varchar } from 'drizzle-orm/mysql-core'; + +export const users = mysqlTable('users', { + id: int('id').primaryKey().autoincrement(), + email: varchar('email', { length: 255 }).notNull().unique(), + createdAt: timestamp('created_at').notNull().defaultNow() +}); +``` + +# Configure Drizzle Kit {% #config %} + +Drizzle Kit reads `drizzle.config.ts` for migrations and introspection. Set the `dialect`, point `schema` at your table definitions, and pass the connection string through `dbCredentials`: + +```ts +import { defineConfig } from 'drizzle-kit'; + +export default defineConfig({ + dialect: 'mysql', + schema: './src/schema.ts', + out: './drizzle', + dbCredentials: { + url: process.env.DATABASE_URL! + } +}); +``` + +# Run migrations {% #migrate %} + +Generate SQL migration files from your schema, then apply them: + +```bash +npx drizzle-kit generate +npx drizzle-kit migrate +``` + +`generate` diffs your schema against the last snapshot and writes a timestamped `.sql` file into the `out` directory. `migrate` applies any pending files to the database. Point Drizzle Kit at the direct engine port (`3306`), not the pooler, because migrations issue DDL that needs a session-level connection. The primary `admin` user owns the generated database and can run schema changes. + +For prototypes or preview databases where you do not need committed SQL migration files, Drizzle Kit can push the current schema directly: + +```bash +npx drizzle-kit push +``` + +To apply migrations from your application at startup instead of the CLI, use the mysql2 migrator: + +```ts +import { migrate } from 'drizzle-orm/mysql2/migrator'; +import { db } from './db'; + +await migrate(db, { migrationsFolder: './drizzle' }); +``` + +# Query with Drizzle {% #query %} + +Once the schema is migrated, use the query builder. The mysql2 dialect returns an insert result instead of the inserted row, so read the row back with the inserted ID: + +```ts +import { desc, eq } from 'drizzle-orm'; +import { db } from './db'; +import { users } from './schema'; + +const result = await db.insert(users).values({ email: 'ada@example.com' }); + +const [user] = await db.select().from(users).where(eq(users.id, Number(result[0].insertId))); + +const recent = await db + .select() + .from(users) + .orderBy(desc(users.createdAt)) + .limit(10); +``` + +On long-running servers, create the `db` instance once at module scope and reuse it. On serverless, keep a single instance per module scope so warm invocations reuse it, and use the pooler when your database specification includes it. + +# Pool connections from serverless {% #pooling %} + +Each running instance opens its own connections to the engine. On serverless and edge platforms such as Vercel, Netlify, and Cloudflare, short-lived instances can fan out into more backend connections than the engine allows. If your database specification includes the [connection pooler](/docs/products/databases/mysql/connection-pooling), route runtime traffic through it by connecting on port `6033` on the same hostname. + +Keep `drizzle.config.ts` and the startup migrator pointed at `DIRECT_URL` so DDL still runs over a session-level connection: + +```env +# Runtime: pooler port +DATABASE_URL="mysql://admin:<password>@db-<hash>.<region>.appwrite.center:6033/<database>" + +# Migrations & introspection: direct connection to the engine +DIRECT_URL="mysql://admin:<password>@db-<hash>.<region>.appwrite.center:3306/<database>" +``` + +```ts +import { defineConfig } from 'drizzle-kit'; + +export default defineConfig({ + dialect: 'mysql', + schema: './src/schema.ts', + out: './drizzle', + dbCredentials: { + url: process.env.DIRECT_URL! + } +}); +``` + +The pooler defaults to transaction mode, which does not keep a backend connection across statements. Drizzle's regular mysql2 query builder sends text queries and works with transaction pooling, but session-level features such as explicit prepared queries, user variables, and temporary tables need the direct port or a session-mode pooler. See the [pooler modes](/docs/products/databases/mysql/connection-pooling#modes) page for the trade-offs. + +# Use a branch for previews and CI {% #branches %} + +[Branches](/docs/products/databases/mysql/branches) are isolated copies of a database with their own hostname and connection string. They're ideal for running migrations against throwaway data in a pull-request preview or an integration-test job: + +1. Create a branch from the API and read its `connectionString`. +2. Export it as `DIRECT_URL` and `DATABASE_URL` for the preview or CI job. +3. Run `drizzle-kit migrate` or `drizzle-kit push`, then run your test suite against the branch. +4. Delete the branch when the job finishes. + +Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations run against representative data without touching production. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/mysql/connections" title="Connections" %} +Retrieve credentials and rotate the primary password. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/connection-pooling" title="Connection pooler" %} +Pool modes, ports, and read/write splitting for serverless workloads. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/branches" title="Branches" %} +Ephemeral database copies for preview environments and CI. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/network-security" title="Network security" %} +TLS and network controls for native MySQL databases. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/mysql/integrations/ef-core/+page.markdoc b/src/routes/docs/products/databases/mysql/integrations/ef-core/+page.markdoc new file mode 100644 index 00000000000..2e74ff84805 --- /dev/null +++ b/src/routes/docs/products/databases/mysql/integrations/ef-core/+page.markdoc @@ -0,0 +1,178 @@ +--- +layout: article +title: EF Core +description: Use Entity Framework Core with an Appwrite native MySQL database. Configure the MySQL provider, run migrations against the direct MySQL host, and rely on the provider's connection pool from an ASP.NET server. +--- + +A native MySQL database is a standard MySQL engine, so [Entity Framework Core](https://learn.microsoft.com/ef/core/) works with it through a MySQL EF Core provider. Point the provider at the connection details from the [Connections](/docs/products/databases/mysql/connections) page, then use `DbContext`, migrations, and LINQ queries as you would with any MySQL server. + +{% info title="Before you start" %} +You'll need a native MySQL database in a `ready` state and its credentials. See [native MySQL databases](/docs/products/databases/mysql) to create one and [Connections](/docs/products/databases/mysql/connections) to retrieve the connection details. +{% /info %} + +# Install the provider {% #install %} + +Add Oracle's EF Core provider for MySQL, the EF Core design-time package, and the `dotnet-ef` CLI tool: + +```bash +dotnet add package MySql.EntityFrameworkCore +dotnet add package Microsoft.EntityFrameworkCore.Design +dotnet new tool-manifest +dotnet tool install dotnet-ef +``` + +Keep the MySQL provider and EF Core design package on the same major version as your app's EF Core packages. + +# Set the connection string {% #connection-string %} + +ADO.NET providers use key/value connection strings rather than a URL. Store the connection string in `appsettings.json` under `ConnectionStrings`, and keep the password out of source control with [user secrets](https://learn.microsoft.com/aspnet/core/security/app-secrets) or environment variables: + +```json +{ + "ConnectionStrings": { + "Default": "Server=db-<hash>.<region>.appwrite.center;Port=3306;Database=<database>;User=admin;Password=<password>;SslMode=Required" + } +} +``` + +The Appwrite MySQL host accepts TLS connections on port `3306`. `SslMode=Required` encrypts the connection. For full certificate and hostname verification, use `SslMode=VerifyFull`: + +```text +Server=db-<hash>.<region>.appwrite.center;Port=3306;Database=<database>;User=admin;Password=<password>;SslMode=VerifyFull +``` + +# Configure the DbContext {% #dbcontext %} + +Define your model and `DbContext`. The model is ordinary EF Core code, and the table name in this example is prefixed so it is easy to identify the objects created by the guide: + +```csharp +using Microsoft.EntityFrameworkCore; + +public class User +{ + public int Id { get; set; } + public string Email { get; set; } = string.Empty; + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; +} + +public class AppDbContext : DbContext +{ + public AppDbContext(DbContextOptions<AppDbContext> options) + : base(options) + { + } + + public DbSet<User> Users => Set<User>(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity<User>(entity => + { + entity.ToTable("ef_core_users"); + entity.HasIndex(user => user.Email).IsUnique(); + entity.Property(user => user.Email).HasMaxLength(255).IsRequired(); + entity.Property(user => user.CreatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP(6)"); + }); + } +} +``` + +# Register the provider {% #provider %} + +Register the `DbContext` in `Program.cs`, reading the connection string with `GetConnectionString`. Use `UseMySQL(...)` for Oracle's MySQL EF Core provider: + +```csharp +using Microsoft.EntityFrameworkCore; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddDbContext<AppDbContext>(options => + options.UseMySQL( + builder.Configuration.GetConnectionString("Default")!, + mySqlOptions => mySqlOptions.MigrationsHistoryTable("ef_core_migrations_history"))); +``` + +The `MigrationsHistoryTable` option keeps EF Core's migration tracking table prefixed with the rest of this guide's example objects. + +# Run migrations {% #migrate %} + +Migrations need a session connection and full DDL privileges, so run them against the direct MySQL host on port `3306`, not the [pooler](/docs/products/databases/mysql/connection-pooling). The primary `admin` user owns the database. + +Create the first migration, then apply it: + +```bash +dotnet tool run dotnet-ef migrations add InitialCreate +dotnet tool run dotnet-ef database update +``` + +`dotnet tool run dotnet-ef database update` reads the same `Default` connection string and applies any pending migrations. EF Core records applied migrations in the configured migration history table so it only applies new migrations next time. + +# Wire up an ASP.NET server {% #aspnet %} + +The route handlers use ordinary EF Core queries: + +```csharp +var app = builder.Build(); + +app.MapGet("/users", async (AppDbContext db) => + await db.Users.OrderByDescending(user => user.CreatedAt).Take(10).ToListAsync()); + +app.MapPost("/users", async (AppDbContext db, CreateUser request) => +{ + var user = new User { Email = request.Email }; + db.Users.Add(user); + await db.SaveChangesAsync(); + + return Results.Created($"/users/{user.Id}", user); +}); + +app.Run(); + +public record CreateUser(string Email); +``` + +# Connection pooling {% #pooling %} + +Connector/NET has connection pooling enabled by default. Each process keeps its own pool keyed on the connection string, so a long-running ASP.NET server usually connects directly to MySQL and tunes the provider pool with connection string options: + +```text +Server=db-<hash>.<region>.appwrite.center;Port=3306;Database=<database>;User=admin;Password=<password>;SslMode=Required;Max Pool Size=50 +``` + +Keep the sum across all your processes under the engine's connection limit for the database's specification. + +If you run many short-lived instances that each open their own provider pool, route runtime traffic through the Appwrite [connection pooler](/docs/products/databases/mysql/connection-pooling) on port `6033`: + +```text +Server=db-<hash>.<region>.appwrite.center;Port=6033;Database=<database>;User=admin;Password=<password>;SslMode=Required;Max Pool Size=20 +``` + +Keep migrations and administrative jobs on the direct port `3306`. The pooler defaults to **transaction mode**, which does not hold a backend connection across statements. If your application relies on session state such as server-side prepared statements, user variables, or temporary tables, switch the pooler to **session mode**, see the [pooler](/docs/products/databases/mysql/connection-pooling#modes) page for the trade-offs. + +# Use a branch for previews and CI {% #branches %} + +Database [branches](/docs/products/databases/mysql/branches) are isolated copies of a database with their own hostname and credentials. They're useful for running migrations against throwaway data in a pull-request preview or integration-test job: + +1. Create a branch from the API and read its connection details. +2. Build the connection string for the branch host and pass it as the `Default` connection string. +3. Run `dotnet tool run dotnet-ef database update` and your test suite against the branch. +4. Delete the branch when the job finishes. + +Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations run against representative data without touching the source database. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/mysql/connections" title="Connect" %} +Retrieve credentials, rotate the password, and create scoped connection users. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/connection-pooling" title="Connection pooler" %} +Pool modes, ports, and read/write splitting for serverless workloads. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/branches" title="Branches" %} +Ephemeral database copies for preview environments and CI. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/network-security" title="Network" %} +TLS modes, certificate verification, mTLS, and IP allowlists. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/mysql/integrations/fastapi/+page.markdoc b/src/routes/docs/products/databases/mysql/integrations/fastapi/+page.markdoc new file mode 100644 index 00000000000..b5859571a30 --- /dev/null +++ b/src/routes/docs/products/databases/mysql/integrations/fastapi/+page.markdoc @@ -0,0 +1,193 @@ +--- +layout: article +title: FastAPI +description: Use FastAPI and SQLAlchemy 2.x with an Appwrite native MySQL database. Configure the async asyncmy engine, pass TLS through connect_args, inject a session per request, and run Alembic migrations against the direct database port. +--- + +An Appwrite native MySQL database is a standard MySQL engine, so [FastAPI](https://fastapi.tiangolo.com/) with [SQLAlchemy](https://docs.sqlalchemy.org/) and an async MySQL driver works against it with no Appwrite-specific runtime code. You point `create_async_engine` at the connection string from the [connections](/docs/products/databases/mysql/connections) page and use the SQLAlchemy ORM, the FastAPI dependency system, and Alembic the same way you would against any self-hosted MySQL server. + +{% info title="Before you start" %} +You'll need a native MySQL database in a `ready` state and its credentials. See [native MySQL databases](/docs/products/databases/mysql) to create one and [connections](/docs/products/databases/mysql/connections) to retrieve the connection string. The primary user is `admin`, and the database name is generated for each database. +{% /info %} + +# Install dependencies {% #install %} + +```bash +pip install "fastapi[standard]" "sqlalchemy[asyncio]" asyncmy alembic +``` + +# Set the connection string {% #connection-string %} + +Fetch the connection string with the [API](/docs/products/databases/mysql/connections#credentials). Put it in your environment, never commit it. SQLAlchemy's asyncmy dialect uses the `mysql+asyncmy://` scheme, so swap the leading `mysql://` for it and add `charset=utf8mb4`: + +```env +DATABASE_URL="mysql+asyncmy://admin:<password>@db-<hash>.<region>.appwrite.center:3306/<database>?charset=utf8mb4" +DATABASE_SSL=true +``` + +This guide uses the [asyncmy](https://github.com/long2ice/asyncmy) driver. SQLAlchemy also supports the async `mysql+aiomysql://` dialect, but the snippets below use `mysql+asyncmy://`. + +# Create the async engine {% #engine %} + +Cloud connections require TLS. Build an `ssl.SSLContext` and pass it through `connect_args`; use `DATABASE_SSL=false` only for a local MySQL server that does not offer TLS: + +```python +import ssl + +from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine + +connect_args = ( + {"ssl": ssl.create_default_context()} + if settings.database_ssl + else {} +) + +engine = create_async_engine( + settings.database_url, + connect_args=connect_args, + pool_size=10, + max_overflow=5, + pool_pre_ping=True, +) + +SessionLocal = async_sessionmaker(engine, expire_on_commit=False) +``` + +The server certificate is signed by a well-known public CA, so `ssl.create_default_context()` validates the certificate chain against the system trust store. See the [Network](/docs/products/databases/mysql/network-security) page for TLS and network access controls. + +# Define a model {% #model %} + +```python +from datetime import datetime + +from sqlalchemy import String, func +from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column + +class Base(DeclarativeBase): + pass + +class User(Base): + __tablename__ = "fastapi_users" + + id: Mapped[int] = mapped_column(primary_key=True) + email: Mapped[str] = mapped_column(String(255), unique=True) + created_at: Mapped[datetime] = mapped_column(server_default=func.now()) +``` + +SQLAlchemy renders the integer primary key as `AUTO_INCREMENT` for MySQL. The explicit `String(255)` keeps the unique email index within MySQL's indexed column limits. + +# Inject a session per request {% #session %} + +FastAPI's dependency system gives each request its own `AsyncSession` and closes it when the request finishes. Define a dependency that yields a session, then annotate path operations with it: + +```python +from typing import Annotated + +from fastapi import Depends, FastAPI +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +app = FastAPI() + +async def get_session(): + async with SessionLocal() as session: + yield session + +SessionDep = Annotated[AsyncSession, Depends(get_session)] + +@app.post("/users") +async def create_user(email: str, session: SessionDep): + user = User(email=email) + session.add(user) + await session.commit() + await session.refresh(user) + return user + +@app.get("/users") +async def list_users(session: SessionDep): + result = await session.scalars(select(User).order_by(User.created_at.desc())) + return result.all() +``` + +Create the engine once at module scope and reuse it for the whole process, the pool lives inside it. Don't open a new engine per request. + +# Run migrations {% #migrate %} + +Generate Alembic's async scaffold, which opens the connection asynchronously and hands a sync connection to the migration context: + +```bash +alembic init -t async migrations +``` + +Point `target_metadata` at `Base.metadata` in `migrations/env.py`, then autogenerate and apply: + +```bash +alembic revision --autogenerate -m "init" +alembic upgrade head +``` + +Review the generated migration before applying it. If the database contains tables that Alembic should not manage, configure Alembic's include filters or run the migration against a branch so autogenerate only emits changes for your FastAPI app. + +Run migrations against the **direct** engine port (`3306`), not the pooler. DDL needs a session-level connection, and Alembic's async template already uses a `NullPool`, so a fresh connection is opened and closed per run. The primary `admin` user owns the default database and can run schema changes. + +# Pool sizing {% #sizing %} + +A long-running `uvicorn` server holds a SQLAlchemy pool for its lifetime, so connect to the **direct** engine port (`3306`) with a sized pool. Keep `pool_size` multiplied by the number of server processes under the connection limit of your [specification](/docs/products/databases/mysql#specifications), and let `max_overflow` absorb short bursts: + +```python +engine = create_async_engine( + settings.database_url, + connect_args=connect_args, + pool_size=10, + max_overflow=5, + pool_pre_ping=True, +) +``` + +When the same app runs on a serverless platform (for example an Appwrite [function](/docs/products/functions)) where each invocation is a fresh instance, that can fan out into more backend connections than the engine allows. Route runtime traffic through the [connection pooler](/docs/products/databases/mysql/connection-pooling) on port `6033` instead, and avoid keeping another application pool on top of it: + +```env +DATABASE_URL="mysql+asyncmy://admin:<password>@db-<hash>.<region>.appwrite.center:6033/<database>?charset=utf8mb4" +DATABASE_SSL=true +``` + +```python +from sqlalchemy import NullPool + +engine = create_async_engine( + settings.database_url, + connect_args=connect_args, + poolclass=NullPool, + pool_pre_ping=True, +) +``` + +The pooler defaults to **transaction mode**, which returns the backend connection to the pool after each transaction. Use **session mode** if your application relies on session-level state such as user variables, temporary tables, or server-side prepared statements, see the [pooler](/docs/products/databases/mysql/connection-pooling#modes) page for the trade-offs. + +# Use a branch for previews and CI {% #branches %} + +[Branches](/docs/products/databases/mysql/branches) are instant, isolated copies of a database with their own hostname and connection string. They're ideal for running migrations against throwaway data in a pull-request preview or an integration-test job: + +1. Create a branch from the API and read its `connectionString`. +2. Rewrite the scheme to `mysql+asyncmy://`, add `charset=utf8mb4`, and export it as `DATABASE_URL`. +3. Run `alembic upgrade head` against the branch's direct port, then your test suite. +4. Delete the branch when the job finishes. + +Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations run against representative data without touching production. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/mysql/connections" title="Connections" %} +Retrieve credentials and rotate the primary password. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/connection-pooling" title="Connection pooler" %} +Pool modes, ports, and read/write splitting for serverless workloads. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/branches" title="Branches" %} +Ephemeral database copies for preview environments and CI. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/network-security" title="Network" %} +TLS modes, certificate verification, mTLS, and IP allowlists. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/mysql/integrations/gorm/+page.markdoc b/src/routes/docs/products/databases/mysql/integrations/gorm/+page.markdoc new file mode 100644 index 00000000000..3cd1a6eba43 --- /dev/null +++ b/src/routes/docs/products/databases/mysql/integrations/gorm/+page.markdoc @@ -0,0 +1,217 @@ +--- +layout: article +title: GORM +description: Use GORM with an Appwrite native MySQL database in Go. Build the MySQL DSN, open a connection, size the database/sql pool, and run migrations with AutoMigrate or golang-migrate. +--- + +A native MySQL database is a standard MySQL engine, so [GORM](https://gorm.io/) talks to it through the regular MySQL driver. Build a go-sql-driver DSN from the credentials returned by Appwrite, hand it to `gorm.io/driver/mysql`, and use models, `AutoMigrate`, and the query API as you would against any MySQL server. + +{% info title="Before you start" %} +You'll need a native MySQL database in a `ready` state and its credentials. See [native MySQL databases](/docs/products/databases/mysql) to create one and [Connections](/docs/products/databases/mysql/connections) to retrieve the hostname, password, and generated database name. The primary user is `admin`. +{% /info %} + +# Build the DSN {% #dsn %} + +Keep the password out of source and read the connection details from the environment: + +```env +DB_HOST="db-<hash>.<region>.appwrite.center" +DB_PORT="3306" +DB_NAME="<database>" +DB_PASSWORD="<password>" +DB_TLS="true" +``` + +GORM's MySQL driver uses [go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) DSNs. The wire format is `admin:<password>@tcp(db-<hash>.<region>.appwrite.center:3306)/<database>?parseTime=true&tls=true`. Use `mysql.Config` to format the DSN so passwords and database names are escaped correctly: + +```go +cfg := mysqlcfg.Config{ + User: "admin", + Passwd: os.Getenv("DB_PASSWORD"), + Net: "tcp", + Addr: net.JoinHostPort(os.Getenv("DB_HOST"), os.Getenv("DB_PORT")), + DBName: os.Getenv("DB_NAME"), + ParseTime: true, + TLSConfig: os.Getenv("DB_TLS"), +} + +dsn := cfg.FormatDSN() +``` + +`parseTime=true` lets the driver scan MySQL `DATE`, `DATETIME`, and `TIMESTAMP` values into Go `time.Time` values. Keep `DB_TLS=true` for Appwrite Cloud connections. Local development environments that terminate no TLS can set `DB_TLS=false`. + +For mTLS, see the [Network security](/docs/products/databases/mysql/network-security) page. + +# Open a connection {% #open %} + +Pass the DSN to `mysql.Open`, then call `gorm.Open`: + +```go +package main + +import ( + "fmt" + "net" + "os" + "time" + + mysqlcfg "github.com/go-sql-driver/mysql" + gormmysql "gorm.io/driver/mysql" + "gorm.io/gorm" +) + +type User struct { + ID uint `gorm:"primaryKey"` + Email string `gorm:"size:255;not null;uniqueIndex"` + CreatedAt time.Time +} + +func (User) TableName() string { + return "gorm_users" +} + +func main() { + cfg := mysqlcfg.Config{ + User: "admin", + Passwd: os.Getenv("DB_PASSWORD"), + Net: "tcp", + Addr: net.JoinHostPort(os.Getenv("DB_HOST"), os.Getenv("DB_PORT")), + DBName: os.Getenv("DB_NAME"), + ParseTime: true, + TLSConfig: os.Getenv("DB_TLS"), + } + + db, err := gorm.Open(gormmysql.Open(cfg.FormatDSN()), &gorm.Config{}) + if err != nil { + panic(err) + } + + if err := db.AutoMigrate(&User{}); err != nil { + panic(err) + } + + user := User{Email: fmt.Sprintf("ada+%d@example.com", time.Now().UnixNano())} + if err := db.Create(&user).Error; err != nil { + panic(err) + } + + var saved User + if err := db.First(&saved, "email = ?", user.Email).Error; err != nil { + panic(err) + } + + fmt.Println(saved.ID, saved.Email) +} +``` + +Connect to the direct engine port (`3306`) for a long-running server and cap the underlying pool yourself, see [pool sizing](#pool) below. If you route runtime traffic through the [connection pooler](/docs/products/databases/mysql/connection-pooling) on port `6033`, enable `InterpolateParams` in the driver config so placeholder values are sent as one text query in transaction mode: + +```go +cfg := mysqlcfg.Config{ + User: "admin", + Passwd: os.Getenv("DB_PASSWORD"), + Net: "tcp", + Addr: net.JoinHostPort(os.Getenv("DB_HOST"), "6033"), + DBName: os.Getenv("DB_NAME"), + ParseTime: true, + TLSConfig: os.Getenv("DB_TLS"), + InterpolateParams: true, +} + +db, err := gorm.Open(gormmysql.Open(cfg.FormatDSN()), &gorm.Config{}) +``` + +# Define a model and migrate {% #model %} + +Declare your models as Go structs and let GORM create the tables with `AutoMigrate`: + +```go +type User struct { + ID uint `gorm:"primaryKey"` + Email string `gorm:"size:255;not null;uniqueIndex"` + CreatedAt time.Time +} + +func (User) TableName() string { + return "gorm_users" +} + +if err := db.AutoMigrate(&User{}); err != nil { + panic(err) +} +``` + +`AutoMigrate` creates the table if it's missing and adds any missing columns and indexes. It does not drop columns or change existing column types, so it's convenient in development but not a substitute for versioned migrations in production. Run schema changes against the direct engine port because DDL needs a session-level connection. The primary `admin` user owns the generated database and can run schema changes. + +For versioned migrations, [golang-migrate](https://github.com/golang-migrate/migrate) runs ordered up/down files. Point it at the direct engine port: + +```bash +migrate -path ./migrations \ + -database "mysql://admin:$DB_PASSWORD@tcp($DB_HOST:$DB_PORT)/$DB_NAME?tls=$DB_TLS&x-migrations-table=gorm_schema_migrations" \ + up +``` + +# Size the connection pool {% #pool %} + +GORM manages a `database/sql` pool under the hood. A long-running Go server holds that pool for its whole lifetime, so connect to the direct engine port and cap the pool against the engine's connection limit. Reach the underlying `*sql.DB` with `db.DB()`: + +```go +sqlDB, err := db.DB() +if err != nil { + panic(err) +} + +sqlDB.SetMaxOpenConns(25) +sqlDB.SetMaxIdleConns(25) +sqlDB.SetConnMaxLifetime(time.Hour) +``` + +Keep the sum of `SetMaxOpenConns` across every instance below the specification's `maxConnections`. If you run many instances or a serverless runtime that opens a fresh pool per cold start, route runtime traffic through the [connection pooler](/docs/products/databases/mysql/connection-pooling) and keep each instance's pool small. The pooler multiplexes them onto a smaller set of backend connections. + +# Run SQL with GORM {% #sql %} + +Use GORM's raw SQL helpers when you need a query that is clearer as SQL than as model operations. MySQL uses `?` placeholders: + +```go +type Result struct { + Email string +} + +var result Result +if err := db.Raw( + "SELECT email FROM gorm_users WHERE email = ? LIMIT 1", + user.Email, +).Scan(&result).Error; err != nil { + panic(err) +} +``` + +The same connection rules apply: use the direct port for migrations and session-level features, and use the pooler for high fan-out runtime queries when your database specification includes it. + +# Use a branch for previews and CI {% #branches %} + +[Branches](/docs/products/databases/mysql/branches) are isolated copies of a database with their own hostname and connection string. They're ideal for running migrations against throwaway data in a pull-request preview or an integration-test job: + +1. Create a branch from the API and read its `connectionString`. +2. Export the branch host, password, and database name into the environment your tests read. +3. Run `migrate ... up` or `AutoMigrate`, then run your test suite against the branch's direct port. +4. Delete the branch when the job finishes. + +Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations run against representative data without touching production. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/mysql/connections" title="Connections" %} +Retrieve credentials and rotate the primary password. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/connection-pooling" title="Connection pooler" %} +Pool modes, ports, and read/write splitting for serverless workloads. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/branches" title="Branches" %} +Ephemeral database copies for preview environments and CI. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/network-security" title="Network security" %} +TLS and network controls for native MySQL databases. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/mysql/integrations/grafana/+page.markdoc b/src/routes/docs/products/databases/mysql/integrations/grafana/+page.markdoc new file mode 100644 index 00000000000..3946eac362a --- /dev/null +++ b/src/routes/docs/products/databases/mysql/integrations/grafana/+page.markdoc @@ -0,0 +1,99 @@ +--- +layout: article +title: Grafana +description: Connect Grafana to an Appwrite native MySQL database as a data source and build dashboards. Configure the MySQL data source and provision it from YAML. +--- + +An Appwrite [native MySQL database](/docs/products/databases/mysql) exposes a standard managed MySQL 8.4 or 8.0 engine, so [Grafana](https://grafana.com/) connects to it through the built-in **MySQL data source** with no Appwrite-specific configuration. Point the data source at your database hostname, authenticate with your database credentials, and query your tables to build dashboards and alerts. + +{% info title="Before you start" %} +You'll need a native MySQL database in a `ready` state and its credentials. Call `mysql.get()` from the Appwrite API to read `hostname`, `connectionPort`, `connectionUser`, `connectionPassword`, and `connectionString`. The primary user is `admin`, and Appwrite generates the database name for each database. See [Connections](/docs/products/databases/mysql/connections) to retrieve the values. +{% /info %} + +# Protect dashboard credentials {% #credentials %} + +Grafana can execute any SQL allowed by the data source user. Keep panel queries read-only, avoid saving write statements in dashboards, and restrict network access to trusted Grafana hosts with an [IP allowlist](/docs/products/databases/mysql/network-security#ip-allowlist). If your deployment has a secondary MySQL user with only `SELECT` privileges, use that user for Grafana. + +# Choose a connection target {% #target %} + +Grafana holds its data source connections open for the lifetime of the process. Long-lived connections should use either the direct MySQL port `3306` or a [connection pooler](/docs/products/databases/mysql/connection-pooling) on port `6033` running in **session mode**. + +Do not point Grafana at the **transaction-mode** pooler. Transaction mode hands a backend connection back to the pool after every statement, which breaks the session assumptions Grafana relies on for connection reuse and prepared statements. For a typical dashboard workload the direct MySQL port is the simplest choice. See the [pooler modes](/docs/products/databases/mysql/connection-pooling#modes) page for the trade-offs. + +# Add the MySQL data source {% #data-source %} + +In Grafana, open **Connections** > **Data sources** > **Add data source** and select **MySQL**. Fill in the connection details using the values from your native MySQL database: + +| Field | Value | +| --- | --- | +| **Host URL** | `db-<hash>.<region>.appwrite.center:3306` | +| **Database** | `<database>` | +| **Username** | `admin` | +| **Password** | `<password>` | + +Regions are `fra`, `nyc`, `sfo`, `sgp`, `syd`, and `tor`. Appwrite Cloud encrypts connections to the public hostname with TLS. Grafana's MySQL data source uses MySQL-specific TLS fields such as `tlsAuthWithCACert`, `tlsSkipVerify`, and TLS certificate values in `secureJsonData`. + +If you use Grafana Cloud and restrict database access with an IP allowlist, add the Grafana Cloud outbound IP ranges for your stack to the database allowlist. Grafana Cloud can reach the public Appwrite database hostname directly. Private connectivity features are only needed when the database is on a private network. + +Under **Connection limits**, keep the connection counts modest so Grafana doesn't exhaust the engine's connection budget. **Max open** caps total connections from this Grafana instance, **Max idle** caps pooled idle connections, and **Max lifetime** recycles connections after the given number of seconds. Select **Save & test** to verify connectivity. + +# Provision from YAML {% #provisioning %} + +Instead of configuring the data source by hand, you can [provision](https://grafana.com/docs/grafana/latest/administration/provisioning/) it declaratively. Drop a file into Grafana's `provisioning/datasources/` directory and read the password from an environment variable so it never lands in source control: + +```yaml +apiVersion: 1 + +datasources: + - name: Appwrite native MySQL + type: mysql + url: db-<hash>.<region>.appwrite.center:3306 + user: admin + jsonData: + database: <database> + maxOpenConns: 5 + maxIdleConns: 2 + maxIdleConnsAuto: true + connMaxLifetime: 14400 + secureJsonData: + password: $GRAFANA_DB_PASSWORD + editable: false +``` + +The MySQL data source uses `type: mysql`, keeps the database name under `jsonData.database`, and reads the password from `secureJsonData.password`. Grafana expands `$GRAFANA_DB_PASSWORD` from the process environment when it loads the provisioning file. Add Grafana's MySQL TLS fields if your deployment requires explicit TLS configuration. See the [MySQL data source](https://grafana.com/docs/grafana/latest/datasources/mysql/configure/) docs for every available field. + +# Build a panel {% #panel %} + +With the data source connected, create a dashboard and add a panel backed by it. Switch the query editor to code mode and write a read-only query against your tables. For example, to plot daily sign-ups from a `grafana_users` table over time: + +```text +SELECT + CAST(DATE(created_at) AS DATETIME) AS time, + COUNT(*) AS signups +FROM grafana_users +GROUP BY CAST(DATE(created_at) AS DATETIME) +ORDER BY time; +``` + +Grafana maps the `time` column to the panel's time axis and `signups` to the value. MySQL 8.4 enables `ONLY_FULL_GROUP_BY`, so the selected time expression must match the grouped expression. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/mysql" title="MySQL databases" %} +Overview of native MySQL database engines, versions, and regions. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/connections" title="Connect" %} +Retrieve credentials and rotate the primary password. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/connection-pooling" title="Connection pooler" %} +Pool modes and ports. Use the direct MySQL port or session mode for Grafana. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/network-security" title="Network" %} +TLS modes, certificate verification, and IP allowlists. +{% /cards_item %} +{% /cards %} + +{% arrow_link href="/docs/products/databases/mysql/connections" %} +Connect to a native MySQL database +{% /arrow_link %} diff --git a/src/routes/docs/products/databases/mysql/integrations/laravel/+page.markdoc b/src/routes/docs/products/databases/mysql/integrations/laravel/+page.markdoc new file mode 100644 index 00000000000..944c397c743 --- /dev/null +++ b/src/routes/docs/products/databases/mysql/integrations/laravel/+page.markdoc @@ -0,0 +1,215 @@ +--- +layout: article +title: Laravel +description: Use Laravel and Eloquent with an Appwrite native MySQL database. Configure the connection, run migrations against the direct port, and pool serverless traffic through the connection pooler. +--- + +A native MySQL database is a standard MySQL engine, so [Laravel](https://laravel.com/docs) works against it with no Appwrite-specific configuration. Point the `mysql` connection in `config/database.php` at the credentials from the [Connections](/docs/products/databases/mysql/connections) page, then use Eloquent, the query builder, migrations, and queues as you would against any MySQL server. + +{% info title="Before you start" %} +You'll need a native MySQL database in a `ready` state and its credentials. See [MySQL databases](/docs/products/databases/mysql) to create one. To retrieve credentials, call [`mysql.get()`](/docs/products/databases/mysql/connections#credentials) and use the returned hostname, port, username, password, and database name. The primary username is `admin`, and the database name is generated per database. +{% /info %} + +# Configure the connection {% #connection %} + +Laravel reads database credentials from `.env`. Fetch them with [`mysql.get()`](/docs/products/databases/mysql/connections#credentials), then set the matching connection. Never commit `.env`: + +```env +DB_CONNECTION=mysql +DB_HOST=db-<hash>.<region>.appwrite.center +DB_PORT=3306 +DB_DATABASE=<database> +DB_USERNAME=admin +DB_PASSWORD=<password> +DB_CHARSET=utf8mb4 +DB_COLLATION=utf8mb4_unicode_ci +``` + +The scaffolded `config/database.php` wires these variables into the `mysql` connection: + +```php +'mysql' => [ + 'driver' => 'mysql', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '3306'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'unix_socket' => env('DB_SOCKET', ''), + 'charset' => env('DB_CHARSET', 'utf8mb4'), + 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + Mysql::ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + ]) : [], +], +``` + +Laravel imports `Pdo\Mysql` at the top of this file in new projects. Appwrite Cloud requires TLS on the public hostname. If your PHP runtime needs an explicit CA bundle for MySQL TLS verification, set `MYSQL_ATTR_SSL_CA` to a trusted root store path and keep the `options` entry in the `mysql` connection: + +```php +'options' => extension_loaded('pdo_mysql') ? array_filter([ + Mysql::ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), +]) : [], +``` + +# Run migrations {% #migrate %} + +Define your schema with a migration: + +```php +Schema::create('posts', function (Blueprint $table) { + $table->id(); + $table->string('title'); + $table->text('body'); + $table->timestamps(); +}); +``` + +Apply migrations from your machine or a deploy step: + +```bash +php artisan migrate + +# non-interactive, for CI and production deploys +php artisan migrate --force +``` + +Run `migrate` against the **direct** MySQL port (`3306`), not the pooler. Migrations issue DDL and schema inspection queries that should use a stable backend connection. The primary `admin` user owns the database and can run schema changes. + +# Query with Eloquent {% #query %} + +Once the schema is migrated, use Eloquent models and the query builder as usual: + +```php +namespace App\Models; + +use Illuminate\Database\Eloquent\Model; + +class Post extends Model +{ + protected $fillable = [ + 'title', + 'body', + ]; +} +``` + +```php +use App\Models\Post; + +$post = Post::create([ + 'title' => 'Hello from a native MySQL database', + 'body' => 'Stored in a native MySQL database.', +]); + +$recent = Post::query() + ->orderByDesc('created_at') + ->limit(10) + ->get(); +``` + +Nothing about the native MySQL database changes how Eloquent, relationships, transactions, or the query builder behave. It is a standard MySQL server behind a TLS connection. + +# Pool connections from serverless {% #pooling %} + +The right port depends on how your app runs. + +A **long-running** PHP process, traditional PHP-FPM with persistent connections, [Laravel Octane](https://laravel.com/docs/octane), or a queue worker, holds its own backend connection for its lifetime. Point these at the **direct** MySQL port (`3306`), or at the [connection pooler](/docs/products/databases/mysql/connection-pooling) in **session mode**. Don't put a long-lived process behind the transaction-mode pooler. + +A **serverless** or per-request deployment (Vercel, AWS Lambda, Cloud Run) opens a fresh connection on every invocation and can fan out into more backend connections than the engine allows. Route runtime traffic through the pooler's **transaction-mode** port (`6033`) on the same hostname, and keep a named direct connection for migrations and other schema operations: + +```env +# Runtime: pooled, transaction mode +DB_HOST=db-<hash>.<region>.appwrite.center +DB_PORT=6033 +DB_DATABASE=<database> +DB_USERNAME=admin +DB_PASSWORD=<password> +DB_CHARSET=utf8mb4 +DB_COLLATION=utf8mb4_unicode_ci + +# Migrations and schema operations: direct MySQL port +DB_DIRECT_HOST=db-<hash>.<region>.appwrite.center +DB_DIRECT_PORT=3306 +DB_DIRECT_DATABASE=<database> +DB_DIRECT_USERNAME=admin +DB_DIRECT_PASSWORD=<password> +``` + +Add a second MySQL connection in `config/database.php` for direct schema work: + +```php +'mysql_direct' => [ + 'driver' => 'mysql', + 'url' => env('DB_DIRECT_URL'), + 'host' => env('DB_DIRECT_HOST', env('DB_HOST', '127.0.0.1')), + 'port' => env('DB_DIRECT_PORT', env('DB_PORT', '3306')), + 'database' => env('DB_DIRECT_DATABASE', env('DB_DATABASE', 'laravel')), + 'username' => env('DB_DIRECT_USERNAME', env('DB_USERNAME', 'root')), + 'password' => env('DB_DIRECT_PASSWORD', env('DB_PASSWORD', '')), + 'unix_socket' => env('DB_SOCKET', ''), + 'charset' => env('DB_CHARSET', 'utf8mb4'), + 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + Mysql::ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + ]) : [], +], +``` + +Run schema commands against that connection: + +```bash +php artisan migrate --database=mysql_direct --force +``` + +For manual schema work, call `DB::connection('mysql_direct')`. The transaction-mode pooler does not keep session state across transactions, so user variables, temporary tables, and server-side prepared statements should use the direct port or **session mode**. See the [pooler](/docs/products/databases/mysql/connection-pooling#modes) page for the trade-offs. + +# Queues and Horizon {% #queues %} + +A queue worker is a long-running process. `php artisan queue:work` boots once and processes jobs for its whole lifetime, holding a persistent database connection the entire time. The same applies to every worker that [Laravel Horizon](https://laravel.com/docs/horizon) supervises. Treat workers like any other long-lived process: + +- Connect them to the **direct** MySQL port (`3306`) or the **session-mode** pooler, never the transaction-mode pooler. +- Restart workers periodically with `--max-time` or `--max-jobs` so a fresh process reclaims memory and reopens its connection. Supervisor or Horizon restarts them automatically. + +```bash +php artisan queue:work --max-time=3600 --max-jobs=500 +``` + +Each worker counts as one backend connection, so size your worker pool (and Horizon's `maxProcesses`) against the connection budget of your [specification](/docs/products/databases/mysql). Horizon itself requires Redis for the queue backend; only your application's data connection touches the native MySQL database. + +# Use a branch for previews and CI {% #branches %} + +MySQL [branches](/docs/products/databases/mysql/branches) are instant, isolated copies of a database with their own hostname. Create them from the API for running migrations against throwaway data in a pull-request preview or an integration-test job: + +1. Create a branch from the API and read its connection details. +2. Export them as the `DB_*` variables for the job. +3. Run `php artisan migrate --force` and your test suite against the branch. +4. Delete the branch when the job finishes. + +Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations and tests run against realistic data without touching production. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/mysql/connections" title="Connect" %} +Retrieve credentials, rotate the password, and create scoped connection users. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/connection-pooling" title="Connection pooler" %} +Pool modes, ports, and read/write splitting for serverless workloads. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/branches" title="Branches" %} +Ephemeral database copies for preview environments and CI. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/network-security" title="Network" %} +TLS modes, certificate verification, mTLS, and IP allowlists. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/mysql/integrations/metabase/+page.markdoc b/src/routes/docs/products/databases/mysql/integrations/metabase/+page.markdoc new file mode 100644 index 00000000000..6f93a1a2960 --- /dev/null +++ b/src/routes/docs/products/databases/mysql/integrations/metabase/+page.markdoc @@ -0,0 +1,78 @@ +--- +layout: article +title: Metabase +description: Connect Metabase to an Appwrite native MySQL database for analytics. Configure MySQL connection details, require TLS on Cloud, and build dashboards on a session-safe connection. +--- + +Appwrite's native MySQL database is a standard MySQL engine, so [Metabase](https://www.metabase.com/) can connect to it without an Appwrite-specific adapter. Add the database in Metabase, use the host and credentials from the [Connections](/docs/products/databases/mysql/connections) page, and Metabase will sync the schema so your team can build questions and dashboards. + +{% info title="Before you start" %} +You'll need a native MySQL database in a `ready` state and its credentials. Fetch them with [`mysql.get()`](/docs/products/databases/mysql/connections#credentials). The response includes `hostname`, `connectionPort`, `connectionUser`, `connectionPassword`, and `connectionString`. The primary user is `admin`, and the database name is generated per database. +{% /info %} + +# Choose credentials for Metabase {% #credentials %} + +Metabase connects with the MySQL user you provide. Use the least-privilege MySQL account available for analytics. A reporting account typically needs `SELECT` on the tables and views Metabase should query. The primary `admin` user returned by Appwrite can read and write data and schema, so reserve it for trusted Metabase instances. + +Metabase features such as model actions and editable table data need write privileges. Enable them only on a connection whose MySQL account has the specific write permissions that workflow needs. + +# Add the database in Metabase {% #add-database %} + +In Metabase, click the grid icon, then open **Admin > Databases > Add a database**. Choose **MySQL** as the database type and fill in the connection form with the values from the [Connections](/docs/products/databases/mysql/connections) page. + +| Field | Value | +|-------|-------| +| Host | `db-<hash>.<region>.appwrite.center` | +| Port | `3306` | +| Database name | `<database>` | +| Username | `admin`, or your least-privilege MySQL account | +| Password | `<password>` | + +Turn on **Use a secure connection (SSL)**. Appwrite Cloud requires TLS for native MySQL connections on the public hostname. If your Metabase deployment needs an explicit certificate chain, add it in Metabase's **SSL certificate** field. See [Network security](/docs/products/databases/mysql/network-security) for Appwrite TLS and network controls. + +{% info title="Use a session-safe connection" %} +Metabase keeps database sessions open and can use session-level MySQL behavior while exploring data. Connect it to the direct MySQL port (`3306`). If your specification includes the [connection pooler](/docs/products/databases/mysql/connection-pooling), use `session` mode for Metabase. Transaction-mode pooling can break session-level features because a client session is not bound to one backend connection. +{% /info %} + +Click **Save changes**. Metabase verifies the connection and starts its first schema sync. + +# Build a question or dashboard {% #build %} + +Once the first sync finishes, your tables appear in the data picker. To build your first chart: + +1. Click **+ New > Question** and pick your Appwrite MySQL database as the data source. +2. Choose a table, add a summary such as count, sum, or average, and group by a column such as a timestamp. +3. Switch the visualization to a line, bar, or table view, then save the question. +4. Add saved questions to a dashboard and use filters to slice data across cards. + +Use read-only native SQL questions for analytics dashboards. If the connection uses `admin`, use Metabase permissions to limit who can run native SQL or enable write-capable features. + +# How Metabase syncs your schema {% #sync %} + +After you connect, Metabase scans the database to discover tables, columns, constraints, and field metadata, then keeps that metadata current on a schedule: + +- A lightweight schema sync runs hourly by default. +- A more intensive field-value scan runs daily by default to populate filter dropdowns. + +New tables and columns appear after the next sync. To pull them in immediately, open **Admin > Databases > your database** and click **Sync database schema**. You can also restrict which schemas Metabase tracks and adjust the sync and scan cadence from the database settings. Refer to the [Metabase documentation](https://www.metabase.com/docs/latest/databases/sync-scan) for the full set of sync and scan options. + +# Use a branch for testing {% #branches %} + +[Branches](/docs/products/databases/mysql/branches) are isolated copies of a native MySQL database with their own host and credentials. Point a second Metabase database connection at a branch when you want to validate a dashboard against a snapshot of production data without querying the live database. Branches are managed through the API, and you can delete the branch when testing is complete. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/mysql" title="MySQL" %} +Provision and manage a native MySQL database for your project. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/connections" title="Connections" %} +Retrieve connection details and rotate the primary password. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/connection-pooling" title="Connection pooling" %} +Pool modes and ports, including why BI tools need session or direct connections. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/network-security" title="Network security" %} +TLS, certificate verification, IP allowlists, and idle connection timeouts. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/mysql/integrations/nextjs/+page.markdoc b/src/routes/docs/products/databases/mysql/integrations/nextjs/+page.markdoc new file mode 100644 index 00000000000..74385b46e73 --- /dev/null +++ b/src/routes/docs/products/databases/mysql/integrations/nextjs/+page.markdoc @@ -0,0 +1,204 @@ +--- +layout: article +title: Next.js +description: Connect a Next.js App Router application to an Appwrite native MySQL database from Route Handlers, Server Actions, and Server Components, pool from serverless, and fall back to the SQL API on the Edge runtime. +--- + +An Appwrite native MySQL database works with standard MySQL drivers and ORMs, so a [Next.js](https://nextjs.org/) App Router application can query it from server-side code. Point your driver at the connection string from the [Connections](/docs/products/databases/mysql/connections) page and keep all database access on the server. + +{% info title="Before you start" %} +You'll need a native MySQL database in a `ready` state and its credentials. See [MySQL](/docs/products/databases/mysql) to create one and [Connections](/docs/products/databases/mysql/connections) to retrieve the connection string. The primary user is `admin`, and Appwrite generates the database name for each database. +{% /info %} + +# Where to connect {% #where %} + +In the App Router, every server-side execution context runs on the **Node.js runtime by default**, and the Node.js runtime can open raw TCP sockets. That means you can use a standard database driver from any of these: + +- **Route Handlers** (`app/api/.../route.ts`) for public routes, webhooks, and REST-style APIs. +- **Server Actions** (`'use server'` functions) for form submissions and app-internal mutations. +- **Server Components** (`async` components) for read queries that render straight into the page. + +Never import a database driver into a Client Component (`'use client'`) or ship the connection string to the browser. Keep all database access on the server. + +The one exception is the **Edge runtime** (`export const runtime = 'edge'`), which runs in a constrained environment and cannot use Node.js TCP drivers. If a route opts into Edge, use the [SQL API](/docs/products/databases/mysql/quick-start#first-queries) over HTTPS, see [the Edge runtime section](#edge) below. + +# Install the driver {% #install %} + +Install the MySQL driver for Node.js: + +```bash +npm install mysql2 +``` + +# Environment variables {% #env %} + +Put the connection strings in your environment and never commit them. For local development, use `.env.local`, which Next.js loads automatically and the default `create-next-app` template excludes from Git: + +```env +DATABASE_URL="mysql://admin:<password>@db-<hash>.<region>.appwrite.center:6033/<database>" +DIRECT_URL="mysql://admin:<password>@db-<hash>.<region>.appwrite.center:3306/<database>" +MYSQL_SSL="true" +``` + +`DATABASE_URL` points at the [connection pooler](/docs/products/databases/mysql/connection-pooling) port (`6033`) for runtime traffic, and `DIRECT_URL` points at the MySQL engine port (`3306`) for migrations and schema changes. The connection string returned by Appwrite uses the engine port, so change only the port when you connect through the pooler. If your specification does not include the pooler, use the direct connection string for `DATABASE_URL` too. + +Appwrite Cloud serves MySQL over TLS. The examples below use `MYSQL_SSL="true"` to make `mysql2` request TLS and verify the server certificate with the runtime's trusted CAs. For full certificate verification controls or mTLS, see the [Network security](/docs/products/databases/mysql/network-security) page. + +# Create a table for the examples {% #table %} + +Run this SQL once through your migration workflow or the `mysql` client: + +```sql +CREATE TABLE IF NOT EXISTS nextjs_users ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + email VARCHAR(255) NOT NULL UNIQUE, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +INSERT INTO nextjs_users (email) +VALUES ('ada@example.com') +ON DUPLICATE KEY UPDATE email = nextjs_users.email; +``` + +# Pool serverless connections {% #pooling %} + +When you deploy to Vercel, Netlify, or another serverless platform, each invocation can spin up a fresh instance with its own connection pool. Hundreds of concurrent invocations fan out into far more backend connections than the engine allows. Route runtime traffic through the [connection pooler](/docs/products/databases/mysql/connection-pooling) on the pooler port so it can multiplex those instances over a small number of backend connections. + +The pooler defaults to **transaction mode**, which does not keep a backend connection across statements, so session-level features such as server-side prepared statements and temporary tables are unavailable. Use `pool.query()` with `?` placeholders for pooled runtime queries. Reserve the engine port for migrations, schema changes, and workloads that need a full session. + +# Use a singleton client {% #singleton %} + +Instantiate one pool per module scope and reuse it across invocations, so warm serverless instances do not reconnect on every request. In development, Next.js hot reload re-evaluates modules, which can leak connections, so cache the pool on `globalThis`. + +With [mysql2](https://sidorares.github.io/node-mysql2/): + +```ts +// lib/db.ts +import mysql, { type Pool } from 'mysql2/promise'; + +const globalForDb = globalThis as unknown as { mysqlPool?: Pool }; + +function createPool() { + const url = new URL(process.env.DATABASE_URL!); + + return mysql.createPool({ + host: url.hostname, + port: Number(url.port || 3306), + user: decodeURIComponent(url.username), + password: decodeURIComponent(url.password), + database: decodeURIComponent(url.pathname.slice(1)), + waitForConnections: true, + connectionLimit: 5, + ssl: process.env.MYSQL_SSL === 'true' ? { rejectUnauthorized: true } : undefined + }); +} + +export const pool = globalForDb.mysqlPool ?? createPool(); + +if (process.env.NODE_ENV !== 'production') globalForDb.mysqlPool = pool; +``` + +Query it from a Server Component or Route Handler: + +```ts +// app/users/route.ts +import type { RowDataPacket } from 'mysql2'; + +import { pool } from '@/lib/db'; + +type UserRow = RowDataPacket & { + id: number; + email: string; + created_at: Date; +}; + +export async function GET() { + const [users] = await pool.query<UserRow[]>( + 'SELECT id, email, created_at FROM nextjs_users ORDER BY created_at DESC LIMIT ?', + [10] + ); + + return Response.json(users); +} +``` + +# Use Prisma or Drizzle {% #orm %} + +For a typed schema, migrations, and a query builder, reach for an ORM. Both integrate with the pooled `DATABASE_URL` plus direct `DIRECT_URL` pattern above. + +- **Prisma**: point the runtime at the pooled connection string and keep the Prisma CLI on the engine port for migrations, then run `prisma migrate deploy`. See the [Prisma](/docs/products/databases/mysql/integrations/prisma) guide for the full config and migration flow. +- **Drizzle**: use Drizzle's MySQL driver for runtime queries and the direct URL for `drizzle-kit` migrations. See the [Drizzle](/docs/products/databases/mysql/integrations/drizzle) guide. + +{% arrow_link href="/docs/products/databases/mysql/integrations/prisma" %} +Set up Prisma against native MySQL +{% /arrow_link %} + +# Edge runtime: use the SQL API {% #edge %} + +If a Route Handler or route segment opts into the Edge runtime, it cannot use a Node.js MySQL driver: + +```ts +export const runtime = 'edge'; +``` + +From the Edge runtime, you can execute one parameterized SQL statement over HTTPS and get JSON back. `SELECT`, `INSERT`, `UPDATE`, and `DELETE` statements are allowed by default. Use the global `fetch` available in the Edge runtime: + +```ts +// app/edge-users/route.ts +export const runtime = 'edge'; + +export async function GET() { + const response = await fetch( + 'https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>/executions', + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Appwrite-Project': process.env.APPWRITE_PROJECT_ID!, + 'X-Appwrite-Key': process.env.APPWRITE_API_KEY! + }, + body: JSON.stringify({ + sql: 'SELECT id, email FROM nextjs_users WHERE created_at > ? ORDER BY created_at DESC LIMIT ?', + bindings: ['2026-05-01 00:00:00', 10] + }) + } + ); + + if (!response.ok) { + throw new Error(await response.text()); + } + + const { rows } = await response.json(); + return Response.json(rows); +} +``` + +The response is `{ rows, rowCount, columns, durationMs, truncated, bytes }`. Bindings are sent separately and never interpolated into the SQL string. Add `APPWRITE_PROJECT_ID` and `APPWRITE_API_KEY` to your environment alongside the database URLs. Use an API key with the `dedicatedDatabases.execute` scope. + +{% arrow_link href="/docs/products/databases/mysql/quick-start#first-queries" %} +Read the SQL API reference +{% /arrow_link %} + +# Local development {% #local %} + +`next dev` runs on the Node.js runtime, so a local server can connect to the native MySQL database with the same server-only driver code. Keep `DATABASE_URL`, `DIRECT_URL`, and `MYSQL_SSL` in `.env.local`. + +For throwaway data in tests or experiments, create a [branch](/docs/products/databases/mysql/branches), an instant, isolated copy with its own connection string, and point `.env.local` at it. Delete the branch when you're done. + +# Deploy {% #deploy %} + +When you deploy, set the same `DATABASE_URL`, `DIRECT_URL`, `MYSQL_SSL`, `APPWRITE_PROJECT_ID`, and `APPWRITE_API_KEY` as environment variables on your hosting platform. Run migrations from the direct connection in your build or release step, and use the SQL API from any Edge Functions. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/mysql/integrations/prisma" title="Prisma" %} +Datasource config, pooled and direct URLs, and the migration workflow. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/quick-start#first-queries" title="SQL API" %} +Query over HTTPS from the Edge runtime without a TCP connection. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/connection-pooling" title="Connection pooler" %} +Pool modes, ports, and read/write splitting for serverless workloads. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/mysql/integrations/prisma/+page.markdoc b/src/routes/docs/products/databases/mysql/integrations/prisma/+page.markdoc new file mode 100644 index 00000000000..3db720058bd --- /dev/null +++ b/src/routes/docs/products/databases/mysql/integrations/prisma/+page.markdoc @@ -0,0 +1,214 @@ +--- +layout: article +title: Prisma +description: Use Prisma ORM with an Appwrite native MySQL database. Configure the Prisma 7 datasource, apply schema changes through the direct connection, and instantiate Prisma Client with the MySQL driver adapter. +--- + +[Prisma ORM](https://www.prisma.io/) works with Appwrite's native MySQL database as a standard MySQL target. Configure Prisma with the connection string from Appwrite, apply schema changes through the direct database connection, and use Prisma Client from your application code. + +{% info title="Before you start" %} +You'll need a native MySQL database in a `ready` state and its credentials. The database object returned by the Appwrite API includes `hostname`, `connectionUser`, `connectionPassword`, and `connectionString`; you can read it with `mysql.get()` or from the response when you create the database. +{% /info %} + +# Initialize Prisma {% #initialize %} + +Install Prisma, Prisma Client, the MySQL driver adapter, the MariaDB driver used by the adapter, and the TypeScript tools used by the examples: + +```bash +npm install -D prisma typescript tsx @types/node +npm install @prisma/client @prisma/adapter-mariadb dotenv mariadb +``` + +Initialize Prisma for MySQL: + +```bash +npx prisma init --datasource-provider mysql --output ../generated/prisma +``` + +Prisma 7 generates a `prisma.config.ts` file and a `prisma/schema.prisma` file. Set `"type": "module"` in `package.json` if your project does not already use ECMAScript modules. + +# Configure connection strings {% #connection-strings %} + +Store the Appwrite connection string in environment variables and do not commit it: + +```env +DATABASE_URL="mysql://admin:<password>@db-<hash>.<region>.appwrite.center:3306/<database>?sslaccept=strict" +DIRECT_URL="mysql://admin:<password>@db-<hash>.<region>.appwrite.center:3306/<database>?sslaccept=strict" +``` + +`DATABASE_URL` is the runtime connection used by Prisma Client. `DIRECT_URL` is the direct database connection used by Prisma CLI commands. + +Appwrite Cloud uses TLS for MySQL connections. Prisma's MySQL connector uses `sslaccept=strict` for TLS with certificate verification. For mTLS and network controls, see [Network security](/docs/products/databases/mysql/network-security). + +Prisma's `migrate dev` command uses a shadow database to detect drift. Appwrite's MySQL `admin` user is scoped to its database and cannot create another MySQL database for that workflow. Generate development migrations against a local MySQL database, a separate Appwrite database, or an Appwrite branch, then apply committed migrations to this database with `migrate deploy`. + +# Configure Prisma {% #configure-prisma %} + +In `prisma.config.ts`, read the CLI connection string from the environment: + +```ts +import "dotenv/config"; +import { defineConfig } from "prisma/config"; + +export default defineConfig({ + schema: "prisma/schema.prisma", + migrations: { + path: "prisma/migrations", + seed: "tsx prisma/seed.ts", + }, + datasource: { + url: process.env["DIRECT_URL"] ?? process.env["DATABASE_URL"], + }, +}); +``` + +In `prisma/schema.prisma`, keep the datasource provider in the schema file and generate Prisma Client into the output directory created by `prisma init`: + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" +} + +datasource db { + provider = "mysql" +} + +model User { + id String @id @default(uuid()) + email String @unique + createdAt DateTime @default(now()) + + @@map("prisma_User") +} +``` + +Rename the mapped table for your application. If you are connecting to an existing database, use Prisma introspection after you configure the environment variables so your schema matches the current tables. + +# Pool connections from serverless {% #pooling %} + +Prisma Client opens database connections from each running application instance. On serverless platforms, many cold starts can quickly multiply the number of backend MySQL connections. On specifications that include the [connection pooler](/docs/products/databases/mysql/connection-pooling), route runtime traffic through the pooler port, `6033`, for `DATABASE_URL` while keeping `DIRECT_URL` on the direct MySQL port, `3306`, for migrations and introspection: + +```env +# Runtime: pooled connection through the Appwrite connection pooler +DATABASE_URL="mysql://admin:<password>@db-<hash>.<region>.appwrite.center:6033/<database>?sslaccept=strict" + +# Prisma CLI: direct MySQL connection for migrations and introspection +DIRECT_URL="mysql://admin:<password>@db-<hash>.<region>.appwrite.center:3306/<database>?sslaccept=strict" +``` + +The pooler defaults to transaction mode, which gives the highest connection multiplexing. If your application depends on session-level features such as user variables, temporary tables, or session-scoped prepared statements, use session mode or keep that workload on the direct MySQL port. See [Connection pooling](/docs/products/databases/mysql/connection-pooling#modes) for pool mode trade-offs. + +# Apply schema changes {% #schema-changes %} + +For an initial migration generated from the Prisma schema file, create a migration directory and write the SQL diff: + +```bash +mkdir -p prisma/migrations/20260708160000_init +npx prisma migrate diff --from-empty --to-schema prisma/schema.prisma --script --output prisma/migrations/20260708160000_init/migration.sql +``` + +If this database already contains tables and Prisma has not created its migration history, baseline it once before applying migrations: + +```bash +mkdir -p prisma/migrations/00000000000000_baseline +touch prisma/migrations/00000000000000_baseline/migration.sql +npx prisma migrate resolve --applied 00000000000000_baseline +``` + +Apply committed migrations through the direct MySQL connection: + +```bash +npx prisma migrate deploy +``` + +Generate Prisma Client after you install dependencies or change `prisma/schema.prisma`: + +```bash +npx prisma generate +``` + +The primary `admin` user owns its assigned database and can run schema changes inside that database. Keep migrations and introspection on the direct MySQL port because they need session-level behavior. + +# Seed data {% #seed %} + +With the seed command configured in `prisma.config.ts`, add a seed script: + +```ts +import "dotenv/config"; +import { PrismaMariaDb } from "@prisma/adapter-mariadb"; +import { PrismaClient } from "../generated/prisma/client"; + +const adapter = new PrismaMariaDb(process.env.DATABASE_URL!); +const prisma = new PrismaClient({ adapter }); + +await prisma.user.upsert({ + where: { email: "ada@example.com" }, + update: {}, + create: { email: "ada@example.com" }, +}); + +await prisma.$disconnect(); +``` + +Run the seed command: + +```bash +npx prisma db seed +``` + +# Query with Prisma Client {% #query %} + +Instantiate Prisma Client with the MySQL driver adapter: + +```ts +import "dotenv/config"; +import { PrismaMariaDb } from "@prisma/adapter-mariadb"; +import { PrismaClient } from "./generated/prisma/client"; + +const adapter = new PrismaMariaDb(process.env.DATABASE_URL!); +const prisma = new PrismaClient({ adapter }); + +const user = await prisma.user.create({ + data: { email: "grace@example.com" }, +}); + +const recent = await prisma.user.findMany({ + orderBy: { createdAt: "desc" }, + take: 10, +}); + +console.log({ user, recent }); + +await prisma.$disconnect(); +``` + +On long-running servers, instantiate `PrismaClient` once and reuse it. On serverless platforms, keep a single client per module scope so warm invocations reuse it, and rely on the pooler to absorb cold-start connection churn when your database specification includes it. + +# Use a branch for previews and CI {% #branches %} + +[Branches](/docs/products/databases/mysql/branches) are instant, isolated copies of a database with their own hostname and connection string. They are useful for running migrations against throwaway data in a pull-request preview or integration-test job: + +1. Create a branch from the API and read its `connectionString`. +2. Export it as `DIRECT_URL`, and use the pooled variant as `DATABASE_URL` if the branch has the pooler enabled. +3. Run `npx prisma migrate deploy` and your test suite against the branch. +4. Delete the branch when the job finishes. + +Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations run against realistic data without touching production. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/mysql/connections" title="Connections" %} +Retrieve credentials, rotate the primary password, and connect with MySQL tools. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/connection-pooling" title="Connection pooling" %} +Pool modes, ports, and read/write splitting for serverless workloads. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/branches" title="Branches" %} +Ephemeral database copies for preview environments and CI. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/network-security" title="Network security" %} +TLS modes, certificate verification, mTLS, and IP allowlists. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/mysql/integrations/rails/+page.markdoc b/src/routes/docs/products/databases/mysql/integrations/rails/+page.markdoc new file mode 100644 index 00000000000..325fd3228c8 --- /dev/null +++ b/src/routes/docs/products/databases/mysql/integrations/rails/+page.markdoc @@ -0,0 +1,173 @@ +--- +layout: article +title: Rails +description: Use Ruby on Rails and ActiveRecord with an Appwrite native MySQL database. Configure database.yml, run migrations against the direct database port, and size the ActiveRecord pool. +--- + +A native MySQL database is a standard MySQL engine, so [Ruby on Rails](https://rubyonrails.org/) works against it through ActiveRecord with no Appwrite-specific configuration. Point `config/database.yml` at the connection details from the [Connections](/docs/products/databases/mysql/connections) page and use ActiveRecord, migrations, and the rest of the Rails toolchain exactly as you would against any MySQL server. + +{% info title="Before you start" %} +You'll need a native MySQL database in a `ready` state and its credentials. Call `mysql.get()` from the Appwrite API to read `hostname`, `connectionUser`, `connectionPassword`, and `connectionString`. The primary user is `admin`, and Appwrite generates the database name for each database. +{% /info %} + +# Install the database driver {% #driver %} + +ActiveRecord talks to MySQL through the `mysql2` driver gem. Add it to your `Gemfile`: + +```ruby +# Gemfile +gem "mysql2", "~> 0.5" +``` + +The `mysql2` gem builds against MySQL client libraries, so the MySQL or MariaDB client headers must be available at install time. + +Then install: + +```bash +bundle install +``` + +# Set the connection string {% #connection-string %} + +Fetch the connection string with the [API](/docs/products/databases/mysql/connections#credentials). Keep it in an environment variable and do not commit it: + +```env +DATABASE_URL="mysql://admin:<password>@db-<hash>.<region>.appwrite.center:3306/<database>" +DB_SSL_MODE="required" +``` + +Rails maps the `mysql://` URL scheme to the `mysql2` adapter by default. Set `ssl_mode` in `database.yml` so mysql2 requires TLS on Appwrite Cloud. For certificate verification (`verify_identity`) or mTLS, see the [Network](/docs/products/databases/mysql/network-security) page. + +# Configure database.yml {% #database-yml %} + +Rails reads `DATABASE_URL` automatically. The simplest configuration points the `url` at the environment variable and lets ActiveRecord parse the host, port, database, and credentials out of it: + +```yaml +# config/database.yml +production: + adapter: mysql2 + url: <%= ENV["DATABASE_URL"] %> + ssl_mode: <%= ENV.fetch("DB_SSL_MODE", "required") %> + max_connections: <%= ENV.fetch("RAILS_MAX_THREADS", 5) %> +``` + +If you'd rather set the fields explicitly, the discrete keys map one-to-one to the values from the [Connections](/docs/products/databases/mysql/connections#credentials) response. Use ERB to read each value from the environment so no secret lands in source control: + +```yaml +# config/database.yml +production: + adapter: mysql2 + encoding: utf8mb4 + host: <%= ENV["DB_HOST"] %> # db-<hash>.<region>.appwrite.center + port: <%= ENV.fetch("DB_PORT", 3306) %> + database: <%= ENV["DB_NAME"] %> # <database> + username: <%= ENV.fetch("DB_USER", "admin") %> + password: <%= ENV["DB_PASSWORD"] %> + ssl_mode: <%= ENV.fetch("DB_SSL_MODE", "required") %> + max_connections: <%= ENV.fetch("RAILS_MAX_THREADS", 5) %> +``` + +When both `DATABASE_URL` and explicit keys are present, Rails merges them. `ssl_mode` and `max_connections` can still be set in `database.yml`, so a `url`-based config can keep secrets in the environment and tune the connection pool in YAML. + +# Size the connection pool {% #pool %} + +ActiveRecord manages a per-process connection pool. In current Rails apps, `max_connections:` caps how many backend connections a single Rails process holds. Older apps may use the `pool:` name for the same setting. The value defaults to `5` and must be large enough for every thread that checks out a connection, your Puma worker threads plus any background job threads in the same process. + +```yaml +production: + adapter: mysql2 + url: <%= ENV["DATABASE_URL"] %> + ssl_mode: <%= ENV.fetch("DB_SSL_MODE", "required") %> + max_connections: <%= ENV.fetch("RAILS_MAX_THREADS", 5) %> +``` + +Tying `max_connections` to `RAILS_MAX_THREADS` keeps it aligned with Puma's thread count. Each Puma **worker** is a separate process with its own pool, so the backend connection count is roughly `max_connections × workers × server instances`. Keep that product within your specification's `maxConnections`, see [specifications](/docs/products/databases/mysql#specifications). + +# Run migrations {% #migrate %} + +Generate and apply migrations the usual way: + +```bash +bin/rails db:migrate +``` + +Migrations issue DDL and need a session-level connection, so run them against the direct MySQL port `3306`, not the transaction-mode pooler. Point `DATABASE_URL` (or a separate migration URL) at the direct port when you run `db:migrate`. The primary `admin` user owns the database and can run schema changes. + +# Use ActiveRecord {% #activerecord %} + +Once `database.yml` is configured, models work with no further setup. Define a migration and model, then query through ActiveRecord: + +```ruby +# db/migrate/20240101000000_create_rails_users.rb +class CreateRailsUsers < ActiveRecord::Migration[8.1] + def change + create_table :rails_users do |t| + t.string :email, null: false + t.timestamps + end + add_index :rails_users, :email, unique: true + end +end +``` + +```ruby +# app/models/rails_user.rb +class RailsUser < ApplicationRecord + validates :email, presence: true, uniqueness: true +end +``` + +```ruby +RailsUser.create!(email: 'ada@example.com') + +recent = RailsUser.order(created_at: :desc).limit(10) +``` + +ActiveRecord opens connections lazily and reuses them from the pool, so a long-running Puma server keeps a small, stable set of backend connections rather than opening one per request. + +# Pooling for a long-running server {% #pooling %} + +A Rails app under Puma is a long-running process: it holds an ActiveRecord pool for its lifetime. That pairs naturally with the direct MySQL port `3306`, sized so `max_connections × workers` stays within your connection budget. This is the recommended setup for a persistent server. + +If you instead route through the [connection pooler](/docs/products/databases/mysql/connection-pooling) to absorb spikes or many app instances, prefer **session mode**, which keeps a backend connection for the whole client session and behaves like a direct connection to ActiveRecord. The pooler defaults to **transaction mode**, which hands out a different backend connection per transaction. If your Rails configuration enables server-side prepared statements, disable them for transaction-mode pooling or use session mode: + +```yaml +production: + adapter: mysql2 + url: <%= ENV["DATABASE_URL"] %> # pooler host, port 6033 + ssl_mode: <%= ENV.fetch("DB_SSL_MODE", "required") %> + max_connections: <%= ENV.fetch("RAILS_MAX_THREADS", 5) %> + prepared_statements: false +``` + +See the [pooler](/docs/products/databases/mysql/connection-pooling#modes) page for the mode trade-offs. Whichever runtime connection you choose, always run `bin/rails db:migrate` against the direct MySQL port. + +# Use a branch for previews and CI {% #branches %} + +[Branches](/docs/products/databases/mysql/branches) are instant, isolated copies of a database with their own hostname and connection string. They're ideal for running migrations against throwaway data in a pull-request preview or an integration-test job: + +1. Create a branch from the API and read its `connectionString`. +2. Export it as `DATABASE_URL` for the job. +3. Run `bin/rails db:migrate` and your test suite against the branch. +4. Delete the branch when the job finishes. + +A branch has no pooler and exposes the MySQL port directly, which gives migrations the session-level connection they need. Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations run against realistic data without touching production. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/mysql/connections" title="Connections" %} +Retrieve credentials and rotate the primary password. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/connection-pooling" title="Connection pooler" %} +Pool modes, ports, and read/write splitting for high-concurrency workloads. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/branches" title="Branches" %} +Ephemeral database copies for preview environments and CI. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/network-security" title="Network" %} +TLS modes, certificate verification, mTLS, and IP allowlists. +{% /cards_item %} +{% /cards %} + +For ActiveRecord and migration details beyond this guide, see the [Rails configuration guide](https://guides.rubyonrails.org/configuring.html). diff --git a/src/routes/docs/products/databases/mysql/integrations/retool/+page.markdoc b/src/routes/docs/products/databases/mysql/integrations/retool/+page.markdoc new file mode 100644 index 00000000000..d29e7d9fb3d --- /dev/null +++ b/src/routes/docs/products/databases/mysql/integrations/retool/+page.markdoc @@ -0,0 +1,89 @@ +--- +layout: article +title: Retool +description: Connect Retool to an Appwrite native MySQL database to build internal and admin tools. Fetch connection details with the API, configure the MySQL resource with TLS, and allow Retool Cloud network access when needed. +--- + +An Appwrite [native MySQL database](/docs/products/databases/mysql) exposes a standard MySQL connection, so [Retool](https://retool.com/) connects to it through the built-in **MySQL** resource. Use the database hostname, generated database name, and credentials from Appwrite, then build queries, tables, and forms in Retool for dashboards and admin panels. + +{% info title="Before you start" %} +You'll need a native MySQL database in a `ready` state, an Appwrite API key with `databases.read`, and permission to create resources in Retool. Fetch connection details by calling `mysql.get()`, which returns `hostname`, `connectionPort`, `connectionUser`, `connectionPassword`, and `connectionString`. See [Connections](/docs/products/databases/mysql/connections) for the full flow. +{% /info %} + +# Choose credentials {% #credentials %} + +The primary user is `admin`, and the database name is generated for each database. The connection string has this form: `mysql://admin:<password>@db-<hash>.<region>.appwrite.center:3306/<database>`. + +Use the returned `admin` credentials for the Retool resource and keep access limited through Retool resource permissions, Appwrite network controls, TLS, and narrowly scoped queries. Retool stores resource credentials server-side, so app users query through the Retool resource instead of connecting to MySQL from the browser. + +# Create the MySQL resource {% #resource %} + +In Retool, go to **Resources**, click **Create new** > **Resource**, search for `MySQL`, and select the MySQL tile. Give the resource a clear **Name** and optional **Description** that identifies the Appwrite database and environment. + +In **Resource credentials**, either paste the Appwrite connection string or fill in the fields manually: + +| Retool setting | Value | +|----------------|-------| +| Host | `db-<hash>.<region>.appwrite.center` | +| Port | `3306` | +| Database name | `<database>` | +| Connection options | Optional MySQL query parameters, such as `timezone=UTC` or `connectTimeout=10000` | +| Authentication | **Username and password** | +| Username | `admin` | +| Password | The value from `connectionPassword` | + +In **Advanced options**, choose an outbound region if your Retool organization uses regional egress and you want the resource traffic to originate near your Appwrite database. + +Enable **SSL/TLS**. Appwrite Cloud terminates TLS at the edge, and the certificate is signed by a public CA. If Retool shows **Reject unauthorized**, keep it enabled. If Retool shows **Verification mode**, choose **Full verification**. Leave **CA certificate** empty. + +Click **Test connection**. If the test succeeds, click **Create resource**. + +# Pick the connection path {% #connection-path %} + +For most Retool apps, connect directly to MySQL on port `3306`. Appwrite's MySQL pooler listens on port `6033` only on specifications that include connection pooling. If your database has the [connection pooler](/docs/products/databases/mysql/connection-pooling) enabled and your Retool queries rely on session-level state such as server-side prepared statements, user variables, or temporary tables, configure the pooler in **session** mode for that Retool resource. + +{% arrow_link href="/docs/products/databases/mysql/connection-pooling#modes" %} +Compare pooler modes +{% /arrow_link %} + +# Allow Retool Cloud through the network {% #network %} + +If you enabled an [IP allowlist](/docs/products/databases/mysql/network-security#ip-allowlist) for the database, add the Retool Cloud egress addresses for the resource's outbound region. Retool's default outbound region is `us-west-2`, and Retool also documents `eu-central-1` and `ap-southeast-1` egress addresses. Retrieve the current list from [Retool's IP address documentation](https://docs.retool.com/data-sources/reference/ip-allowlist-cloud-orgs) instead of copying static addresses into your runbook. + +If you leave the database allowlist open, rely on TLS, database credentials, and Retool resource permissions to protect access. + +{% arrow_link href="/docs/products/databases/mysql/network-security#ip-allowlist" %} +Configure the IP allowlist +{% /arrow_link %} + +# Build an admin tool {% #build %} + +After the resource is connected, create MySQL queries in Retool and wire them to components: + +- Use **SQL mode** for read queries that feed a **Table**, chart, or other display component. +- Use **GUI mode** actions such as **Insert a record**, **Update an existing record**, **Update a record, or create a new record if it doesn't exist**, **Delete a record**, **Bulk insert records**, **Bulk update via a primary key**, and **Bulk upsert via a primary key** for forms, imports, and editable tables. +- Bind table edits to the **Save changes** event and refresh the read query after writes complete. +- Show a confirmation modal before delete actions. + +Reference component values with Retool's `{{ }}` embedded expressions. Retool converts MySQL queries to prepared statements by default, which separates values from SQL text and helps prevent SQL injection. Keep that protection enabled unless you have a specific, reviewed reason to disable it. + +# Use a branch for staging {% #branches %} + +[Branches](/docs/products/databases/mysql/branches) are API-created, isolated copies of a MySQL database with their own connection details. Create a branch for staging or preview work, fetch its `connectionString`, and configure a second Retool MySQL resource against that branch. Delete the branch when the staging tool is no longer needed. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/mysql" title="MySQL" %} +Create and manage a native MySQL database. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/connections" title="Connections" %} +Retrieve credentials and rotate the primary password. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/network-security" title="Network security" %} +TLS, IP allowlists, and other network controls. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/branches" title="Branches" %} +Create isolated database copies for staging and preview environments. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/mysql/integrations/spring-boot/+page.markdoc b/src/routes/docs/products/databases/mysql/integrations/spring-boot/+page.markdoc new file mode 100644 index 00000000000..983119888a8 --- /dev/null +++ b/src/routes/docs/products/databases/mysql/integrations/spring-boot/+page.markdoc @@ -0,0 +1,182 @@ +--- +layout: article +title: Spring Boot +description: Connect a Spring Boot application to an Appwrite native MySQL database with Spring Data JPA and Hibernate. Configure the JDBC datasource and HikariCP pool, map entities, and run Flyway or Liquibase migrations against MySQL. +--- + +An Appwrite native MySQL database is a standard MySQL engine, so a Spring Boot application connects to it through MySQL Connector/J with no Appwrite-specific runtime configuration. Point `spring.datasource` at the JDBC URL from your database credentials, size the built-in HikariCP pool, and use Spring Data JPA, Hibernate, Flyway, or Liquibase as you would with any managed MySQL server. + +{% info title="Before you start" %} +You'll need a native MySQL database in a `ready` state and its credentials. You can fetch credentials with the API by calling `mysql.get()`, which returns `hostname`, `connectionUser`, `connectionPassword`, and `connectionString`. See [Connections](/docs/products/databases/mysql/connections) for the full flow. +{% /info %} + +# Add the dependencies {% #dependencies %} + +A Spring Data JPA application needs the JPA starter and MySQL Connector/J. HikariCP ships with `spring-boot-starter-data-jpa`, and Spring Boot picks the driver class from the JDBC URL. + +```text +org.springframework.boot:spring-boot-starter-data-jpa +com.mysql:mysql-connector-j +``` + +If you use Flyway, add `org.springframework.boot:spring-boot-starter-flyway` and `org.flywaydb:flyway-mysql`. If you use Liquibase, add `org.springframework.boot:spring-boot-starter-liquibase`. + +# Configure the datasource {% #datasource %} + +Build the JDBC URL from the host and database name in the credentials returned by `mysql.get()`. Appwrite uses port `3306`, the primary user is `admin`, and the database name is generated per database. Read the password from the environment: + +```yaml +spring: + datasource: + url: jdbc:mysql://db-<hash>.<region>.appwrite.center:3306/<database>?sslMode=REQUIRED + username: admin + password: ${DB_PASSWORD} + hikari: + maximum-pool-size: 10 + minimum-idle: 2 + connection-timeout: 30000 + max-lifetime: 1200000 + jpa: + hibernate: + ddl-auto: validate +``` + +The equivalent `application.properties`: + +```ini +spring.datasource.url=jdbc:mysql://db-<hash>.<region>.appwrite.center:3306/<database>?sslMode=REQUIRED +spring.datasource.username=admin +spring.datasource.password=${DB_PASSWORD} +spring.datasource.hikari.maximum-pool-size=10 +spring.datasource.hikari.minimum-idle=2 +spring.datasource.hikari.connection-timeout=30000 +spring.datasource.hikari.max-lifetime=1200000 +spring.jpa.hibernate.ddl-auto=validate +``` + +`sslMode=REQUIRED` enables TLS for the MySQL connection. If your security policy requires host certificate validation, configure MySQL Connector/J with `sslMode=VERIFY_IDENTITY` and a JVM trust configuration that trusts the certificate chain. The [Network security](/docs/products/databases/mysql/network-security) page covers TLS, mTLS, and IP allowlists. + +# Size the HikariCP pool {% #pool %} + +A Spring Boot server is long-running, so it holds its HikariCP pool open for the lifetime of the process. Keep `maximum-pool-size` modest. HikariCP guidance is that throughput usually peaks at a small pool, roughly `(CPU cores x 2) + effective spindle count` for the database, not hundreds of connections. A pool that exceeds what MySQL can serve only queues work inside the database and adds latency. + +Each replica of your application opens its own pool, so multiply `maximum-pool-size` by the number of instances and keep the total under the connection budget of your native MySQL database [specification](/docs/products/databases/mysql#specifications). Set `max-lifetime` a little below your infrastructure's idle timeout so HikariCP recycles connections before they are closed. + +# Map an entity {% #entity %} + +Define a JPA entity and a Spring Data repository as usual. This example uses a prefixed table name so it is easy to identify in a shared database: + +```java +package com.example.demo; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +@Entity +@Table(name = "spring_boot_users") +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, unique = true) + private String email; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } +} +``` + +```java +package com.example.demo; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserRepository extends JpaRepository<User, Long> { + Optional<User> findByEmail(String email); +} +``` + +Inject the repository wherever you need it and call `save`, `findById`, `findByEmail`, and the rest of the generated query methods. HikariCP hands each transaction a pooled connection and returns it on commit. + +# Run migrations {% #migrate %} + +Let a migration tool own the schema and set `ddl-auto: validate` so Hibernate checks the mapping against the live tables at startup but never alters them. Add Flyway or Liquibase to your build and Spring Boot runs pending migrations automatically on boot. + +For example, a Flyway migration at `src/main/resources/db/migration/V1__init.sql` can create the table used by the entity above: + +```sql +CREATE TABLE spring_boot_users ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + email VARCHAR(255) NOT NULL UNIQUE +); +``` + +Flyway reads versioned scripts from `src/main/resources/db/migration`. Point Flyway at the direct MySQL port `3306` so migrations run on a session connection with DDL privileges. Setting `spring.flyway.url` gives Flyway its own datasource, independent of the runtime pool: + +```ini +spring.flyway.url=jdbc:mysql://db-<hash>.<region>.appwrite.center:3306/<database>?sslMode=REQUIRED +spring.flyway.user=admin +spring.flyway.password=${DB_PASSWORD} +``` + +Liquibase is equivalent: it reads a changelog from `src/main/resources/db/changelog` and accepts its own `spring.liquibase.url`, `spring.liquibase.user`, and `spring.liquibase.password` pointing at the same MySQL port. + +The primary `admin` user owns the database and can run schema changes, so run migrations as `admin`. + +# Pooling and the connection pooler {% #pooling %} + +HikariCP is already a connection pool, so a long-running Spring Boot server should connect to the direct MySQL port `3306` and let HikariCP manage connections. Routing a server's traffic through the [connection pooler](/docs/products/databases/mysql/connection-pooling) in `transaction` mode stacks HikariCP on top of a transaction pooler and can break session-level features such as server-side prepared statements, user variables, and temporary tables. + +If you put the pooler in front of your server, use `session` mode so MySQL keeps a backend connection for the whole client session. Connect HikariCP on the pooler port `6033` and keep `maximum-pool-size` small. See the [connection pooler](/docs/products/databases/mysql/connection-pooling#modes) page for the mode trade-offs. Always run Flyway or Liquibase against port `3306` regardless of how runtime traffic connects. + +# Use a branch for previews and CI {% #branches %} + +[Branches](/docs/products/databases/mysql/branches) are instant, isolated copies of a database with their own hostname and connection string. They are useful for running migrations against throwaway data in a pull-request preview or integration-test job. + +1. Create a branch with the API and read its `connectionString`. +2. Convert the connection string to a JDBC URL and inject it into `spring.datasource.url`, or split it into `spring.datasource.url`, `spring.datasource.username`, and `spring.datasource.password`. +3. Boot the application so Flyway or Liquibase applies migrations, then run your test suite against the branch. +4. Delete the branch when the job finishes. + +Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations and `@DataJpaTest` integration tests run against representative data without touching production. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/mysql/connections" title="Connections" %} +Retrieve credentials and rotate the primary password. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/connection-pooling" title="Connection pooler" %} +Pool modes, ports, and read/write splitting for the connection pooler. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/branches" title="Branches" %} +Ephemeral database copies for preview environments and CI. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/network-security" title="Network security" %} +TLS modes, certificate verification, mTLS, and IP allowlists. +{% /cards_item %} +{% /cards %} + +{% arrow_link href="/docs/products/databases/mysql" %} +Back to native MySQL overview +{% /arrow_link %} diff --git a/src/routes/docs/products/databases/mysql/maintenance/+page.markdoc b/src/routes/docs/products/databases/mysql/maintenance/+page.markdoc new file mode 100644 index 00000000000..9dc6fd110e3 --- /dev/null +++ b/src/routes/docs/products/databases/mysql/maintenance/+page.markdoc @@ -0,0 +1,642 @@ +--- +layout: article +title: Maintenance +description: Maintenance windows, online engine version upgrades, pause and resume, and the lifecycle states of your MySQL database. +--- + +Appwrite manages the infrastructure around your database: security patches, engine upgrades, and instance health. This page covers the controls you have over when and how that maintenance happens. + +# Maintenance window {% #window %} + +Routine maintenance that can briefly affect the database runs inside a weekly window that you choose. Set it through the API by picking a day and start hour (UTC): + +{% multicode %} +```server-nodejs +import { Client, Mysql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.updateMaintenance({ + databaseId: '<DATABASE_ID>', + day: 'sun', + hourUtc: 3, +}); +``` +```server-deno +import { Client, Mysql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.updateMaintenance({ + databaseId: '<DATABASE_ID>', + day: 'sun', + hourUtc: 3, +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Mysql; + +$client = (new Client()) + ->setEndpoint('https://cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$mysql = new Mysql($client); + +$mysql->updateMaintenance( + databaseId: '<DATABASE_ID>', + day: 'sun', + hourUtc: 3, +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.mysql import Mysql + +client = Client() +client.set_endpoint('https://cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +mysql = Mysql(client) + +mysql.update_maintenance( + database_id='<DATABASE_ID>', + day='sun', + hour_utc=3, +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +mysql = Mysql.new(client) + +mysql.update_maintenance( + database_id: '<DATABASE_ID>', + day: 'sun', + hour_utc: 3, +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Mysql mysql = new Mysql(client); + +await mysql.UpdateMaintenance( + databaseId: "<DATABASE_ID>", + day: "sun", + hourUtc: 3 +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Mysql mysql = Mysql(client); + +await mysql.updateMaintenance( + databaseId: '<DATABASE_ID>', + day: 'sun', + hourUtc: 3, +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Mysql + +val client = Client() + .setEndpoint("https://cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val mysql = Mysql(client) + +mysql.updateMaintenance( + databaseId = "<DATABASE_ID>", + day = "sun", + hourUtc = 3, +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let mysql = Mysql(client) + +_ = try await mysql.updateMaintenance( + databaseId: "<DATABASE_ID>", + day: "sun", + hourUtc: 3 +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewMysql(client) + + _, err := service.UpdateMaintenance("<DATABASE_ID>", "sun", 3) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::mysql::Mysql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let mysql = Mysql::new(&client); + + mysql.update_maintenance("<DATABASE_ID>", "sun", 3).await?; + + Ok(()) +} +``` +```bash +curl -X PATCH \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <YOUR_API_KEY>" \ + -H "Content-Type: application/json" \ + -d '{ + "day": "sun", + "hourUtc": 3 + }' \ + https://cloud.appwrite.io/v1/mysql/<DATABASE_ID>/maintenance +``` +{% /multicode %} + +`day` accepts `sun` through `sat`, and `hourUtc` accepts `0` to `23`. + +# Engine version upgrades {% #upgrades %} + +You can upgrade the MySQL version online, such as from 8.0 to 8.4. A second instance is provisioned on the target version, data streams over with logical replication, and traffic cuts over once replication has caught up, with no read or write outage. The target version must be newer than the database's current version: + +{% multicode %} +```server-nodejs +import { Client, Mysql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.createUpgrade({ + databaseId: '<DATABASE_ID>', + targetVersion: '8.4', +}); +``` +```server-deno +import { Client, Mysql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.createUpgrade({ + databaseId: '<DATABASE_ID>', + targetVersion: '8.4', +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Mysql; + +$client = (new Client()) + ->setEndpoint('https://cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$mysql = new Mysql($client); + +$mysql->createUpgrade( + databaseId: '<DATABASE_ID>', + targetVersion: '8.4', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.mysql import Mysql + +client = Client() +client.set_endpoint('https://cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +mysql = Mysql(client) + +mysql.create_upgrade( + database_id='<DATABASE_ID>', + target_version='8.4', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +mysql = Mysql.new(client) + +mysql.create_upgrade( + database_id: '<DATABASE_ID>', + target_version: '8.4', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Mysql mysql = new Mysql(client); + +await mysql.CreateUpgrade( + databaseId: "<DATABASE_ID>", + targetVersion: "8.4" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Mysql mysql = Mysql(client); + +await mysql.createUpgrade( + databaseId: '<DATABASE_ID>', + targetVersion: '8.4', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Mysql + +val client = Client() + .setEndpoint("https://cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val mysql = Mysql(client) + +mysql.createUpgrade( + databaseId = "<DATABASE_ID>", + targetVersion = "8.4", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let mysql = Mysql(client) + +_ = try await mysql.createUpgrade( + databaseId: "<DATABASE_ID>", + targetVersion: "8.4" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewMysql(client) + + _, err := service.CreateUpgrade("<DATABASE_ID>", "8.4") + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::mysql::Mysql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let mysql = Mysql::new(&client); + + mysql.create_upgrade("<DATABASE_ID>", "8.4").await?; + + Ok(()) +} +``` +```bash +curl -X POST \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <YOUR_API_KEY>" \ + -H "Content-Type: application/json" \ + -d '{ + "targetVersion": "8.4" + }' \ + https://cloud.appwrite.io/v1/mysql/<DATABASE_ID>/upgrades +``` +{% /multicode %} + +# Pause and resume {% #pause-resume %} + +A paused database stops its compute but keeps its storage, configuration, and credentials. Pause a database you are not using to stop paying for compute; resume it when you need it again. From the API, update the status: + +{% multicode %} +```server-nodejs +import { Client, Mysql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.update({ + databaseId: '<DATABASE_ID>', + status: 'paused', +}); +``` +```server-deno +import { Client, Mysql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.update({ + databaseId: '<DATABASE_ID>', + status: 'paused', +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Mysql; + +$client = (new Client()) + ->setEndpoint('https://cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$mysql = new Mysql($client); + +$mysql->update( + databaseId: '<DATABASE_ID>', + status: 'paused', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.mysql import Mysql + +client = Client() +client.set_endpoint('https://cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +mysql = Mysql(client) + +mysql.update( + database_id='<DATABASE_ID>', + status='paused', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +mysql = Mysql.new(client) + +mysql.update( + database_id: '<DATABASE_ID>', + status: 'paused', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Mysql mysql = new Mysql(client); + +await mysql.Update( + databaseId: "<DATABASE_ID>", + status: "paused" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Mysql mysql = Mysql(client); + +await mysql.update( + databaseId: '<DATABASE_ID>', + status: 'paused', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Mysql + +val client = Client() + .setEndpoint("https://cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val mysql = Mysql(client) + +mysql.update( + databaseId = "<DATABASE_ID>", + status = "paused", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let mysql = Mysql(client) + +_ = try await mysql.update( + databaseId: "<DATABASE_ID>", + status: "paused" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/mysql" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewMysql(client) + + _, err := service.Update( + "<DATABASE_ID>", + mysql.WithUpdateStatus("paused"), + ) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::mysql::Mysql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let mysql = Mysql::new(&client); + + mysql.update("<DATABASE_ID>", None, Some("paused"), None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None).await?; + + Ok(()) +} +``` +```bash +curl -X PATCH \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <YOUR_API_KEY>" \ + -H "Content-Type: application/json" \ + -d '{ + "status": "paused" + }' \ + https://cloud.appwrite.io/v1/mysql/<DATABASE_ID> +``` +{% /multicode %} + +Set `status` back to `ready` to resume. Both transitions are asynchronous: the request returns immediately and the database moves through `pausing` or `resuming` before settling. A database in the `failed` state can also be recovered by setting its status to `ready`. + +# Lifecycle states {% #states %} + +| Status | Meaning | +|----------------|------------------------------------------------------------------| +| `provisioning` | Being created | +| `ready` | Online and accepting connections | +| `scaling` | A configuration or specification change is being applied | +| `pausing` | Transitioning to paused | +| `paused` | Compute stopped, storage retained | +| `resuming` | Transitioning back to ready | +| `restoring` | A backup or point-in-time restore is in progress | +| `failed` | An infrastructure error occurred; the database can be resumed | + +# Deleting a database {% #delete %} + +Delete a database programmatically or from the databases list in the Console. Deletion stops billing and invalidates the credentials immediately. Deleting a database also deletes its backups, so export anything you need first. diff --git a/src/routes/docs/products/databases/mysql/monitoring/+page.markdoc b/src/routes/docs/products/databases/mysql/monitoring/+page.markdoc new file mode 100644 index 00000000000..125beea074f --- /dev/null +++ b/src/routes/docs/products/databases/mysql/monitoring/+page.markdoc @@ -0,0 +1,215 @@ +--- +layout: article +title: Monitoring +description: Check database health, watch lifecycle states, and inspect active connections on your native MySQL database. +--- + +Every native MySQL database ships with programmatic health checks, and the engine's own instrumentation is fully available to you. There is nothing to install in the database for these checks. + +# Database health {% #status %} + +Poll the database status for live health information after the database is ready: uptime, connection counts, replica state, and storage volumes. Use it from your own monitoring: + +{% multicode %} +```server-nodejs +import { Client, Mysql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +const status = await mysql.getStatus({ + databaseId: '<DATABASE_ID>', +}); +``` +```server-deno +import { Client, Mysql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +const status = await mysql.getStatus({ + databaseId: '<DATABASE_ID>', +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Mysql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$mysql = new Mysql($client); + +$status = $mysql->getStatus( + databaseId: '<DATABASE_ID>', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.mysql import Mysql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +mysql = Mysql(client) + +status = mysql.get_status( + database_id='<DATABASE_ID>', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +mysql = Mysql.new(client) + +status = mysql.get_status( + database_id: '<DATABASE_ID>', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Mysql mysql = new Mysql(client); + +var status = await mysql.GetStatus( + databaseId: "<DATABASE_ID>" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Mysql mysql = Mysql(client); + +final status = await mysql.getStatus( + databaseId: '<DATABASE_ID>', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Mysql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val mysql = Mysql(client) + +val status = mysql.getStatus( + databaseId = "<DATABASE_ID>", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let mysql = Mysql(client) + +let status = try await mysql.getStatus( + databaseId: "<DATABASE_ID>" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewMysql(client) + + result, err := service.GetStatus("<DATABASE_ID>") + if err != nil { + panic(err) + } + _ = result +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::mysql::Mysql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let mysql = Mysql::new(&client); + + let status = mysql.get_status("<DATABASE_ID>").await?; + + Ok(()) +} +``` +```bash +curl -X GET \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <YOUR_API_KEY>" \ + https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>/status +``` +{% /multicode %} + +For the database lifecycle state (`ready`, `scaling`, `restoring`, and friends), read the `status` field of the database object itself. Use that field from deploy pipelines to wait for the database; see [lifecycle states](/docs/products/databases/mysql/maintenance#states). + +# Inspect activity in the engine {% #activity %} + +MySQL's own instrumentation works unmodified. From the `mysql` client or any driver: + +```sql +-- live connections and their current statements +SELECT id, user, host, db, command, time, state, info +FROM performance_schema.processlist; + +-- kill a runaway connection +KILL <id>; + +-- execution plan for a slow query +EXPLAIN ANALYZE SELECT ...; +``` + +A healthy OLTP database keeps its working set in memory. If throughput drops while disk reads climb, move up a [specification](/docs/products/databases/mysql/scaling) so the buffer pool fits the working set. diff --git a/src/routes/docs/products/databases/mysql/network-security/+page.markdoc b/src/routes/docs/products/databases/mysql/network-security/+page.markdoc new file mode 100644 index 00000000000..576abc37c4d --- /dev/null +++ b/src/routes/docs/products/databases/mysql/network-security/+page.markdoc @@ -0,0 +1,500 @@ +--- +layout: article +title: Network security +description: TLS by default, IP allowlists, and idle timeouts for your MySQL database. +--- + +Every native database is reachable through a unique public hostname, secured with TLS, and protected by network controls that you configure per database. + +# Hostname {% #hostname %} + +Each database gets a stable hostname in the form: + +``` +db-<hash>.<region>.appwrite.center +``` + +The hostname does not change for the lifetime of the database, across restarts, resizes, failovers, and version upgrades. You can copy it from the database response. + +# TLS {% #tls %} + +Connections on Appwrite Cloud are encrypted with TLS, terminated at Appwrite's edge and forwarded to your database over the internal network. Use TLS when connecting to the public hostname. If your driver does not infer TLS from the connection string, configure it to require TLS. + +# IP allowlist {% #ip-allowlist %} + +By default, any host that has your credentials can reach the database over the public internet. To restrict access to known networks, configure an IP allowlist. Connections from addresses outside the allowlist are dropped at the network layer, before authentication. + +From the API, pass CIDR blocks or single addresses: + +{% multicode %} + +```server-nodejs +import { Client, Mysql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.update({ + databaseId: '<DATABASE_ID>', + networkIPAllowlist: ['203.0.113.0/24', '198.51.100.7'], +}); +``` + +```server-deno +import { Client, Mysql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.update({ + databaseId: '<DATABASE_ID>', + networkIPAllowlist: ['203.0.113.0/24', '198.51.100.7'], +}); +``` + +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Mysql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$mysql = new Mysql($client); + +$mysql->update( + databaseId: '<DATABASE_ID>', + networkIPAllowlist: ['203.0.113.0/24', '198.51.100.7'], +); +``` + +```server-python +from appwrite.client import Client +from appwrite.services.mysql import Mysql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +mysql = Mysql(client) + +mysql.update( + database_id='<DATABASE_ID>', + network_i_p_allowlist=['203.0.113.0/24', '198.51.100.7'], +) +``` + +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +mysql = Mysql.new(client) + +mysql.update( + database_id: '<DATABASE_ID>', + network_i_p_allowlist: ['203.0.113.0/24', '198.51.100.7'], +) +``` + +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Mysql mysql = new Mysql(client); + +await mysql.Update( + databaseId: "<DATABASE_ID>", + networkIPAllowlist: new List<string> { "203.0.113.0/24", "198.51.100.7" } +); +``` + +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Mysql mysql = Mysql(client); + +await mysql.update( + databaseId: '<DATABASE_ID>', + networkIPAllowlist: ['203.0.113.0/24', '198.51.100.7'], +); +``` + +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Mysql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val mysql = Mysql(client) + +mysql.update( + databaseId = "<DATABASE_ID>", + networkIPAllowlist = listOf("203.0.113.0/24", "198.51.100.7"), +) +``` + +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let mysql = Mysql(client) + +_ = try await mysql.update( + databaseId: "<DATABASE_ID>", + networkIPAllowlist: ["203.0.113.0/24", "198.51.100.7"] +) +``` + +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/mysql" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewMysql(client) + + _, err := service.Update( + "<DATABASE_ID>", + mysql.WithUpdateNetworkIPAllowlist([]string{"203.0.113.0/24", "198.51.100.7"}), + ) + if err != nil { + panic(err) + } +} +``` + +```server-rust +use appwrite::Client; +use appwrite::services::Mysql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let mysql = Mysql::new(&client); + + mysql.update( + "<DATABASE_ID>", + None, + None, + None, + None, + None, + None, + Some(vec!["203.0.113.0/24".to_string(), "198.51.100.7".to_string()]), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + ).await?; + + Ok(()) +} +``` + +```bash +curl -X PATCH \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + -H "Content-Type: application/json" \ + -d '{ + "networkIPAllowlist": [ + "203.0.113.0/24", + "198.51.100.7" + ] + }' \ + https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID> +``` + +{% /multicode %} + +Rules: + +- Entries are IPv4 or IPv6 addresses or CIDR blocks, up to 100 entries per database. +- An empty allowlist means the database accepts connections from any address. +- The allowlist applies to the database and pooler ports. Appwrite's internal infrastructure, backups, monitoring, and replication are unaffected. + +{% info title="Don't lock yourself out" %} +If you connect from networks with changing addresses (home ISPs, mobile networks, serverless platforms without static egress), an allowlist can block you. Add your serverless provider's egress ranges, or leave the allowlist empty and rely on strong credentials and rotation. +{% /info %} + +# Idle timeout {% #idle-timeout %} + +Connections that stay idle are closed by the edge proxy after `networkIdleTimeoutSeconds`, 900 seconds by default. Long-lived driver pools usually send periodic keepalives and are not affected. Raise the timeout if you hold connections open across long pauses; lower it to reclaim connection slots faster. + +{% multicode %} + +```server-nodejs +import { Client, Mysql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.update({ + databaseId: '<DATABASE_ID>', + networkIdleTimeoutSeconds: 900, +}); +``` + +```server-deno +import { Client, Mysql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.update({ + databaseId: '<DATABASE_ID>', + networkIdleTimeoutSeconds: 900, +}); +``` + +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Mysql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$mysql = new Mysql($client); + +$mysql->update( + databaseId: '<DATABASE_ID>', + networkIdleTimeoutSeconds: 900, +); +``` + +```server-python +from appwrite.client import Client +from appwrite.services.mysql import Mysql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +mysql = Mysql(client) + +mysql.update( + database_id='<DATABASE_ID>', + network_idle_timeout_seconds=900, +) +``` + +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +mysql = Mysql.new(client) + +mysql.update( + database_id: '<DATABASE_ID>', + network_idle_timeout_seconds: 900, +) +``` + +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Mysql mysql = new Mysql(client); + +await mysql.Update( + databaseId: "<DATABASE_ID>", + networkIdleTimeoutSeconds: 900 +); +``` + +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Mysql mysql = Mysql(client); + +await mysql.update( + databaseId: '<DATABASE_ID>', + networkIdleTimeoutSeconds: 900, +); +``` + +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Mysql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val mysql = Mysql(client) + +mysql.update( + databaseId = "<DATABASE_ID>", + networkIdleTimeoutSeconds = 900, +) +``` + +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let mysql = Mysql(client) + +_ = try await mysql.update( + databaseId: "<DATABASE_ID>", + networkIdleTimeoutSeconds: 900 +) +``` + +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/mysql" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewMysql(client) + + _, err := service.Update( + "<DATABASE_ID>", + mysql.WithUpdateNetworkIdleTimeoutSeconds(900), + ) + if err != nil { + panic(err) + } +} +``` + +```server-rust +use appwrite::Client; +use appwrite::services::Mysql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let mysql = Mysql::new(&client); + + mysql.update("<DATABASE_ID>", None, None, None, None, None, Some(900), None, None, None, None, None, None, None, None, None, None, None, None, None, None).await?; + + Ok(()) +} +``` + +```bash +curl -X PATCH \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + -H "Content-Type: application/json" \ + -d '{ + "networkIdleTimeoutSeconds": 900 + }' \ + https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID> +``` + +{% /multicode %} + +# Locking down access {% #checklist %} + +For a production database: + +1. Set an IP allowlist covering only your application's egress addresses. +2. Rotate the [primary password](/docs/products/databases/mysql/connections#rotate) on a schedule, and after anyone with access leaves your team. +3. Watch [connection activity](/docs/products/databases/mysql/monitoring#activity) for unexpected clients. diff --git a/src/routes/docs/products/databases/mysql/quick-start/+page.markdoc b/src/routes/docs/products/databases/mysql/quick-start/+page.markdoc new file mode 100644 index 00000000000..ffac64735be --- /dev/null +++ b/src/routes/docs/products/databases/mysql/quick-start/+page.markdoc @@ -0,0 +1,104 @@ +--- +layout: article +title: Quick start +description: Create your first native MySQL database in the Appwrite Console, retrieve its credentials, and run your first queries with the mysql client. +--- + +You can create a MySQL database and run your first query in a few minutes. + +# Create a database {% #create %} + +1. In your project, go to **Databases**. +2. Click **Create database**. +3. Give your database a name, and optionally a custom database ID. +4. Under **Choose database type**, select **MySQL** from the **Native databases** group. + +{% only_dark %} +![Create database type selection](/images/docs/products/databases/mysql/dark/create-database-type.avif) +{% /only_dark %} +{% only_light %} +![Create database type selection](/images/docs/products/databases/mysql/create-database-type.avif) +{% /only_light %} + +5. Under **Specifications**, select your preferred tier. +6. Optionally configure **Read replicas** and **Point-in-time recovery (PITR)**. You can change both later. + +{% only_dark %} +![Database specifications](/images/docs/products/databases/mysql/dark/create-database-specs.avif) +{% /only_dark %} +{% only_light %} +![Database specifications](/images/docs/products/databases/mysql/create-database-specs.avif) +{% /only_light %} + +7. Review the database summary and click **Create database**. + +Your database starts provisioning and becomes `ready` shortly after. + +# Get your connection details {% #credentials %} + +The connection details are returned on the database object. Fetch the database with an API key that has the `databases.read` scope: + +```server-nodejs +import { Client, Mysql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +const database = await mysql.get({ + databaseId: '<DATABASE_ID>', +}); + +console.log(database.connectionString); +``` + +The response includes the hostname, port, username, password, and the generated database name, plus a ready-made connection string in the form `mysql://admin:<password>@db-<hash>.<region>.appwrite.center:3306/<database>`. See [Connections](/docs/products/databases/mysql/connections) for the full response and every language. + +# Connect with the mysql client {% #mysql-client %} + +Pass the connection details to the `mysql` command-line client: + +```bash +mysql -h db-<hash>.<region>.appwrite.center -P 3306 -u admin -p -D <database> +``` + +Enter the password when prompted. + +# Run your first queries {% #first-queries %} + +Create a table, insert a row, and read it back: + +```sql +CREATE TABLE books ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + title TEXT NOT NULL, + author TEXT NOT NULL +); + +INSERT INTO books (title, author) +VALUES ('The Hitchhiker''s Guide to the Galaxy', 'Douglas Adams'); + +SELECT * FROM books; +``` + +You can run the same queries from your application with any MySQL driver. See [Connections](/docs/products/databases/mysql/connections) for driver examples. + +# Next steps {% #next-steps %} + +{% cards %} +{% cards_item href="/docs/products/databases/mysql/connections" title="Connections" %} +Connect from your application with any MySQL driver or ORM. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/backups" title="Backups" %} +Configure backup policies and point-in-time recovery. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/high-availability" title="High availability" %} +Add read replicas with automatic failover. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql/monitoring" title="Monitoring" %} +Poll live health and status information from your pipelines. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/mysql/scaling/+page.markdoc b/src/routes/docs/products/databases/mysql/scaling/+page.markdoc new file mode 100644 index 00000000000..5506e47060e --- /dev/null +++ b/src/routes/docs/products/databases/mysql/scaling/+page.markdoc @@ -0,0 +1,649 @@ +--- +layout: article +title: Scaling +description: Resize the compute specification of your MySQL database with zero downtime and grow storage automatically as your data grows. +--- + +Native databases scale in two dimensions: the compute specification (CPU, memory, and connection limit) and storage. Both can change after creation, without dump-and-restore migrations. + +# List available specifications {% #specifications %} + +Each database runs against a specification that defines its CPU, memory, included storage, and maximum connections. List the specifications available to your plan: + +{% multicode %} +```server-nodejs +import { Client, Mysql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +const specifications = await mysql.listSpecifications({ + +}); +``` +```server-deno +import { Client, Mysql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +const specifications = await mysql.listSpecifications({ + +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Mysql; + +$client = (new Client()) + ->setEndpoint('https://cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$mysql = new Mysql($client); + +$specifications = $mysql->listSpecifications( + +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.mysql import Mysql + +client = Client() +client.set_endpoint('https://cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +mysql = Mysql(client) + +specifications = mysql.list_specifications( + +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +mysql = Mysql.new(client) + +specifications = mysql.list_specifications( + +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Mysql mysql = new Mysql(client); + +var specifications = await mysql.ListSpecifications( + +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Mysql mysql = Mysql(client); + +final specifications = await mysql.listSpecifications( + +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Mysql + +val client = Client() + .setEndpoint("https://cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val mysql = Mysql(client) + +val specifications = mysql.listSpecifications( + +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let mysql = Mysql(client) + +let specifications = try await mysql.listSpecifications( + +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewMysql(client) + + result, err := service.ListSpecifications() + if err != nil { + panic(err) + } + _ = result +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::mysql::Mysql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let mysql = Mysql::new(&client); + + let specifications = mysql.list_specifications().await?; + + Ok(()) +} +``` +```bash +curl -X GET \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <YOUR_API_KEY>" \ + https://cloud.appwrite.io/v1/mysql/specifications +``` +{% /multicode %} + +# Change the compute specification {% #resize %} + +From the API, pass the new specification ID: + +{% multicode %} +```server-nodejs +import { Client, Mysql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.update({ + databaseId: '<DATABASE_ID>', + specification: '<SPECIFICATION>', +}); +``` +```server-deno +import { Client, Mysql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.update({ + databaseId: '<DATABASE_ID>', + specification: '<SPECIFICATION>', +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Mysql; + +$client = (new Client()) + ->setEndpoint('https://cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$mysql = new Mysql($client); + +$mysql->update( + databaseId: '<DATABASE_ID>', + specification: '<SPECIFICATION>', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.mysql import Mysql + +client = Client() +client.set_endpoint('https://cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +mysql = Mysql(client) + +mysql.update( + database_id='<DATABASE_ID>', + specification='<SPECIFICATION>', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +mysql = Mysql.new(client) + +mysql.update( + database_id: '<DATABASE_ID>', + specification: '<SPECIFICATION>', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Mysql mysql = new Mysql(client); + +await mysql.Update( + databaseId: "<DATABASE_ID>", + specification: "<SPECIFICATION>" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Mysql mysql = Mysql(client); + +await mysql.update( + databaseId: '<DATABASE_ID>', + specification: '<SPECIFICATION>', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Mysql + +val client = Client() + .setEndpoint("https://cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val mysql = Mysql(client) + +mysql.update( + databaseId = "<DATABASE_ID>", + specification = "<SPECIFICATION>", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let mysql = Mysql(client) + +_ = try await mysql.update( + databaseId: "<DATABASE_ID>", + specification: "<SPECIFICATION>" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/mysql" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewMysql(client) + + _, err := service.Update( + "<DATABASE_ID>", + mysql.WithUpdateSpecification("<SPECIFICATION>"), + ) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::mysql::Mysql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let mysql = Mysql::new(&client); + + mysql.update("<DATABASE_ID>", None, None, Some("<SPECIFICATION>"), None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None).await?; + + Ok(()) +} +``` +```bash +curl -X PATCH \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <YOUR_API_KEY>" \ + -H "Content-Type: application/json" \ + -d '{ + "specification": "<SPECIFICATION>" + }' \ + https://cloud.appwrite.io/v1/mysql/<DATABASE_ID> +``` +{% /multicode %} + +Resizes apply with zero downtime through a rolling cutover: a new instance is provisioned on the target specification, data is streamed over, and traffic cuts over once it has caught up. The database status shows `scaling` while the resize is in progress. + +# Storage {% #storage %} + +Each specification includes a storage allowance, and storage beyond the allowance is billed per GB. Storage only grows; you cannot shrink a database's storage after it has expanded. To reclaim a smaller footprint, restore a [backup](/docs/products/databases/mysql/backups) into a new database. + +# Storage autoscaling {% #autoscaling %} + +With storage autoscaling enabled, Appwrite grows the storage automatically when usage crosses a threshold, so the database never hits a full disk. Configure it through the API: + +{% multicode %} +```server-nodejs +import { Client, Mysql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.update({ + databaseId: '<DATABASE_ID>', + storageAutoscaling: true, + storageAutoscalingThresholdPercent: 85, + storageAutoscalingMaxGb: 500, +}); +``` +```server-deno +import { Client, Mysql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const mysql = new Mysql(client); + +await mysql.update({ + databaseId: '<DATABASE_ID>', + storageAutoscaling: true, + storageAutoscalingThresholdPercent: 85, + storageAutoscalingMaxGb: 500, +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Mysql; + +$client = (new Client()) + ->setEndpoint('https://cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$mysql = new Mysql($client); + +$mysql->update( + databaseId: '<DATABASE_ID>', + storageAutoscaling: true, + storageAutoscalingThresholdPercent: 85, + storageAutoscalingMaxGb: 500, +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.mysql import Mysql + +client = Client() +client.set_endpoint('https://cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +mysql = Mysql(client) + +mysql.update( + database_id='<DATABASE_ID>', + storage_autoscaling=True, + storage_autoscaling_threshold_percent=85, + storage_autoscaling_max_gb=500, +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +mysql = Mysql.new(client) + +mysql.update( + database_id: '<DATABASE_ID>', + storage_autoscaling: true, + storage_autoscaling_threshold_percent: 85, + storage_autoscaling_max_gb: 500, +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Mysql mysql = new Mysql(client); + +await mysql.Update( + databaseId: "<DATABASE_ID>", + storageAutoscaling: true, + storageAutoscalingThresholdPercent: 85, + storageAutoscalingMaxGb: 500 +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Mysql mysql = Mysql(client); + +await mysql.update( + databaseId: '<DATABASE_ID>', + storageAutoscaling: true, + storageAutoscalingThresholdPercent: 85, + storageAutoscalingMaxGb: 500, +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Mysql + +val client = Client() + .setEndpoint("https://cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val mysql = Mysql(client) + +mysql.update( + databaseId = "<DATABASE_ID>", + storageAutoscaling = true, + storageAutoscalingThresholdPercent = 85, + storageAutoscalingMaxGb = 500, +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let mysql = Mysql(client) + +_ = try await mysql.update( + databaseId: "<DATABASE_ID>", + storageAutoscaling: true, + storageAutoscalingThresholdPercent: 85, + storageAutoscalingMaxGb: 500 +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/mysql" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewMysql(client) + + _, err := service.Update( + "<DATABASE_ID>", + mysql.WithUpdateStorageAutoscaling(true), + mysql.WithUpdateStorageAutoscalingThresholdPercent(85), + mysql.WithUpdateStorageAutoscalingMaxGb(500), + ) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::mysql::Mysql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let mysql = Mysql::new(&client); + + mysql.update("<DATABASE_ID>", None, None, None, None, None, None, None, None, None, None, Some(true), Some(85), Some(500), None, None, None, None, None, None, None).await?; + + Ok(()) +} +``` +```bash +curl -X PATCH \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <YOUR_API_KEY>" \ + -H "Content-Type: application/json" \ + -d '{ + "storageAutoscaling": true, + "storageAutoscalingThresholdPercent": 85, + "storageAutoscalingMaxGb": 500 + }' \ + https://cloud.appwrite.io/v1/mysql/<DATABASE_ID> +``` +{% /multicode %} + +| Parameter | Range | Description | +|---------------------------------------|--------------------|-----------------------------------------------------------| +| `storageAutoscaling` | boolean | Enable automatic storage growth | +| `storageAutoscalingThresholdPercent` | 50 - 95 | Usage percentage that triggers an expansion (default 85) | +| `storageAutoscalingMaxGb` | integer, 0 = no cap | Upper bound for automatic growth | + +Set a cap if you want a hard ceiling on storage cost; without one, autoscaling grows storage as needed and the overage is billed per GB. + +# Picking a specification {% #picking %} + +Guidelines for choosing a starting tier: + +- **Connections**: count the maximum concurrent connections your application opens, including all replicas of your app server. If it exceeds the specification's connection cap, either move up a tier or put the [connection pooler](/docs/products/databases/mysql/connection-pooling) in front. +- **Memory**: MySQL performs best when the working set fits in memory. Track cache hit ratio with database metrics; a sustained ratio below ~99% for an OLTP workload is a sign to add memory. +- **CPU**: sustained CPU above 70-80% at normal load leaves no headroom for spikes, migrations, or backups. + +Start small and resize up when the metrics say so; resizes are online, so there is no penalty for growing later. diff --git a/src/routes/docs/products/databases/postgresql/+layout.svelte b/src/routes/docs/products/databases/postgresql/+layout.svelte new file mode 100644 index 00000000000..1ae57a78eb2 --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/+layout.svelte @@ -0,0 +1,156 @@ +<script lang="ts"> + import Docs from '$lib/layouts/Docs.svelte'; + import Sidebar, { type NavParent, type NavTree } from '$lib/layouts/Sidebar.svelte'; + + let { children } = $props(); + + const parent: NavParent = { + href: '/docs/products/databases', + label: 'PostgreSQL' + }; + + const navigation: NavTree = [ + { + label: 'Getting started', + items: [ + { + label: 'Overview', + href: '/docs/products/databases/postgresql' + }, + { + label: 'Quick start', + href: '/docs/products/databases/postgresql/quick-start' + } + ] + }, + { + label: 'Connecting', + items: [ + { + label: 'Connections', + href: '/docs/products/databases/postgresql/connections' + }, + { + label: 'Connection pooling', + href: '/docs/products/databases/postgresql/connection-pooling' + } + ] + }, + { + label: 'Manage', + items: [ + { + label: 'Extensions', + href: '/docs/products/databases/postgresql/extensions' + }, + { + label: 'Backups', + href: '/docs/products/databases/postgresql/backups' + }, + { + label: 'Branches', + href: '/docs/products/databases/postgresql/branches' + }, + { + label: 'High availability', + href: '/docs/products/databases/postgresql/high-availability' + }, + { + label: 'Scaling', + href: '/docs/products/databases/postgresql/scaling' + }, + { + label: 'Network security', + href: '/docs/products/databases/postgresql/network-security' + }, + { + label: 'Monitoring', + href: '/docs/products/databases/postgresql/monitoring' + }, + { + label: 'Maintenance', + href: '/docs/products/databases/postgresql/maintenance' + } + ] + }, + { + label: 'Integrations', + items: [ + { + label: 'Node.js drivers', + href: '/docs/products/databases/postgresql/integrations/drivers' + }, + { + label: 'Prisma', + href: '/docs/products/databases/postgresql/integrations/prisma' + }, + { + label: 'Drizzle', + href: '/docs/products/databases/postgresql/integrations/drizzle' + }, + { + label: 'Auth.js', + href: '/docs/products/databases/postgresql/integrations/auth-js' + }, + { + label: 'Better Auth', + href: '/docs/products/databases/postgresql/integrations/better-auth' + }, + { + label: 'Laravel', + href: '/docs/products/databases/postgresql/integrations/laravel' + }, + { + label: 'Rails', + href: '/docs/products/databases/postgresql/integrations/rails' + }, + { + label: 'Django', + href: '/docs/products/databases/postgresql/integrations/django' + }, + { + label: 'FastAPI', + href: '/docs/products/databases/postgresql/integrations/fastapi' + }, + { + label: 'Spring Boot', + href: '/docs/products/databases/postgresql/integrations/spring-boot' + }, + { + label: 'EF Core', + href: '/docs/products/databases/postgresql/integrations/ef-core' + }, + { + label: 'GORM', + href: '/docs/products/databases/postgresql/integrations/gorm' + }, + { + label: 'Next.js', + href: '/docs/products/databases/postgresql/integrations/nextjs' + }, + { + label: 'dbt', + href: '/docs/products/databases/postgresql/integrations/dbt' + }, + { + label: 'Metabase', + href: '/docs/products/databases/postgresql/integrations/metabase' + }, + { + label: 'Grafana', + href: '/docs/products/databases/postgresql/integrations/grafana' + }, + { + label: 'Retool', + href: '/docs/products/databases/postgresql/integrations/retool' + } + ] + } + ]; +</script> + +<Docs variant="two-side-navs"> + <Sidebar {navigation} {parent} /> + + {@render children()} +</Docs> diff --git a/src/routes/docs/products/databases/postgresql/+page.markdoc b/src/routes/docs/products/databases/postgresql/+page.markdoc new file mode 100644 index 00000000000..1b4f62973a1 --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/+page.markdoc @@ -0,0 +1,108 @@ +--- +layout: article +title: PostgreSQL +description: Run a dedicated, native PostgreSQL database provisioned for your project and connect to it directly with standard PostgreSQL clients. +--- + +Appwrite native PostgreSQL databases give you a managed PostgreSQL instance provisioned for your project. You pick the compute specification, and Appwrite provisions the engine in your project's region with its own storage, networking, and credentials, exposed through a per-database public hostname secured with TLS. + +Native databases are different from Appwrite databases like [TablesDB](/docs/products/databases/tablesdb), [DocumentsDB](/docs/products/databases/documentsdb), and [VectorsDB](/docs/products/databases/vectorsdb), which are accessed through Appwrite SDKs and platform APIs. A native PostgreSQL database gives you the raw engine. You connect with `psql` or any PostgreSQL driver and use the full feature set of PostgreSQL, with no Appwrite abstraction layer in between. + +{% info title="Looking for Appwrite data APIs?" %} +If you want Appwrite SDKs, platform permissions, and serverless scaling for app data, start with [Appwrite Databases](/docs/products/databases). Native PostgreSQL is for teams who want a managed PostgreSQL engine they can talk to directly with their own ORM, migrations, and drivers. +{% /info %} + +# Supported versions {% #versions %} + +Appwrite manages the database container, storage, backups, and networking. You bring the application. + +| Engine | Default version | Other supported versions | Default port | +|------------|-----------------|--------------------------|--------------| +| PostgreSQL | 18 | 17 | 5432 | + +The version is selected on create and can be upgraded later. Upgrades run online: a second instance is provisioned on the new version, data is streamed over with logical replication, and traffic cuts over once replication is caught up, with no read or write outages and no manual dump-and-restore. + +A new database starts in a `provisioning` state and becomes `ready` within a few minutes. Large specifications or high-availability configurations take longer to schedule; poll the database status or watch the Console until it reports `ready`. + +# Regions {% #regions %} + +A native database lives in the same region as the project that owns it. There is no per-database region selector. Each database gets a unique hostname in the form `db-<hash>.<region>.appwrite.center`, and data does not leave the region. + +# Feature overview {% #features %} + +{% cards %} +{% cards_item href="/docs/products/databases/postgresql/connections" title="Connections" %} +Connect with `psql` or any driver. Credentials are rotatable from the Console and API. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/connection-pooling" title="Connection pooling" %} +Per-database connection pooler with automatic read/write split when high availability is enabled. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/extensions" title="Extensions" %} +Install PostgreSQL extensions like PostGIS, pgvector, and pg_trgm. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/backups" title="Backups" %} +Scheduled backups, manual backups, restores, and point-in-time recovery. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/branches" title="Branches" %} +Spin up an ephemeral copy of your database in seconds from a storage snapshot. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/high-availability" title="High availability" %} +Up to five synchronous, semi-synchronous, or asynchronous replicas with automatic failover. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/scaling" title="Scaling" %} +Resize compute online and grow storage automatically as your data grows. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/network-security" title="Network security" %} +TLS by default, optional IP allowlists, and a dedicated hostname per database. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/monitoring" title="Monitoring" %} +Live compute, connection, storage, and workload metrics, with an active-connections inspector. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/maintenance" title="Maintenance" %} +Maintenance windows, online version upgrades, and pause and resume. +{% /cards_item %} +{% /cards %} + +# Specifications and pricing {% #specifications %} + +Each database runs against one of the following compute specifications, billed monthly: + +| Tier | Specification | CPU | Memory | Storage | Bandwidth | Connections | Price | +|-----------------|----------------|---------|--------|---------|-----------|-------------|---------| +| Starter | `s-1vcpu-1gb` | 1 core | 1 GB | 10 GB | 50 GB | 100 | $10/mo | +| Standard | `s-2vcpu-2gb` | 2 cores | 2 GB | 25 GB | 200 GB | 200 | $20/mo | +| Standard Plus | `s-2vcpu-4gb` | 2 cores | 4 GB | 50 GB | 400 GB | 500 | $49/mo | +| Professional | `s-4vcpu-8gb` | 4 cores | 8 GB | 200 GB | 1 TB | 1,000 | $85/mo | +| Business | `s-4vcpu-16gb` | 4 cores | 16 GB | 500 GB | 2 TB | 2,000 | $160/mo | +| Business Plus | `s-4vcpu-32gb` | 4 cores | 32 GB | 1 TB | 5 TB | 4,000 | $299/mo | +| Enterprise | `s-8vcpu-32gb` | 8 cores | 32 GB | 2 TB | 7.5 TB | 5,000 | $425/mo | +| Enterprise Plus | `s-8vcpu-64gb` | 8 cores | 64 GB | 3 TB | 10 TB | 10,000 | $699/mo | + +The Storage and Bandwidth columns are the monthly allowances included with each tier. Usage beyond them is billed as overage, and optional features are billed as add-ons on top of the tier price: + +| Add-on | Price | +|----------------------------|---------------------------------| +| Storage overage | $0.125 per GB per month | +| Bandwidth overage | $0.08 per GB per month | +| High availability replica | 50% of the tier price, per replica | +| Point-in-time recovery | 20% of the tier price | + +You can [resize between tiers](/docs/products/databases/postgresql/scaling) at any time with zero downtime. + +# Limits {% #limits %} + +The following limits apply per database: + +| Limit | Value | +|--------------------------------|-----------------------------------------------------------| +| Databases per project | 10 | +| High availability replicas | 0 - 5 | +| IP allowlist entries | 100 | +| Backup retention | 1 - 365 days | +| Point-in-time recovery window | 1 - 35 days | +| Extensions installed | 50 | +| Max simultaneous connections | 10,000 platform cap, lower on smaller specifications | + +# Billing and plan requirements {% #plans %} + +Native databases are available on paid plans, and a payment method must be attached to your organization. Each database is billed by the hour against its [compute specification](#specifications), with separate line items for overages and add-ons. See [pricing](/pricing) for details. diff --git a/src/routes/docs/products/databases/postgresql/backups/+page.markdoc b/src/routes/docs/products/databases/postgresql/backups/+page.markdoc new file mode 100644 index 00000000000..22e538a2714 --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/backups/+page.markdoc @@ -0,0 +1,1299 @@ +--- +layout: article +title: Backups +description: Scheduled backups, manual backups, restores, and point-in-time recovery for your PostgreSQL database. +--- + +Native databases are backed up automatically. Backups are stored off the database instance and restorable from the API. For finer recovery granularity than scheduled backups, enable point-in-time recovery. + +# Automatic backups {% #automatic %} + +Every database gets a default backup policy when it is provisioned, so you have scheduled backups from day one. You can adjust the default policy, or add more policies with different schedules and retention windows. + +# Backup policies {% #policies %} + +A policy defines a schedule (cron expression) and a retention period in days. Create one with: + +{% multicode %} +```server-nodejs +import { Client, Postgresql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.createBackupPolicy({ + databaseId: '<DATABASE_ID>', + policyId: 'hourly', + name: 'Hourly', + schedule: '0 * * * *', + retention: 30, +}); +``` +```server-deno +import { Client, Postgresql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.createBackupPolicy({ + databaseId: '<DATABASE_ID>', + policyId: 'hourly', + name: 'Hourly', + schedule: '0 * * * *', + retention: 30, +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Postgresql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$postgresql = new Postgresql($client); + +$postgresql->createBackupPolicy( + databaseId: '<DATABASE_ID>', + policyId: 'hourly', + name: 'Hourly', + schedule: '0 * * * *', + retention: 30, +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.postgresql import Postgresql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +postgresql = Postgresql(client) + +postgresql.create_backup_policy( + database_id='<DATABASE_ID>', + policy_id='hourly', + name='Hourly', + schedule='0 * * * *', + retention=30, +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +postgresql = Postgresql.new(client) + +postgresql.create_backup_policy( + database_id: '<DATABASE_ID>', + policy_id: 'hourly', + name: 'Hourly', + schedule: '0 * * * *', + retention: 30, +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Postgresql postgresql = new Postgresql(client); + +await postgresql.CreateBackupPolicy( + databaseId: "<DATABASE_ID>", + policyId: "hourly", + name: "Hourly", + schedule: "0 * * * *", + retention: 30 +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Postgresql postgresql = Postgresql(client); + +await postgresql.createBackupPolicy( + databaseId: '<DATABASE_ID>', + policyId: 'hourly', + name: 'Hourly', + schedule: '0 * * * *', + retention: 30, +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Postgresql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val postgresql = Postgresql(client) + +postgresql.createBackupPolicy( + databaseId = "<DATABASE_ID>", + policyId = "hourly", + name = "Hourly", + schedule = "0 * * * *", + retention = 30, +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let postgresql = Postgresql(client) + +_ = try await postgresql.createBackupPolicy( + databaseId: "<DATABASE_ID>", + policyId: "hourly", + name: "Hourly", + schedule: "0 * * * *", + retention: 30 +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewPostgresql(client) + + _, err := service.CreateBackupPolicy("<DATABASE_ID>", "hourly", "Hourly", "0 * * * *", 30) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::postgresql::Postgresql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let postgresql = Postgresql::new(&client); + + postgresql.create_backup_policy("<DATABASE_ID>", "hourly", "Hourly", "0 * * * *", "<ETENTION>", None, None).await?; + + Ok(()) +} +``` +```bash +curl -X POST \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + -H "Content-Type: application/json" \ + -d '{ + "policyId": "hourly", + "name": "Hourly", + "schedule": "0 * * * *", + "retention": 30 + }' \ + https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/backups/policies +``` +{% /multicode %} + +| Parameter | Value | Description | +|-------------|-------------------------------------|-----------------------------------------------| +| `policyId` | custom ID or `unique()` | Policy identifier | +| `name` | text | Display name | +| `schedule` | cron expression | When backups run, for example `0 3 * * *` | +| `retention` | 1 - 365 days | How long backups from this policy are kept | + +List, update, and delete policies with `listBackupPolicies`, `updateBackupPolicy`, and `deleteBackupPolicy`. The number of policies you can create depends on your plan. + +# Manual backups {% #manual %} + +Take an on-demand backup before a risky change: + +{% multicode %} +```server-nodejs +import { Client, Postgresql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +const backup = await postgresql.createBackup({ + databaseId: '<DATABASE_ID>', +}); +``` +```server-deno +import { Client, Postgresql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +const backup = await postgresql.createBackup({ + databaseId: '<DATABASE_ID>', +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Postgresql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$postgresql = new Postgresql($client); + +$backup = $postgresql->createBackup( + databaseId: '<DATABASE_ID>', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.postgresql import Postgresql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +postgresql = Postgresql(client) + +backup = postgresql.create_backup( + database_id='<DATABASE_ID>', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +postgresql = Postgresql.new(client) + +backup = postgresql.create_backup( + database_id: '<DATABASE_ID>', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Postgresql postgresql = new Postgresql(client); + +var backup = await postgresql.CreateBackup( + databaseId: "<DATABASE_ID>" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Postgresql postgresql = Postgresql(client); + +final backup = await postgresql.createBackup( + databaseId: '<DATABASE_ID>', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Postgresql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val postgresql = Postgresql(client) + +val backup = postgresql.createBackup( + databaseId = "<DATABASE_ID>", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let postgresql = Postgresql(client) + +let backup = try await postgresql.createBackup( + databaseId: "<DATABASE_ID>" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewPostgresql(client) + + result, err := service.CreateBackup("<DATABASE_ID>") + if err != nil { + panic(err) + } + _ = result +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::postgresql::Postgresql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let postgresql = Postgresql::new(&client); + + let backup = postgresql.create_backup("<DATABASE_ID>", None).await?; + + Ok(()) +} +``` +```bash +curl -X POST \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/backups +``` +{% /multicode %} + +The backup runs asynchronously with status `pending` until it completes. List backups and check their status with `listBackups`, or fetch one with `getBackup`. + +# Restore from a backup {% #restore %} + +Restoring replaces the database's current data with the backup's contents. The database status moves to `restoring` and returns to `ready` when the restore completes; connections are unavailable during the restore. + +{% multicode %} +```server-nodejs +import { Client, Postgresql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.createRestoration({ + databaseId: '<DATABASE_ID>', + type: 'backup', + backupId: '<BACKUP_ID>', +}); +``` +```server-deno +import { Client, Postgresql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.createRestoration({ + databaseId: '<DATABASE_ID>', + type: 'backup', + backupId: '<BACKUP_ID>', +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Postgresql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$postgresql = new Postgresql($client); + +$postgresql->createRestoration( + databaseId: '<DATABASE_ID>', + type: 'backup', + backupId: '<BACKUP_ID>', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.postgresql import Postgresql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +postgresql = Postgresql(client) + +postgresql.create_restoration( + database_id='<DATABASE_ID>', + type='backup', + backup_id='<BACKUP_ID>', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +postgresql = Postgresql.new(client) + +postgresql.create_restoration( + database_id: '<DATABASE_ID>', + type: 'backup', + backup_id: '<BACKUP_ID>', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Postgresql postgresql = new Postgresql(client); + +await postgresql.CreateRestoration( + databaseId: "<DATABASE_ID>", + type: "backup", + backupId: "<BACKUP_ID>" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Postgresql postgresql = Postgresql(client); + +await postgresql.createRestoration( + databaseId: '<DATABASE_ID>', + type: 'backup', + backupId: '<BACKUP_ID>', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Postgresql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val postgresql = Postgresql(client) + +postgresql.createRestoration( + databaseId = "<DATABASE_ID>", + type = "backup", + backupId = "<BACKUP_ID>", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let postgresql = Postgresql(client) + +_ = try await postgresql.createRestoration( + databaseId: "<DATABASE_ID>", + type: "backup", + backupId: "<BACKUP_ID>" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/postgresql" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewPostgresql(client) + + _, err := service.CreateRestoration( + "<DATABASE_ID>", + postgresql.WithCreateRestorationType("backup"), + postgresql.WithCreateRestorationBackupId("<BACKUP_ID>"), + ) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::postgresql::Postgresql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let postgresql = Postgresql::new(&client); + + postgresql.create_restoration("<DATABASE_ID>", Some("backup"), Some("<BACKUP_ID>"), None).await?; + + Ok(()) +} +``` +```bash +curl -X POST \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "backup" + }' \ + https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/restorations +``` +{% /multicode %} + +Track progress with `getRestoration` or the database status. + +{% info title="Restores overwrite current data" %} +Everything written after the backup was taken is lost when you restore it. If you need the current state too, take a manual backup first, or use a [branch](/docs/products/databases/postgresql/branches) to inspect data without touching the live database. +{% /info %} + +# Point-in-time recovery {% #pitr %} + +{% only_dark %} +![Point-in-time recovery settings](/images/docs/products/databases/postgresql/dark/settings-pitr.avif) +{% /only_dark %} +{% only_light %} +![Point-in-time recovery settings](/images/docs/products/databases/postgresql/settings-pitr.avif) +{% /only_light %} + +Scheduled backups recover to fixed snapshots. Point-in-time recovery (PITR) continuously archives the write-ahead log, so you can restore to any moment inside the retention window, for example the second before a bad migration ran. + +Enable PITR when creating the database, from **Settings** > **PITR** in the Console, or through the API: + +{% multicode %} +```server-nodejs +import { Client, Postgresql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.update({ + databaseId: '<DATABASE_ID>', + pitr: true, + pitrRetentionDays: 7, +}); +``` +```server-deno +import { Client, Postgresql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.update({ + databaseId: '<DATABASE_ID>', + pitr: true, + pitrRetentionDays: 7, +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Postgresql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$postgresql = new Postgresql($client); + +$postgresql->update( + databaseId: '<DATABASE_ID>', + pitr: true, + pitrRetentionDays: 7, +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.postgresql import Postgresql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +postgresql = Postgresql(client) + +postgresql.update( + database_id='<DATABASE_ID>', + pitr=True, + pitr_retention_days=7, +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +postgresql = Postgresql.new(client) + +postgresql.update( + database_id: '<DATABASE_ID>', + pitr: true, + pitr_retention_days: 7, +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Postgresql postgresql = new Postgresql(client); + +await postgresql.Update( + databaseId: "<DATABASE_ID>", + pitr: true, + pitrRetentionDays: 7 +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Postgresql postgresql = Postgresql(client); + +await postgresql.update( + databaseId: '<DATABASE_ID>', + pitr: true, + pitrRetentionDays: 7, +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Postgresql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val postgresql = Postgresql(client) + +postgresql.update( + databaseId = "<DATABASE_ID>", + pitr = true, + pitrRetentionDays = 7, +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let postgresql = Postgresql(client) + +_ = try await postgresql.update( + databaseId: "<DATABASE_ID>", + pitr: true, + pitrRetentionDays: 7 +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/postgresql" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewPostgresql(client) + + _, err := service.Update( + "<DATABASE_ID>", + postgresql.WithUpdatePitr(true), + postgresql.WithUpdatePitrRetentionDays(7), + ) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::postgresql::Postgresql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let postgresql = Postgresql::new(&client); + + postgresql.update("<DATABASE_ID>", None, None, None, None, None, None, None, None, Some(true), Some(7), None, None, None, None, None, None, None, None, None, None).await?; + + Ok(()) +} +``` +```bash +curl -X PATCH \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + -H "Content-Type: application/json" \ + -d '{ + "pitr": true, + "pitrRetentionDays": 7 + }' \ + https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID> +``` +{% /multicode %} + +`pitrRetentionDays` accepts 1 to 35 days. PITR is billed as an add-on on top of your specification; see [pricing](/pricing). + +## Check the recovery window {% #pitr-windows %} + +You can retrieve the time ranges you can restore to. Right after enabling PITR, no recovery window is available yet; the window opens once continuous archiving has captured its first segment. + +{% multicode %} +```server-nodejs +import { Client, Postgresql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +const windows = await postgresql.getPitr({ + databaseId: '<DATABASE_ID>', +}); +``` +```server-deno +import { Client, Postgresql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +const windows = await postgresql.getPitr({ + databaseId: '<DATABASE_ID>', +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Postgresql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$postgresql = new Postgresql($client); + +$windows = $postgresql->getPitr( + databaseId: '<DATABASE_ID>', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.postgresql import Postgresql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +postgresql = Postgresql(client) + +windows = postgresql.get_pitr( + database_id='<DATABASE_ID>', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +postgresql = Postgresql.new(client) + +windows = postgresql.get_pitr( + database_id: '<DATABASE_ID>', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Postgresql postgresql = new Postgresql(client); + +var windows = await postgresql.GetPitr( + databaseId: "<DATABASE_ID>" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Postgresql postgresql = Postgresql(client); + +final windows = await postgresql.getPitr( + databaseId: '<DATABASE_ID>', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Postgresql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val postgresql = Postgresql(client) + +val windows = postgresql.getPitr( + databaseId = "<DATABASE_ID>", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let postgresql = Postgresql(client) + +let windows = try await postgresql.getPitr( + databaseId: "<DATABASE_ID>" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewPostgresql(client) + + result, err := service.GetPitr("<DATABASE_ID>") + if err != nil { + panic(err) + } + _ = result +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::postgresql::Postgresql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let postgresql = Postgresql::new(&client); + + let windows = postgresql.get_pitr("<DATABASE_ID>").await?; + + Ok(()) +} +``` +```bash +curl -X GET \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/pitr +``` +{% /multicode %} + +## Restore to a point in time {% #pitr-restore %} + +Pass a Unix timestamp inside the recovery window: + +{% multicode %} +```server-nodejs +import { Client, Postgresql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.createRestoration({ + databaseId: '<DATABASE_ID>', + type: 'pitr', + targetTime: 1767225600, +}); +``` +```server-deno +import { Client, Postgresql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.createRestoration({ + databaseId: '<DATABASE_ID>', + type: 'pitr', + targetTime: 1767225600, +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Postgresql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$postgresql = new Postgresql($client); + +$postgresql->createRestoration( + databaseId: '<DATABASE_ID>', + type: 'pitr', + targetTime: 1767225600, +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.postgresql import Postgresql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +postgresql = Postgresql(client) + +postgresql.create_restoration( + database_id='<DATABASE_ID>', + type='pitr', + target_time=1767225600, +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +postgresql = Postgresql.new(client) + +postgresql.create_restoration( + database_id: '<DATABASE_ID>', + type: 'pitr', + target_time: 1767225600, +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Postgresql postgresql = new Postgresql(client); + +await postgresql.CreateRestoration( + databaseId: "<DATABASE_ID>", + type: "pitr", + targetTime: 1767225600 +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Postgresql postgresql = Postgresql(client); + +await postgresql.createRestoration( + databaseId: '<DATABASE_ID>', + type: 'pitr', + targetTime: 1767225600, +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Postgresql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val postgresql = Postgresql(client) + +postgresql.createRestoration( + databaseId = "<DATABASE_ID>", + type = "pitr", + targetTime = 1767225600, +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let postgresql = Postgresql(client) + +_ = try await postgresql.createRestoration( + databaseId: "<DATABASE_ID>", + type: "pitr", + targetTime: 1767225600 +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/postgresql" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewPostgresql(client) + + _, err := service.CreateRestoration( + "<DATABASE_ID>", + postgresql.WithCreateRestorationType("pitr"), + postgresql.WithCreateRestorationTargetTime(1767225600), + ) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::postgresql::Postgresql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let postgresql = Postgresql::new(&client); + + postgresql.create_restoration("<DATABASE_ID>", Some("pitr"), None, Some(1767225600)).await?; + + Ok(()) +} +``` +```bash +curl -X POST \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "pitr", + "targetTime": 1767225600 + }' \ + https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/restorations +``` +{% /multicode %} + +Like a backup restore, a PITR restore is in-place: the database is unavailable while restoring and everything after the target time is discarded. + +# Limits {% #limits %} + +| Limit | Value | +|------------------|-----------------| +| Backup retention | 1 - 365 days | +| PITR retention | 1 - 35 days | +| Backup policies | Plan-dependent | diff --git a/src/routes/docs/products/databases/postgresql/branches/+page.markdoc b/src/routes/docs/products/databases/postgresql/branches/+page.markdoc new file mode 100644 index 00000000000..dd3f350d489 --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/branches/+page.markdoc @@ -0,0 +1,677 @@ +--- +layout: article +title: Branches +description: Spin up an ephemeral, isolated copy of your PostgreSQL database in seconds from a storage snapshot. Use branches for previews, migrations, and testing. +--- + +A branch is a short-lived, isolated copy of your database. It has its own endpoint and reuses the parent's credentials, because it is a snapshot copy of the parent's storage volume taken at a point in time. Branches are not replicas: once created, they diverge from the parent and never sync back. + +{% info title="Branches do not merge back" %} +There is no branch merge operation. Use a branch to validate a migration, data repair, or application change, then intentionally cut application traffic over to the validated database or copy the data you want back with engine-native tools. Appwrite does not reconcile two diverged database histories for you. +{% /info %} + +Use cases: + +- **Preview environments**: one branch per pull request, destroyed when the PR closes +- **Test migrations**: apply a destructive `ALTER` against the branch first, observe the behavior, then run it against the source +- **Reproduce a bug**: branch the database, attach a debugger, throw the branch away when done +- **Heavy analytical queries**: `EXPLAIN ANALYZE` experiments against a branch cannot slow down the primary + +# How it works {% #how %} + +Creating a branch takes a fast `CHECKPOINT` on the parent, snapshots the parent's storage volume with a near-instant copy-on-write operation, and provisions a branch instance from the snapshot on the same engine version with its own isolated storage. Branch compute is fixed and lightweight, enough to validate a change rather than carry production load, and is not configurable. The parent and the branch share storage at the moment of branching; storage cost grows as the two diverge. + +# Create a branch {% #create %} + +{% multicode %} +```server-nodejs +import { Client, Postgresql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.createBranch({ + databaseId: '<DATABASE_ID>', + branchId: 'preview', + ttl: 86400, +}); +``` +```server-deno +import { Client, Postgresql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.createBranch({ + databaseId: '<DATABASE_ID>', + branchId: 'preview', + ttl: 86400, +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Postgresql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$postgresql = new Postgresql($client); + +$postgresql->createBranch( + databaseId: '<DATABASE_ID>', + branchId: 'preview', + ttl: 86400, +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.postgresql import Postgresql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +postgresql = Postgresql(client) + +postgresql.create_branch( + database_id='<DATABASE_ID>', + branch_id='preview', + ttl=86400, +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +postgresql = Postgresql.new(client) + +postgresql.create_branch( + database_id: '<DATABASE_ID>', + branch_id: 'preview', + ttl: 86400, +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Postgresql postgresql = new Postgresql(client); + +await postgresql.CreateBranch( + databaseId: "<DATABASE_ID>", + branchId: "preview", + ttl: 86400 +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Postgresql postgresql = Postgresql(client); + +await postgresql.createBranch( + databaseId: '<DATABASE_ID>', + branchId: 'preview', + ttl: 86400, +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Postgresql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val postgresql = Postgresql(client) + +postgresql.createBranch( + databaseId = "<DATABASE_ID>", + branchId = "preview", + ttl = 86400, +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let postgresql = Postgresql(client) + +_ = try await postgresql.createBranch( + databaseId: "<DATABASE_ID>", + branchId: "preview", + ttl: 86400 +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/postgresql" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewPostgresql(client) + + _, err := service.CreateBranch( + "<DATABASE_ID>", + postgresql.WithCreateBranchBranchId("preview"), + postgresql.WithCreateBranchTtl(86400), + ) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::postgresql::Postgresql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let postgresql = Postgresql::new(&client); + + postgresql.create_branch("<DATABASE_ID>", Some("preview"), Some(86400)).await?; + + Ok(()) +} +``` +```bash +curl -X POST \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + -H "Content-Type: application/json" \ + -d '{ + "branchId": "preview", + "ttl": 86400 + }' \ + https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/branches +``` +{% /multicode %} + +Both fields are optional: + +| Field | Default | Purpose | +|------------|--------------------|-----------------------------------------------------------------------| +| `branchId` | auto-generated | Custom ID (`a-z`, `A-Z`, `0-9`, `.`, `-`, `_`, max 36 chars) | +| `ttl` | `86400` (24 hours) | Lifetime in seconds before the branch expires (min 300, max 604800) | + +The call is asynchronous and returns immediately while the branch provisions in the background. When the TTL elapses, the branch and its storage are removed automatically. + +# List branches and connect {% #list %} + +Each entry carries its metadata and connection details, so there is no separate credentials call: + +{% multicode %} +```server-nodejs +import { Client, Postgresql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +const branches = await postgresql.listBranches({ + databaseId: '<DATABASE_ID>', +}); +``` +```server-deno +import { Client, Postgresql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +const branches = await postgresql.listBranches({ + databaseId: '<DATABASE_ID>', +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Postgresql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$postgresql = new Postgresql($client); + +$branches = $postgresql->listBranches( + databaseId: '<DATABASE_ID>', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.postgresql import Postgresql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +postgresql = Postgresql(client) + +branches = postgresql.list_branches( + database_id='<DATABASE_ID>', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +postgresql = Postgresql.new(client) + +branches = postgresql.list_branches( + database_id: '<DATABASE_ID>', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Postgresql postgresql = new Postgresql(client); + +var branches = await postgresql.ListBranches( + databaseId: "<DATABASE_ID>" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Postgresql postgresql = Postgresql(client); + +final branches = await postgresql.listBranches( + databaseId: '<DATABASE_ID>', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Postgresql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val postgresql = Postgresql(client) + +val branches = postgresql.listBranches( + databaseId = "<DATABASE_ID>", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let postgresql = Postgresql(client) + +let branches = try await postgresql.listBranches( + databaseId: "<DATABASE_ID>" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewPostgresql(client) + + result, err := service.ListBranches("<DATABASE_ID>") + if err != nil { + panic(err) + } + _ = result +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::postgresql::Postgresql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let postgresql = Postgresql::new(&client); + + let branches = postgresql.list_branches("<DATABASE_ID>").await?; + + Ok(()) +} +``` +```bash +curl -X GET \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/branches +``` +{% /multicode %} + +A branch gets its own hostname, and reuses the parent's username and password because it is a snapshot copy of the parent's storage. The port is the standard `5432`; branches have no connection pooler. Connect with the branch's `connectionString` straight from the response: + +```bash +psql "<branch connectionString>" +``` + +# Delete a branch {% #delete %} + +Deleting a branch removes the branch's instance, its storage volume, and the underlying snapshot. There is no soft delete: once the branch is gone, the data is gone. + +{% multicode %} +```server-nodejs +import { Client, Postgresql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.deleteBranch({ + databaseId: '<DATABASE_ID>', + branchId: 'preview', +}); +``` +```server-deno +import { Client, Postgresql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.deleteBranch({ + databaseId: '<DATABASE_ID>', + branchId: 'preview', +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Postgresql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$postgresql = new Postgresql($client); + +$postgresql->deleteBranch( + databaseId: '<DATABASE_ID>', + branchId: 'preview', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.postgresql import Postgresql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +postgresql = Postgresql(client) + +postgresql.delete_branch( + database_id='<DATABASE_ID>', + branch_id='preview', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +postgresql = Postgresql.new(client) + +postgresql.delete_branch( + database_id: '<DATABASE_ID>', + branch_id: 'preview', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Postgresql postgresql = new Postgresql(client); + +await postgresql.DeleteBranch( + databaseId: "<DATABASE_ID>", + branchId: "preview" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Postgresql postgresql = Postgresql(client); + +await postgresql.deleteBranch( + databaseId: '<DATABASE_ID>', + branchId: 'preview', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Postgresql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val postgresql = Postgresql(client) + +postgresql.deleteBranch( + databaseId = "<DATABASE_ID>", + branchId = "preview", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let postgresql = Postgresql(client) + +_ = try await postgresql.deleteBranch( + databaseId: "<DATABASE_ID>", + branchId: "preview" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewPostgresql(client) + + _, err := service.DeleteBranch("<DATABASE_ID>", "preview") + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::postgresql::Postgresql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let postgresql = Postgresql::new(&client); + + postgresql.delete_branch("<DATABASE_ID>", "preview").await?; + + Ok(()) +} +``` +```bash +curl -X DELETE \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/branches/preview +``` +{% /multicode %} + +# Billing {% #billing %} + +A branch runs on fixed, lightweight compute, so its cost is dominated by storage. The snapshot is free at the moment of branching; storage cost accumulates as the branch's data diverges from the parent. There is no separate branch line item, branches roll into your regular database storage and compute totals. + +# Use case: per-PR preview database {% #ci-example %} + +A CI pipeline that branches on every pull request and tears down on close: + +```yaml +name: preview-database + +on: + pull_request: + types: [opened, reopened, closed] + +jobs: + branch: + if: github.event.action != 'closed' + runs-on: ubuntu-latest + steps: + - name: Create branch + run: | + curl -X POST \ + -H "X-Appwrite-Project: ${{ vars.APPWRITE_PROJECT_ID }}" \ + -H "X-Appwrite-Key: ${{ secrets.APPWRITE_API_KEY }}" \ + -H "Content-Type: application/json" \ + -d '{"branchId": "pr-${{ github.event.number }}", "ttl": 604800}' \ + https://<REGION>.cloud.appwrite.io/v1/postgresql/${{ vars.DATABASE_ID }}/branches || true + + teardown: + if: github.event.action == 'closed' + runs-on: ubuntu-latest + steps: + - name: Delete branch + run: | + curl -X DELETE \ + -H "X-Appwrite-Project: ${{ vars.APPWRITE_PROJECT_ID }}" \ + -H "X-Appwrite-Key: ${{ secrets.APPWRITE_API_KEY }}" \ + https://<REGION>.cloud.appwrite.io/v1/postgresql/${{ vars.DATABASE_ID }}/branches/pr-${{ github.event.number }} +``` + +The `|| true` on the create call makes the workflow idempotent: if the branch already exists, the call is a no-op. diff --git a/src/routes/docs/products/databases/postgresql/connection-pooling/+page.markdoc b/src/routes/docs/products/databases/postgresql/connection-pooling/+page.markdoc new file mode 100644 index 00000000000..63fa48e3643 --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/connection-pooling/+page.markdoc @@ -0,0 +1,276 @@ +--- +layout: article +title: Connection pooling +description: Configure the per-database connection pooler to serve many short-lived clients, with automatic read/write splitting when high availability is enabled. +--- + +PostgreSQL creates one backend process per connection, which makes each connection relatively expensive. Serverless functions, edge runtimes, and horizontally scaled application servers can easily exhaust the connection limit of your specification. The connection pooler sits in front of your database and multiplexes many client connections onto a small pool of server connections. + +The pooler runs next to your database and is reachable on port `6432` on the same hostname. Your application connects to the pooler exactly like it would connect to PostgreSQL directly, same credentials, same TLS. The pooler is available on specifications that run on dedicated compute; the smallest specifications run on shared capacity and do not include it. + +# Pool modes {% #modes %} + +| Mode | Behavior | Use for | +|---------------|-----------------------------------------------------------------------------|------------------------------------------------| +| `transaction` | A server connection is assigned for the duration of a transaction, then returned to the pool | Serverless and most applications (default) | +| `session` | A server connection is held for the entire client session | Session-level features: prepared statements, advisory locks, `LISTEN/NOTIFY`, temporary tables | + +Transaction mode gives the highest connection multiplexing but does not support session-level state. If your framework prepares statements at the session level, either switch the driver to unnamed prepared statements or use session mode. + +# Configure the pooler {% #configure %} + +Read the current pooler configuration with `getPooler`, and tune the pool mode and sizes with `updatePooler`. All parameters are optional; omitted values keep their current setting. + +{% multicode %} +```server-nodejs +import { Client, Postgresql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +const pooler = await postgresql.updatePooler({ + databaseId: '<DATABASE_ID>', + mode: 'transaction', + maxConnections: 500, + defaultPoolSize: 25, +}); +``` +```server-deno +import { Client, Postgresql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +const pooler = await postgresql.updatePooler({ + databaseId: '<DATABASE_ID>', + mode: 'transaction', + maxConnections: 500, + defaultPoolSize: 25, +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Postgresql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$postgresql = new Postgresql($client); + +$pooler = $postgresql->updatePooler( + databaseId: '<DATABASE_ID>', + mode: 'transaction', + maxConnections: 500, + defaultPoolSize: 25, +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.postgresql import Postgresql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +postgresql = Postgresql(client) + +pooler = postgresql.update_pooler( + database_id='<DATABASE_ID>', + mode='transaction', + max_connections=500, + default_pool_size=25, +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +postgresql = Postgresql.new(client) + +pooler = postgresql.update_pooler( + database_id: '<DATABASE_ID>', + mode: 'transaction', + max_connections: 500, + default_pool_size: 25, +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Postgresql postgresql = new Postgresql(client); + +var pooler = await postgresql.UpdatePooler( + databaseId: "<DATABASE_ID>", + mode: "transaction", + maxConnections: 500, + defaultPoolSize: 25 +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Postgresql postgresql = Postgresql(client); + +final pooler = await postgresql.updatePooler( + databaseId: '<DATABASE_ID>', + mode: 'transaction', + maxConnections: 500, + defaultPoolSize: 25, +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Postgresql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val postgresql = Postgresql(client) + +val pooler = postgresql.updatePooler( + databaseId = "<DATABASE_ID>", + mode = "transaction", + maxConnections = 500, + defaultPoolSize = 25, +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let postgresql = Postgresql(client) + +let pooler = try await postgresql.updatePooler( + databaseId: "<DATABASE_ID>", + mode: "transaction", + maxConnections: 500, + defaultPoolSize: 25 +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/postgresql" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewPostgresql(client) + + result, err := service.UpdatePooler( + "<DATABASE_ID>", + postgresql.WithUpdatePoolerMode("transaction"), + postgresql.WithUpdatePoolerMaxConnections(500), + postgresql.WithUpdatePoolerDefaultPoolSize(25), + ) + if err != nil { + panic(err) + } + _ = result +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::postgresql::Postgresql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let postgresql = Postgresql::new(&client); + + let pooler = postgresql.update_pooler("<DATABASE_ID>", Some("transaction"), Some(500), Some(25), None, None, None, None, None).await?; + + Ok(()) +} +``` +```bash +curl -X PATCH \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + -H "Content-Type: application/json" \ + -d '{ + "mode": "transaction", + "maxConnections": 500, + "defaultPoolSize": 25 + }' \ + https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/pooler +``` +{% /multicode %} + +| Parameter | Range | Description | +|----------------------|----------------|--------------------------------------------------------------------------------| +| `mode` | `transaction`, `session` | How long a server connection stays assigned to a client | +| `maxConnections` | 10 - 10,000 | Maximum pooled client connections. Cannot exceed the connection cap of your specification | +| `defaultPoolSize` | 1 - 1,000 | Server connections per user in the pool | +| `readWriteSplitting` | boolean | Route `SELECT`s to replicas, writes and locked reads to the primary. Defaults to on when high availability is enabled | + +The same settings are available in the Console under **Settings** > **Connection pooler**. + +# Connect through the pooler {% #connect %} + +Take your normal connection string and change the port to `6432`: + +```bash +postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:6432/<database> +``` + +Point your application's runtime traffic at the pooler port. Keep migrations and long-lived administrative sessions on the direct port `5432`, schema changes and tools like `pg_dump` expect session semantics and can misbehave in transaction mode. + +# Read/write splitting {% #read-write-splitting %} + +When [high availability](/docs/products/databases/postgresql/high-availability) is enabled, the pooler can route read-only statements to replicas and everything else to the primary. `SELECT ... FOR UPDATE` and statements inside explicit transactions go to the primary. Replicas replicate asynchronously by default, so a read that immediately follows a write can be stale; use `sync` or `quorum` replication mode if you need read-your-writes consistency through the pooler. + +# Sizing guidance {% #sizing %} + +A useful starting point for `defaultPoolSize` is `4 x CPU cores` of your specification, and it rarely helps to go above your specification's connection cap divided by the number of databases sharing the workload. Watch the connection metrics in the [Monitor tab](/docs/products/databases/postgresql/monitoring) and increase the pool only when clients queue for a connection. + +# When not to use the pooler {% #when-not %} + +Skip the pooler when you have a small, fixed number of long-lived application servers that each maintain their own driver-level pool, and their combined connection count fits comfortably in your specification's limit. Adding a pooler in that setup only adds a hop. diff --git a/src/routes/docs/products/databases/postgresql/connections/+page.markdoc b/src/routes/docs/products/databases/postgresql/connections/+page.markdoc new file mode 100644 index 00000000000..300a2cc2050 --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/connections/+page.markdoc @@ -0,0 +1,541 @@ +--- +layout: article +title: Connections +description: Connect to your PostgreSQL database with psql or any standard driver. Retrieve connection details and rotate the primary password. +--- + +A native PostgreSQL database exposes a PostgreSQL endpoint over TLS. You connect to it the same way you would connect to any PostgreSQL server: with `psql`, any driver in any language, or any ORM. + +# Get connection details in the Console {% #console %} + +{% only_dark %} +![Database credentials dialog](/images/docs/products/databases/postgresql/dark/credentials.avif) +{% /only_dark %} +{% only_light %} +![Database credentials dialog](/images/docs/products/databases/postgresql/credentials.avif) +{% /only_light %} + +The fastest way to connect is through the Appwrite Console: + +1. In your project, go to **Databases** and select your PostgreSQL database. +2. Click **Credentials** to open the credentials dialog. +3. Copy the individual values from the **Details** tab, or switch to the **DSN**, **.env**, **Prisma**, **Drizzle**, or **psql** tab for a ready-made snippet. +4. Paste it into `psql`, your ORM, or your database client. + +Use the API flow below when you need to fetch connection details from automation or inject them into your deployment pipeline. + +# Get connection details with the API {% #credentials %} + +The connection details are returned on the database object itself. Fetch the database with an API key that has the `databases.read` scope: + +{% multicode %} +```server-nodejs +import { Client, Postgresql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +const database = await postgresql.get({ + databaseId: '<DATABASE_ID>', +}); + +console.log(database.connectionString); +``` +```server-deno +import { Client, Postgresql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +const database = await postgresql.get({ + databaseId: '<DATABASE_ID>', +}); + +console.log(database.connectionString); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Postgresql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$postgresql = new Postgresql($client); + +$database = $postgresql->get(databaseId: '<DATABASE_ID>'); + +echo $database['connectionString']; +``` +```server-python +from appwrite.client import Client +from appwrite.services.postgresql import Postgresql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +postgresql = Postgresql(client) + +database = postgresql.get(database_id='<DATABASE_ID>') + +print(database.connection_string) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +postgresql = Postgresql.new(client) + +database = postgresql.get(database_id: '<DATABASE_ID>') + +puts database.connection_string +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Postgresql postgresql = new Postgresql(client); + +var database = await postgresql.Get(databaseId: "<DATABASE_ID>"); + +Console.WriteLine(database.ConnectionString); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Postgresql postgresql = Postgresql(client); + +final database = await postgresql.get( + databaseId: '<DATABASE_ID>', +); + +print(database.connectionString); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Postgresql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val postgresql = Postgresql(client) + +val database = postgresql.get( + databaseId = "<DATABASE_ID>", +) + +println(database.connectionString) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let postgresql = Postgresql(client) + +let database = try await postgresql.get( + databaseId: "<DATABASE_ID>" +) + +print(database.connectionString) +``` +```server-go +package main + +import ( + "fmt" + + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + postgresql := appwrite.NewPostgresql(client) + + database, err := postgresql.Get("<DATABASE_ID>") + if err != nil { + panic(err) + } + fmt.Println(database.ConnectionString) +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::postgresql::Postgresql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let postgresql = Postgresql::new(&client); + + let database = postgresql.get("<DATABASE_ID>").await?; + + println!("{}", database.connection_string); + + Ok(()) +} +``` +```bash +curl -X GET \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID> +``` +{% /multicode %} + +The response includes the connection fields alongside the database configuration: + +```json +{ + "$id": "<DATABASE_ID>", + "name": "main", + "engine": "postgresql", + "version": "18", + "status": "ready", + "hostname": "db-<hash>.<region>.appwrite.center", + "connectionPort": 5432, + "connectionUser": "admin", + "connectionPassword": "<password>", + "connectionString": "postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>" +} +``` + +The primary user is `admin` and the database name is generated per database. + +# Connect with psql {% #psql %} + +Pass the connection string, or the individual values, to `psql`. The Console credentials dialog also has a **psql** tab with the command ready to copy. + +```bash +psql "postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>" +``` + +Or with individual flags: + +```bash +psql -h db-<hash>.<region>.appwrite.center -p 5432 -U admin -d <database> +``` + +# Rotate the primary password {% #rotate %} + +If your password is compromised, or your security policy requires regular rotation, you can issue a new password for the primary user. The change is applied atomically in the engine, and the response carries the new connection details. Existing sessions stay alive until they disconnect, then have to authenticate with the new password. The API key needs the `databases.write` scope. + +{% multicode %} +```server-nodejs +import { Client, Postgresql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +const database = await postgresql.updateCredentials({ + databaseId: '<DATABASE_ID>', +}); +``` +```server-deno +import { Client, Postgresql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +const database = await postgresql.updateCredentials({ + databaseId: '<DATABASE_ID>', +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Postgresql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$postgresql = new Postgresql($client); + +$database = $postgresql->updateCredentials(databaseId: '<DATABASE_ID>'); +``` +```server-python +from appwrite.client import Client +from appwrite.services.postgresql import Postgresql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +postgresql = Postgresql(client) + +database = postgresql.update_credentials(database_id='<DATABASE_ID>') +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +postgresql = Postgresql.new(client) + +database = postgresql.update_credentials(database_id: '<DATABASE_ID>') +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Postgresql postgresql = new Postgresql(client); + +var database = await postgresql.UpdateCredentials(databaseId: "<DATABASE_ID>"); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Postgresql postgresql = Postgresql(client); + +final database = await postgresql.updateCredentials( + databaseId: '<DATABASE_ID>', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Postgresql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val postgresql = Postgresql(client) + +val database = postgresql.updateCredentials( + databaseId = "<DATABASE_ID>", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let postgresql = Postgresql(client) + +let database = try await postgresql.updateCredentials( + databaseId: "<DATABASE_ID>" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + postgresql := appwrite.NewPostgresql(client) + + _, err := postgresql.UpdateCredentials("<DATABASE_ID>") + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::postgresql::Postgresql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let postgresql = Postgresql::new(&client); + + let database = postgresql.update_credentials("<DATABASE_ID>").await?; + + Ok(()) +} +``` +```bash +curl -X PATCH \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/credentials +``` +{% /multicode %} + +# Database roles {% #roles %} + +{% only_dark %} +![Database roles tab](/images/docs/products/databases/postgresql/dark/roles-tab.avif) +{% /only_dark %} +{% only_light %} +![Database roles tab](/images/docs/products/databases/postgresql/roles-tab.avif) +{% /only_light %} + +The primary `admin` role owns the default database. For applications that need narrower access, such as a read-only reporting user or a write-only ingestion user, create additional PostgreSQL roles from the **Roles** tab of your database in the Console. The list shows each role's login, role-creation, and database-creation privileges, its connection limit, and its role memberships. + +Roles are standard PostgreSQL roles, so `GRANT` and `REVOKE` statements in the [SQL editor](/docs/products/databases/postgresql/quick-start#first-queries) or `psql` work on them like on any PostgreSQL server. The `postgres` superuser is managed by Appwrite and cannot be modified. + +# TLS {% #tls %} + +Connections on Appwrite Cloud are encrypted with TLS, terminated at the edge and forwarded to your database over the internal network. The connection string from the credentials dialog carries the right SSL settings for your environment, so drivers need no extra configuration. + +For IP allowlists and other network controls, see [network security](/docs/products/databases/postgresql/network-security). + +# Connecting from an application {% #drivers %} + +There is nothing Appwrite-specific about the driver setup. A few example snippets: + +{% multicode %} +```server-nodejs +import { Client } from 'pg'; + +const client = new Client({ + connectionString: process.env.DATABASE_URL, +}); + +await client.connect(); +const { rows } = await client.query('SELECT now()'); +console.log(rows); +``` +```server-python +import os +import psycopg + +with psycopg.connect(os.environ['DATABASE_URL']) as conn: + with conn.cursor() as cur: + cur.execute('SELECT now()') + print(cur.fetchone()) +``` +```server-php +<?php + +$pdo = new PDO(getenv('DATABASE_DSN')); + +$rows = $pdo->query('SELECT now()')->fetchAll(); +print_r($rows); +``` +```server-go +package main + +import ( + "context" + "fmt" + "os" + + "github.com/jackc/pgx/v5" +) + +func main() { + conn, err := pgx.Connect(context.Background(), os.Getenv("DATABASE_URL")) + if err != nil { + panic(err) + } + defer conn.Close(context.Background()) + + var now string + if err := conn.QueryRow(context.Background(), "SELECT now()").Scan(&now); err != nil { + panic(err) + } + fmt.Println(now) +} +``` +```server-rust +use tokio_postgres::NoTls; + +#[tokio::main] +async fn main() -> Result<(), tokio_postgres::Error> { + let url = std::env::var("DATABASE_URL").expect("DATABASE_URL"); + let (client, connection) = tokio_postgres::connect(&url, NoTls).await?; + + tokio::spawn(async move { + if let Err(e) = connection.await { + eprintln!("connection error: {e}"); + } + }); + + let row = client.query_one("SELECT now()::text", &[]).await?; + let now: &str = row.get(0); + println!("{now}"); + + Ok(()) +} +``` +{% /multicode %} + +Set `DATABASE_URL` to the connection string from the credentials dialog. Once you can run a query, you can use any tool that talks the PostgreSQL wire protocol: pgAdmin, DataGrip, your ORM of choice, your migration tool of choice. Appwrite gets out of the way. diff --git a/src/routes/docs/products/databases/postgresql/extensions/+page.markdoc b/src/routes/docs/products/databases/postgresql/extensions/+page.markdoc new file mode 100644 index 00000000000..0d9c0e155fa --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/extensions/+page.markdoc @@ -0,0 +1,630 @@ +--- +layout: article +title: Extensions +description: Install and manage PostgreSQL extensions like PostGIS, pgvector, and pg_trgm on your database, at no extra cost. +--- + +PostgreSQL exposes a rich extension ecosystem: PostGIS for geospatial data, pgvector for embeddings, pg_trgm for fuzzy search, and more. Native PostgreSQL databases on Appwrite support managing extensions through the API, at no extra cost. + +# Install an extension {% #install %} + +Pass the extension name as it appears in the [extension catalog](#list). The install runs asynchronously: the request returns immediately and a worker runs `CREATE EXTENSION` inside the database, typically within a few seconds. The database must be in the `ready` state. + +{% multicode %} +```server-nodejs +import { Client, Postgresql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.createExtension({ + databaseId: '<DATABASE_ID>', + name: 'vector', +}); +``` +```server-deno +import { Client, Postgresql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.createExtension({ + databaseId: '<DATABASE_ID>', + name: 'vector', +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Postgresql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$postgresql = new Postgresql($client); + +$postgresql->createExtension( + databaseId: '<DATABASE_ID>', + name: 'vector', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.postgresql import Postgresql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +postgresql = Postgresql(client) + +postgresql.create_extension( + database_id='<DATABASE_ID>', + name='vector', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +postgresql = Postgresql.new(client) + +postgresql.create_extension( + database_id: '<DATABASE_ID>', + name: 'vector', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Postgresql postgresql = new Postgresql(client); + +await postgresql.CreateExtension( + databaseId: "<DATABASE_ID>", + name: "vector" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Postgresql postgresql = Postgresql(client); + +await postgresql.createExtension( + databaseId: '<DATABASE_ID>', + name: 'vector', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Postgresql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val postgresql = Postgresql(client) + +postgresql.createExtension( + databaseId = "<DATABASE_ID>", + name = "vector", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let postgresql = Postgresql(client) + +_ = try await postgresql.createExtension( + databaseId: "<DATABASE_ID>", + name: "vector" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewPostgresql(client) + + _, err := service.CreateExtension("<DATABASE_ID>", "vector") + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::postgresql::Postgresql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let postgresql = Postgresql::new(&client); + + postgresql.create_extension("<DATABASE_ID>", "vector").await?; + + Ok(()) +} +``` +```bash +curl -X POST \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "vector" + }' \ + https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/extensions +``` +{% /multicode %} + +Requesting an extension that is not available fails with an error listing the supported extension names. + +# List extensions {% #list %} + +Lists the extensions installed on the database and the extensions available to install: + +{% multicode %} +```server-nodejs +import { Client, Postgresql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +const extensions = await postgresql.listExtensions({ + databaseId: '<DATABASE_ID>', +}); +``` +```server-deno +import { Client, Postgresql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +const extensions = await postgresql.listExtensions({ + databaseId: '<DATABASE_ID>', +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Postgresql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$postgresql = new Postgresql($client); + +$extensions = $postgresql->listExtensions( + databaseId: '<DATABASE_ID>', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.postgresql import Postgresql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +postgresql = Postgresql(client) + +extensions = postgresql.list_extensions( + database_id='<DATABASE_ID>', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +postgresql = Postgresql.new(client) + +extensions = postgresql.list_extensions( + database_id: '<DATABASE_ID>', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Postgresql postgresql = new Postgresql(client); + +var extensions = await postgresql.ListExtensions( + databaseId: "<DATABASE_ID>" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Postgresql postgresql = Postgresql(client); + +final extensions = await postgresql.listExtensions( + databaseId: '<DATABASE_ID>', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Postgresql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val postgresql = Postgresql(client) + +val extensions = postgresql.listExtensions( + databaseId = "<DATABASE_ID>", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let postgresql = Postgresql(client) + +let extensions = try await postgresql.listExtensions( + databaseId: "<DATABASE_ID>" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewPostgresql(client) + + result, err := service.ListExtensions("<DATABASE_ID>") + if err != nil { + panic(err) + } + _ = result +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::postgresql::Postgresql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let postgresql = Postgresql::new(&client); + + let extensions = postgresql.list_extensions("<DATABASE_ID>").await?; + + Ok(()) +} +``` +```bash +curl -X GET \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/extensions +``` +{% /multicode %} + +The response contains two arrays of extension names: + +```json +{ + "installed": ["plpgsql", "vector"], + "available": ["postgis", "pg_trgm", "pgcrypto", "hstore", "citext", "..."] +} +``` + +The available list is reported by the engine for your PostgreSQL version, so it always reflects what can actually be installed on your database. + +# Remove an extension {% #remove %} + +{% multicode %} +```server-nodejs +import { Client, Postgresql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.deleteExtension({ + databaseId: '<DATABASE_ID>', + extensionName: 'vector', +}); +``` +```server-deno +import { Client, Postgresql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.deleteExtension({ + databaseId: '<DATABASE_ID>', + extensionName: 'vector', +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Postgresql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$postgresql = new Postgresql($client); + +$postgresql->deleteExtension( + databaseId: '<DATABASE_ID>', + extensionName: 'vector', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.postgresql import Postgresql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +postgresql = Postgresql(client) + +postgresql.delete_extension( + database_id='<DATABASE_ID>', + extension_name='vector', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +postgresql = Postgresql.new(client) + +postgresql.delete_extension( + database_id: '<DATABASE_ID>', + extension_name: 'vector', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Postgresql postgresql = new Postgresql(client); + +await postgresql.DeleteExtension( + databaseId: "<DATABASE_ID>", + extensionName: "vector" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Postgresql postgresql = Postgresql(client); + +await postgresql.deleteExtension( + databaseId: '<DATABASE_ID>', + extensionName: 'vector', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Postgresql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val postgresql = Postgresql(client) + +postgresql.deleteExtension( + databaseId = "<DATABASE_ID>", + extensionName = "vector", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let postgresql = Postgresql(client) + +_ = try await postgresql.deleteExtension( + databaseId: "<DATABASE_ID>", + extensionName: "vector" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewPostgresql(client) + + _, err := service.DeleteExtension("<DATABASE_ID>", "vector") + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::postgresql::Postgresql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let postgresql = Postgresql::new(&client); + + postgresql.delete_extension("<DATABASE_ID>", "vector").await?; + + Ok(()) +} +``` +```bash +curl -X DELETE \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/extensions/vector +``` +{% /multicode %} + +If there are objects that depend on the extension (tables, indexes, columns), the drop fails. Drop the dependents first, or run `DROP EXTENSION ... CASCADE` yourself through SQL to acknowledge what gets removed. + +# Common extensions {% #common %} + +A few extensions worth knowing about: + +| Name | Use case | +|-------------|-----------------------------------------------------------------------| +| `vector` | Vector data type and similarity search for embeddings (pgvector) | +| `postgis` | Spatial data types, indexes, and functions for geographic objects | +| `pg_trgm` | Trigram matching for fuzzy string search and similarity | +| `pgcrypto` | Cryptographic functions for hashing and encryption | +| `uuid-ossp` | Generate universally unique identifiers (UUIDs) | +| `hstore` | Store sets of key/value pairs within a single column | +| `citext` | Case-insensitive character string data type | +| `ltree` | Represent and query hierarchical tree-like data | + +List the extensions on your database for the full catalog available to it. + +# Limits {% #limits %} + +| Limit | Value | +|---------------------------------|------------| +| Maximum extensions per database | 50 | +| Extension install and uninstall | Free | + +Extensions are installed at their default version for your database's PostgreSQL major version. diff --git a/src/routes/docs/products/databases/postgresql/high-availability/+page.markdoc b/src/routes/docs/products/databases/postgresql/high-availability/+page.markdoc new file mode 100644 index 00000000000..f5036f1f6e4 --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/high-availability/+page.markdoc @@ -0,0 +1,638 @@ +--- +layout: article +title: High availability +description: Run up to five read replicas with asynchronous, synchronous, or quorum replication and automatic failover for your PostgreSQL database. +--- + +A single database instance is a single point of failure. High availability (HA) adds streaming replicas next to your primary: they replicate continuously, serve read traffic through the [connection pooler](/docs/products/databases/postgresql/connection-pooling), and take over automatically when the primary becomes unhealthy. + +High availability requires a specification that runs on dedicated compute; the smallest specifications run on shared capacity and do not support replicas. + +# How it works {% #how-it-works %} + +Replicas receive changes from the primary through PostgreSQL streaming replication (WAL shipping). Each replica is a full copy of the database on its own compute. When the primary fails, the most caught-up replica is promoted to primary and the hostname is repointed, your application keeps connecting to the same host and port. + +# Replication modes {% #sync-modes %} + +| Mode | Behavior | Trade-off | +|----------|----------------------------------------------------------------------------------|--------------------------------------------------| +| `async` | The primary commits without waiting for replicas | Fastest writes; a failover can lose the last moments of writes | +| `sync` | The primary waits for one replica to confirm each commit | No data loss on single failure; slightly higher write latency | +| `quorum` | The primary waits for a majority of replicas to confirm each commit | Strongest durability; highest write latency | + +`async` is the default. For production workloads that cannot lose acknowledged writes, use `sync` with at least two replicas. + +# Enable high availability {% #enable %} + +{% only_dark %} +![High availability settings](/images/docs/products/databases/postgresql/dark/settings-high-availability.avif) +{% /only_dark %} +{% only_light %} +![High availability settings](/images/docs/products/databases/postgresql/settings-high-availability.avif) +{% /only_light %} + +Set the replica count and replication mode on the database, from **Settings** > **High availability** in the Console or through the API: + +{% multicode %} +```server-nodejs +import { Client, Postgresql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.update({ + databaseId: '<DATABASE_ID>', + replicas: 2, + syncMode: 'sync', +}); +``` +```server-deno +import { Client, Postgresql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.update({ + databaseId: '<DATABASE_ID>', + replicas: 2, + syncMode: 'sync', +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Postgresql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$postgresql = new Postgresql($client); + +$postgresql->update( + databaseId: '<DATABASE_ID>', + replicas: 2, + syncMode: 'sync', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.postgresql import Postgresql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +postgresql = Postgresql(client) + +postgresql.update( + database_id='<DATABASE_ID>', + replicas=2, + sync_mode='sync', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +postgresql = Postgresql.new(client) + +postgresql.update( + database_id: '<DATABASE_ID>', + replicas: 2, + sync_mode: 'sync', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Postgresql postgresql = new Postgresql(client); + +await postgresql.Update( + databaseId: "<DATABASE_ID>", + replicas: 2, + syncMode: "sync" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Postgresql postgresql = Postgresql(client); + +await postgresql.update( + databaseId: '<DATABASE_ID>', + replicas: 2, + syncMode: 'sync', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Postgresql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val postgresql = Postgresql(client) + +postgresql.update( + databaseId = "<DATABASE_ID>", + replicas = 2, + syncMode = "sync", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let postgresql = Postgresql(client) + +_ = try await postgresql.update( + databaseId: "<DATABASE_ID>", + replicas: 2, + syncMode: "sync" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/postgresql" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewPostgresql(client) + + _, err := service.Update( + "<DATABASE_ID>", + postgresql.WithUpdateReplicas(2), + postgresql.WithUpdateSyncMode("sync"), + ) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::postgresql::Postgresql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let postgresql = Postgresql::new(&client); + + postgresql.update("<DATABASE_ID>", None, None, None, None, Some("sync"), None, None, None, None, None, None, None, None, None, None, None, None, None, None, None).await?; + + Ok(()) +} +``` +```bash +curl -X PATCH \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + -H "Content-Type: application/json" \ + -d '{ + "replicas": 2, + "syncMode": "sync" + }' \ + https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID> +``` +{% /multicode %} + +Adding replicas provisions them online; the primary keeps serving traffic while each replica seeds from a snapshot and catches up. Setting `replicas` back to `0` disables HA. + +# Check replication status {% #status %} + +You can check each replica's role, health, and replication lag: + +{% multicode %} +```server-nodejs +import { Client, Postgresql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +const replicas = await postgresql.getReplicas({ + databaseId: '<DATABASE_ID>', +}); +``` +```server-deno +import { Client, Postgresql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +const replicas = await postgresql.getReplicas({ + databaseId: '<DATABASE_ID>', +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Postgresql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$postgresql = new Postgresql($client); + +$replicas = $postgresql->getReplicas( + databaseId: '<DATABASE_ID>', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.postgresql import Postgresql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +postgresql = Postgresql(client) + +replicas = postgresql.get_replicas( + database_id='<DATABASE_ID>', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +postgresql = Postgresql.new(client) + +replicas = postgresql.get_replicas( + database_id: '<DATABASE_ID>', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Postgresql postgresql = new Postgresql(client); + +var replicas = await postgresql.GetReplicas( + databaseId: "<DATABASE_ID>" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Postgresql postgresql = Postgresql(client); + +final replicas = await postgresql.getReplicas( + databaseId: '<DATABASE_ID>', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Postgresql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val postgresql = Postgresql(client) + +val replicas = postgresql.getReplicas( + databaseId = "<DATABASE_ID>", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let postgresql = Postgresql(client) + +let replicas = try await postgresql.getReplicas( + databaseId: "<DATABASE_ID>" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewPostgresql(client) + + result, err := service.GetReplicas("<DATABASE_ID>") + if err != nil { + panic(err) + } + _ = result +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::postgresql::Postgresql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let postgresql = Postgresql::new(&client); + + let replicas = postgresql.get_replicas("<DATABASE_ID>").await?; + + Ok(()) +} +``` +```bash +curl -X GET \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/replicas +``` +{% /multicode %} + +# Automatic failover {% #automatic-failover %} + +Appwrite continuously health-checks the primary. When it becomes unresponsive, the platform promotes the replica with the least replication lag, repoints the database hostname, and marks the old primary for replacement. Your application reconnects to the same hostname; a well-configured driver pool retries and recovers without intervention. + +With `async` replication, writes that had not yet reached the promoted replica are lost in a failover. Use `sync` or `quorum` if that is unacceptable. + +# Manual failover {% #manual-failover %} + +Trigger a failover yourself, for example to test your application's recovery behavior. Optionally pass `targetReplicaId` to promote a specific replica. + +{% multicode %} +```server-nodejs +import { Client, Postgresql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.createFailover({ + databaseId: '<DATABASE_ID>', +}); +``` +```server-deno +import { Client, Postgresql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.createFailover({ + databaseId: '<DATABASE_ID>', +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Postgresql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$postgresql = new Postgresql($client); + +$postgresql->createFailover( + databaseId: '<DATABASE_ID>', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.postgresql import Postgresql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +postgresql = Postgresql(client) + +postgresql.create_failover( + database_id='<DATABASE_ID>', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +postgresql = Postgresql.new(client) + +postgresql.create_failover( + database_id: '<DATABASE_ID>', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Postgresql postgresql = new Postgresql(client); + +await postgresql.CreateFailover( + databaseId: "<DATABASE_ID>" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Postgresql postgresql = Postgresql(client); + +await postgresql.createFailover( + databaseId: '<DATABASE_ID>', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Postgresql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val postgresql = Postgresql(client) + +postgresql.createFailover( + databaseId = "<DATABASE_ID>", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let postgresql = Postgresql(client) + +_ = try await postgresql.createFailover( + databaseId: "<DATABASE_ID>" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewPostgresql(client) + + _, err := service.CreateFailover("<DATABASE_ID>") + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::postgresql::Postgresql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let postgresql = Postgresql::new(&client); + + postgresql.create_failover("<DATABASE_ID>", None).await?; + + Ok(()) +} +``` +```bash +curl -X POST \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/failovers +``` +{% /multicode %} + +# Reading from replicas {% #reads %} + +Replicas serve read traffic when [read/write splitting](/docs/products/databases/postgresql/connection-pooling#read-write-splitting) is enabled on the connection pooler. With `async` replication, a read that immediately follows a write can return stale data. Route reads that must see the latest write to the primary, or use `sync` replication. + +# Limits and billing {% #limits %} + +- Up to 5 replicas per database; the maximum depends on your plan. +- Each replica runs on the same specification as the primary and is billed as an add-on. See [pricing](/pricing). +- Replicas live in the same region as the primary. diff --git a/src/routes/docs/products/databases/postgresql/integrations/auth-js/+page.markdoc b/src/routes/docs/products/databases/postgresql/integrations/auth-js/+page.markdoc new file mode 100644 index 00000000000..f2feae0ebd2 --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/integrations/auth-js/+page.markdoc @@ -0,0 +1,229 @@ +--- +layout: article +title: Auth.js +description: Use an Appwrite native PostgreSQL database as the backing store for Auth.js (NextAuth.js). Persist users, accounts, and sessions through a Prisma or Drizzle adapter, pooled from serverless runtimes. +--- + +[Auth.js](https://authjs.dev/) (formerly NextAuth.js) persists users, accounts, sessions, and verification tokens through a database adapter. When you configure an adapter, those records live in your own database instead of only in a cookie, which is what makes database sessions, account linking, and email sign-in possible. An Appwrite [native PostgreSQL database](/docs/products/databases/postgresql) is a standard PostgreSQL engine, so any Auth.js adapter built on a PostgreSQL ORM works against it with no Appwrite-specific configuration. + +{% info title="Before you start" %} +You'll need a native PostgreSQL database in a `ready` state and its credentials. See [native PostgreSQL databases](/docs/products/databases/postgresql) to create one, then open the database in the Console and click **Credentials**. The primary user is `admin`, and each database has its own generated database name. +{% /info %} + +# Choose an adapter {% #adapter %} + +Auth.js doesn't talk to the database directly, it goes through an official adapter. For a native PostgreSQL database, use whichever ORM you already run: + +- **Prisma** through [`@auth/prisma-adapter`](https://authjs.dev/getting-started/adapters/prisma). See the [Prisma guide](/docs/products/databases/postgresql/integrations/prisma) for the full datasource, pooling, and migration setup. +- **Drizzle** through [`@auth/drizzle-adapter`](https://authjs.dev/getting-started/adapters/drizzle). See the [Drizzle guide](/docs/products/databases/postgresql/integrations/drizzle) for the driver connection and `drizzle-kit` migration setup. + +The connection details, ports, and pooling behaviour are the same for both. The rest of this page shows the Prisma path and notes the Drizzle equivalents. + +# Set the connection string {% #connection-string %} + +Auth.js adapters read the database URL from the environment, exactly like any other ORM workload. Copy the connection string from the Console **Credentials** dialog or from `postgresql.get()` in the [API](/docs/products/databases/postgresql/connections#credentials), and give the adapter two URLs: a **pooled** one for the running app and a **direct** one for migrations. + +```env +# Runtime: pooled, transaction mode (prepared statements off) +DATABASE_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:6432/<database>?sslmode=require&pgbouncer=true" + +# Migrations: direct connection to the engine +DIRECT_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require" + +# Development migrations: separate branch or database used only by Prisma Migrate +SHADOW_DATABASE_URL="postgresql://admin:<password>@db-<shadow-hash>.<region>.appwrite.center:5432/<database>?sslmode=require" +``` + +The app runtime connects on the [pooler](/docs/products/databases/postgresql/connection-pooling) port (`6432`), and schema migrations run over the direct engine port (`5432`). The TLS parameter (`sslmode=require`) is already part of the string Appwrite returns. The edge proxy terminates TLS, so no certificate setup is needed. For full certificate verification (`verify-full`) or mTLS, see the [Network](/docs/products/databases/postgresql/network-security) page. + +`SHADOW_DATABASE_URL` must point at a separate PostgreSQL database or branch. Prisma Migrate uses it during `migrate dev` to detect schema drift. It can reset the shadow target, so never set it to the same value as `DIRECT_URL`. `migrate deploy` does not use the shadow database. + +# Create the adapter schema {% #schema %} + +Auth.js ships a [canonical schema](https://authjs.dev/getting-started/database#models) of four core models, `User`, `Account`, `Session`, and `VerificationToken`. With Prisma, add them to `prisma/schema.prisma` and keep connection URLs in `prisma.config.ts` and your Prisma Client setup: + +```prisma +datasource db { + provider = "postgresql" +} +``` + +The generator and models are engine-agnostic: + +```prisma +generator client { + provider = "prisma-client-js" +} + +model User { + id String @id @default(cuid()) + name String? + email String? @unique + emailVerified DateTime? + image String? + accounts Account[] + sessions Session[] +} + +model Account { + id String @id @default(cuid()) + userId String + type String + provider String + providerAccountId String + refresh_token String? @db.Text + access_token String? @db.Text + expires_at Int? + token_type String? + scope String? + id_token String? @db.Text + session_state String? + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([provider, providerAccountId]) +} + +model Session { + id String @id @default(cuid()) + sessionToken String @unique + userId String + expires DateTime + user User @relation(fields: [userId], references: [id], onDelete: Cascade) +} + +model VerificationToken { + identifier String + token String + expires DateTime + + @@unique([identifier, token]) +} +``` + +Configure Prisma CLI commands in `prisma.config.ts`. The CLI uses `DIRECT_URL` because migrations need a direct session connection, while Prisma Client uses the pooled `DATABASE_URL` at runtime. + +```ts +import 'dotenv/config'; +import { defineConfig, env } from 'prisma/config'; + +export default defineConfig({ + schema: 'prisma/schema.prisma', + migrations: { + path: 'prisma/migrations', + }, + datasource: { + url: env('DIRECT_URL'), + shadowDatabaseUrl: process.env.SHADOW_DATABASE_URL, + }, +}); +``` + +With Drizzle, define the equivalent PostgreSQL tables in your schema file and generate a `drizzle-kit` migration instead. The table shapes are the same, and the official adapter docs include a ready-made PostgreSQL schema. + +# Run the migration over the direct connection {% #migrate %} + +Create the tables before the app serves any traffic. Prisma Migrate connects with `DIRECT_URL` (the engine port), so it gets a real session connection with full DDL privileges: + +```bash +npx prisma migrate dev --name authjs-init +``` + +In CI or production, apply already-generated migrations without prompting: + +```bash +npx prisma migrate deploy +``` + +If your build does not run `migrate dev`, generate Prisma Client explicitly: + +```bash +npx prisma generate +``` + +With Drizzle, run `drizzle-kit push` (or `migrate()`) against the same direct URL. The primary `admin` user owns the default database and can run schema changes. Narrower [connection users](/docs/products/databases/postgresql/connections#roles) (`readonly` / `readwrite`) intentionally cannot run DDL, so always migrate as `admin`. + +# Wire the adapter into Auth.js {% #config %} + +Create one Prisma Client with the PostgreSQL driver adapter, pointed at the pooled `DATABASE_URL`: + +```ts +import { PrismaPg } from '@prisma/adapter-pg'; +import { PrismaClient } from '@prisma/client'; + +const connectionString = process.env.DATABASE_URL; +if (!connectionString) throw new Error('DATABASE_URL is required'); + +const adapter = new PrismaPg({ connectionString }); + +export const prisma = new PrismaClient({ adapter }); +``` + +Pass the adapter to your Auth.js config through the `adapter` key. The adapter uses Prisma Client for every runtime read and write: + +```ts +import NextAuth from 'next-auth'; +import { PrismaAdapter } from '@auth/prisma-adapter'; +import { prisma } from '@/prisma'; + +export const { handlers, auth, signIn, signOut } = NextAuth({ + adapter: PrismaAdapter(prisma), + providers: [ + // your providers, e.g. GitHub, Google, Resend + ], +}); +``` + +The Drizzle equivalent is identical apart from the import, `adapter: DrizzleAdapter(db)` from `@auth/drizzle-adapter`, where `db` is your Drizzle instance. + +# Database vs JWT sessions {% #sessions %} + +Auth.js has two session strategies, and the adapter changes the default: + +- **`database`**: the default *once an adapter is configured*. A session row is written to the `Session` table and only an opaque session ID is stored in an `HttpOnly` cookie. Each request looks the session up in the native PostgreSQL database. Sessions can be revoked server-side. +- **`jwt`**: the default when no adapter is set. Session state lives entirely in a signed cookie, and the database is not read on the session path. + +You can set it explicitly: + +```ts +export const { handlers, auth } = NextAuth({ + adapter: PrismaAdapter(prisma), + session: { strategy: 'database' }, + providers: [], +}); +``` + +Even with `strategy: 'jwt'`, the adapter still persists users and linked accounts, so account linking and the admin view of users keep working; only the per-request session read moves off the database. + +# Pooling note {% #pooling %} + +On serverless and edge platforms (Vercel, Netlify, Cloudflare) every invocation is a fresh instance, so connecting straight to the engine fans out into more backend connections than it allows. Routing through the pooler in **transaction mode** (the default) absorbs that churn. Auth.js tables are write-light and read-heavy, exactly the access pattern transaction-mode pooling handles best, so the pooled `DATABASE_URL` above is the right default for the app runtime. + +Transaction mode does not hold a backend connection across statements, so server-side prepared statements are unavailable. The pooled Prisma URL carries `pgbouncer=true`, and Drizzle's `postgres.js` client should be created with `prepare: false`. Migrations always use the direct connection, so they keep a full session connection. If you need session-bound features, switch the pooler to **session mode**, see the [pooler modes](/docs/products/databases/postgresql/connection-pooling#modes) page for the trade-offs. + +# Use a branch for previews {% #branches %} + +[Branches](/docs/products/databases/postgresql/branches) are instant, isolated copies of a database with their own hostname and connection string, ideal for a pull-request preview or an integration-test job that signs users in and out against throwaway data: + +1. Create a branch from the API and read its `connectionString`. +2. Export the direct variant as `DIRECT_URL` and the pooled variant as `DATABASE_URL`. +3. Run `prisma migrate deploy` (or `drizzle-kit push`) and your auth flow against the branch. +4. Delete the branch when the job finishes. + +Because a branch starts from a storage snapshot, the Auth.js tables already exist with realistic data, so preview sign-ins behave like production without touching it. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/postgresql/integrations/prisma" title="Prisma" %} +Datasource, pooled and direct URLs, and migrations for the Prisma adapter. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/integrations/drizzle" title="Drizzle" %} +Driver connection, pooler settings, and drizzle-kit migrations for PostgreSQL. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/connection-pooling" title="Connection pooler" %} +Transaction vs session mode, ports, and serverless connection handling. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/integrations/better-auth" title="Better Auth" %} +The same pattern for Better Auth on a native PostgreSQL database. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/postgresql/integrations/better-auth/+page.markdoc b/src/routes/docs/products/databases/postgresql/integrations/better-auth/+page.markdoc new file mode 100644 index 00000000000..84a6f425d58 --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/integrations/better-auth/+page.markdoc @@ -0,0 +1,147 @@ +--- +layout: article +title: Better Auth +description: Use an Appwrite native PostgreSQL database as the database for Better Auth. Point runtime traffic at the pooler, run schema generation and migrations over a direct connection, and store users and sessions in PostgreSQL. +--- + +[Better Auth](https://www.better-auth.com/) is a framework-agnostic authentication library for TypeScript that keeps its state, including users, sessions, accounts, and verification tokens, in a database you own. A native PostgreSQL database gives Better Auth a standard PostgreSQL engine, so you can use the connection string from the [Connections](/docs/products/databases/postgresql/connections) page and run the Better Auth CLI to create the schema. + +{% info title="Before you start" %} +You'll need a native PostgreSQL database in a `ready` state and its credentials. See [PostgreSQL databases](/docs/products/databases/postgresql) to create one, then open the database and click **Credentials** to copy values from the **Details**, **DSN**, **.env**, **Prisma**, **Drizzle**, or **psql** tab. The primary user is `admin`, and each database has its own generated database name. +{% /info %} + +# Set the connection strings {% #connection-strings %} + +Better Auth needs two connection strings for the same database: a **pooled** URL for your application runtime and a **direct** URL for schema work. Copy the values from the Console **Credentials** dialog, or fetch them with [`postgresql.get()`](/docs/products/databases/postgresql/connections#credentials), and put both in your environment. Never commit them. + +```env +# Runtime: pooler port (transaction mode), absorbs serverless connection churn +DATABASE_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:6432/<database>?sslmode=require" + +# Schema generation & migrations: direct connection to the engine +DIRECT_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require" +``` + +Use `sslmode=require` for Appwrite Cloud connections. The edge proxy terminates TLS for every native PostgreSQL database, so drivers need no extra certificate configuration. See the [Network security](/docs/products/databases/postgresql/network-security) page for TLS and network controls. + +The PostgreSQL pooler runs on port `6432`, and the engine runs on port `5432`. See [Connections](/docs/products/databases/postgresql/connections) for connection details and the [connection pooler](/docs/products/databases/postgresql/connection-pooling#modes) page for the pool modes. + +# Configure Better Auth {% #configure %} + +Better Auth accepts either a **database instance** (a driver connection pool, which it drives through its built-in Kysely adapter) or an **ORM adapter** (Prisma, Drizzle, Kysely). Both work against a native PostgreSQL database. + +## With a driver pool {% #pool %} + +Pass a `pg` `Pool` pointed at the **pooled** URL so runtime traffic is multiplexed. Set `ssl: { rejectUnauthorized: true }` so the driver verifies the proxy's certificate: + +```ts +import { betterAuth } from 'better-auth'; +import { Pool } from 'pg'; + +export const auth = betterAuth({ + database: new Pool({ + connectionString: process.env.DATABASE_URL, + ssl: { rejectUnauthorized: true } + }), + emailAndPassword: { enabled: true } +}); +``` + +This is the simplest path: Better Auth manages the schema for you and can both generate and apply migrations through the CLI below. + +## With an ORM adapter {% #adapter %} + +If you already use an ORM, hand Better Auth an adapter instead of a raw `Pool`. Configure the ORM's own client against the native PostgreSQL database (pooled URL for the runtime client, direct URL for migrations), then wrap it: + +```ts +import { betterAuth } from 'better-auth'; +import { drizzleAdapter } from 'better-auth/adapters/drizzle'; +import { db } from './db'; + +export const auth = betterAuth({ + database: drizzleAdapter(db, { provider: 'pg' }), + emailAndPassword: { enabled: true } +}); +``` + +Set the ORM up against the native PostgreSQL database first, then return here for the schema steps. The [Prisma](/docs/products/databases/postgresql/integrations/prisma) and [Drizzle](/docs/products/databases/postgresql/integrations/drizzle) guides cover the `datasource`/client config, the pooled-vs-direct URL split, and how transaction-mode pooling affects prepared statements. + +# Generate and migrate the schema {% #migrate %} + +The [Better Auth CLI](https://www.better-auth.com/docs/concepts/cli) reads your `auth` config and creates the tables it needs. Run it with the **direct** URL so it gets a session connection with DDL privileges. Transaction-mode pooling is designed for short application transactions, not schema changes. + +Generate the schema for your setup. With a driver pool (built-in Kysely adapter) this produces a SQL file; with an ORM adapter it produces that ORM's schema (a Prisma schema, a Drizzle `schema.ts`): + +```bash +DATABASE_URL="$DIRECT_URL" npx @better-auth/cli@latest generate +``` + +If you're using the built-in adapter (a driver pool), apply the generated schema directly: + +```bash +DATABASE_URL="$DIRECT_URL" npx @better-auth/cli@latest migrate +``` + +`migrate` is only available for the built-in Kysely adapter. With a **Prisma** or **Drizzle** adapter, run `generate` to produce the schema, then apply it with that ORM's own migration tool, `prisma migrate deploy` or `drizzle-kit migrate`, again over `DIRECT_URL`. See the [Prisma](/docs/products/databases/postgresql/integrations/prisma#migrate) and [Drizzle](/docs/products/databases/postgresql/integrations/drizzle) guides for the exact commands. + +Both commands connect over the engine port, so they get full DDL privileges. The primary `admin` user owns the database and can run schema changes; narrower [database roles](/docs/products/databases/postgresql/connections#roles) (`readonly` / `readwrite`) intentionally cannot run DDL. + +# A minimal example {% #example %} + +After the schema exists, your runtime connects on the pooler URL and Better Auth handles the rest. Mount the handler for your framework and start creating users: + +```ts +import { auth } from './auth'; + +const result = await auth.api.signUpEmail({ + body: { + email: 'ada@example.com', + password: 'a-strong-password', + name: 'Ada Lovelace' + } +}); + +console.log(result.user.id); +``` + +On a long-running server, instantiate `auth` once and reuse it. On serverless, keep a single instance per module scope so warm invocations reuse it, and rely on the pooler to absorb cold-start connection churn. + +# Pooling and prepared statements {% #pooling %} + +Auth workloads, including sign-up, login, and session lookups, are short transactions, which is exactly what the pooler's default **transaction mode** is built for. Pointing your runtime pool at port `6432` lets a large number of serverless instances share a small backend pool. + +The one constraint of transaction mode is that it does not hold a backend connection across statements, so **server-side prepared statements are unavailable**. On PostgreSQL, the node-postgres `Pool` is fine with this out of the box. If you drive Better Auth through an ORM, disable prepared statements on the runtime client: with `postgres.js`, set `prepare: false`; with Prisma, add `pgbouncer=true` to the pooled URL. If your app needs prepared statements, advisory locks, or `LISTEN`/`NOTIFY`, switch the pooler to **session mode**. See the [pooler](/docs/products/databases/postgresql/connection-pooling#modes) page for the trade-offs. + +# Use a branch for previews and CI {% #branches %} + +[Branches](/docs/products/databases/postgresql/branches) are isolated copies of a database with their own hostname and connection string, ideal for running auth migrations against throwaway data in a pull-request preview or an integration-test job: + +1. Create a branch from the API and read its `connectionString`. +2. Export it as `DIRECT_URL` (and the pooled variant as `DATABASE_URL`). +3. Run the Better Auth CLI (or your ORM migration) and your test suite against the branch. +4. Delete the branch when the job finishes. + +Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so auth flows run against realistic data without touching production. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/postgresql/connections" title="Connect" %} +Retrieve credentials, rotate the password, and create scoped connection users. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/connection-pooling" title="Connection pooler" %} +Pool modes, ports, and read/write splitting for serverless workloads. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/integrations/prisma" title="Prisma" %} +Drive the Better Auth Prisma adapter with pooled and direct URLs. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/integrations/drizzle" title="Drizzle" %} +Drive the Better Auth Drizzle adapter against a native PostgreSQL database. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/branches" title="Branches" %} +Ephemeral database copies for preview environments and CI. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/integrations/auth-js" title="Auth.js" %} +Use a native PostgreSQL database as the Auth.js (NextAuth) database. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/postgresql/integrations/dbt/+page.markdoc b/src/routes/docs/products/databases/postgresql/integrations/dbt/+page.markdoc new file mode 100644 index 00000000000..2165f8b3295 --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/integrations/dbt/+page.markdoc @@ -0,0 +1,114 @@ +--- +layout: article +title: dbt +description: Run dbt transformations against an Appwrite native PostgreSQL database. Configure profiles.yml for the direct engine port, size threads to your connection budget, and test models against a branch in CI. +--- + +An Appwrite native PostgreSQL database is a standard PostgreSQL server, so [dbt](https://docs.getdbt.com/) works against it through the standard [`dbt-postgres`](https://docs.getdbt.com/docs/local/connect-data-platform/postgres-setup) adapter with no Appwrite-specific configuration. Point a `profiles.yml` target at the connection details from the [Connections](/docs/products/databases/postgresql/connections) page and use `dbt debug`, `dbt run`, and `dbt build` the same way you would against any self-hosted PostgreSQL warehouse. + +dbt compiles your models into `CREATE TABLE` / `CREATE VIEW` statements and runs them in dependency order, materializing a transformed analytics layer inside a schema you control. + +{% info title="Before you start" %} +You'll need a native PostgreSQL database in a `ready` state and its credentials. See [PostgreSQL](/docs/products/databases/postgresql) to create one and [Connections](/docs/products/databases/postgresql/connections) to retrieve them. The primary user is `admin`, the database name is generated for each database, and the engine listens on port `5432`. +{% /info %} + +# Create a build schema {% #build-schema %} + +dbt issues DDL (`CREATE`, `DROP`, `ALTER`) to build your models, so connect with the primary `admin` user and build into a schema reserved for dbt, for example `analytics`. The `admin` user owns the generated database and can create the schema, tables, and views dbt needs. + +Keep transformed tables separate from source data by setting the `schema` field in `profiles.yml`. You can use different schema names per environment, such as `analytics_dev`, `analytics_ci`, and `analytics_prod`. + +# Configure profiles.yml {% #profiles %} + +dbt reads connection details from `~/.dbt/profiles.yml` or the project directory. Configure a `postgres` target against the database host on port `5432`, and set `sslmode: require` so the connection is encrypted: + +```yaml +analytics: + target: dev + outputs: + dev: + type: postgres + host: db-<hash>.<region>.appwrite.center + port: 5432 + user: admin + password: "{{ env_var('APPWRITE_DB_PASSWORD') }}" + dbname: <database> + schema: analytics + sslmode: require + threads: 4 +``` + +The dbt-postgres adapter uses `password` (not `pass`) and `dbname` (you may also write `database`). Read the password from an environment variable with `env_var` rather than committing it. The `schema` key is where dbt materializes models, set it to the build schema your user owns. + +The edge proxy terminates TLS for every native PostgreSQL database, so `sslmode: require` needs no extra certificate files. For full certificate verification, set `sslmode: verify-full` with `sslrootcert` pointing at a trusted root store, `system` on libpq 16+, or your OS bundle such as `/etc/ssl/certs/ca-certificates.crt`. The proxy's certificate is signed by a public CA, so there is no Appwrite-specific CA to download. + +# Test the connection {% #debug %} + +`dbt debug` validates your project files and opens a connection to confirm the credentials and host are correct: + +```bash +dbt debug +``` + +A successful run reports `Connection test: OK connection ok`. If it fails, recheck the host, port, `sslmode`, and password. + +# Run transformations {% #run %} + +Build your models into the analytics schema: + +```bash +dbt run +``` + +`dbt run` executes models only, materializing each as a table or view. Use `dbt build` to run models, tests, seeds, and snapshots together in DAG order, a failing test on an upstream model then skips its dependents: + +```bash +dbt build +``` + +# Choose the right connection {% #connection %} + +dbt opens one database connection per thread and uses each to run DDL and rely on session state (search paths, temporary objects, transactions spanning multiple statements). Point dbt at a connection that preserves that session: + +- **Direct engine port (`5432`)** is the simplest and recommended target. Each thread gets a backend session with full DDL privileges. This is what the `profiles.yml` above uses. +- **Session-mode pooler** also works, because it holds a backend connection for the whole client session. Connect on the pooler port (`6432`) and switch the pool to `session` mode. + +Do **not** point dbt at the **transaction-mode** pooler, which is the pooler default. Transaction mode returns the backend connection to the pool after every statement, so the session state and multi-statement DDL that dbt depends on can break. See the [connection pooling](/docs/products/databases/postgresql/connection-pooling#modes) page for the mode trade-offs. + +# Size threads to your connection budget {% #threads %} + +The `threads` setting controls how many models dbt builds in parallel, and dbt opens one connection per thread. A `threads: 8` run can hold up to eight backend connections at once. dbt also respects model dependencies, so it never runs more models concurrently than your DAG allows, regardless of the thread count. + +Pick a `threads` value that fits the connection budget for your database spec, and leave headroom for any application traffic sharing the same database. If you connect through a session-mode pooler, the same per-thread connections apply at the backend, so size against the pool, not the client side. Start at the adapter default of `4` and raise it only while connections stay within budget. + +# Test transformations against a branch {% #branches %} + +[Branches](/docs/products/databases/postgresql/branches) are isolated copies of a database with their own hostname and connection string. Because a branch starts from a storage snapshot, its schema and data match the source at branch time, so dbt models run against production-like data without touching production. That makes branches ideal for validating transformations in CI: + +1. Create a branch from the API and read its `connectionString`. +2. Set the branch host and credentials as the `profiles.yml` target (or export them as `env_var` values). +3. Run `dbt build` against the branch so models and tests execute on realistic data. +4. Delete the branch when the job finishes. + +This gives every pull request a clean, production-shaped warehouse to build against without touching live analytics tables. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/postgresql/connections" title="Connections" %} +Retrieve credentials and rotate the primary password. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/connection-pooling" title="Connection pooler" %} +Pool modes and ports. Use session mode for dbt, never transaction mode. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/branches" title="Branches" %} +Ephemeral database copies for CI and preview environments. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/network-security" title="Network" %} +TLS modes, certificate verification, and IP allowlists. +{% /cards_item %} +{% /cards %} + +{% arrow_link href="https://docs.getdbt.com/docs/local/connect-data-platform/postgres-setup" %} +dbt-postgres adapter reference +{% /arrow_link %} diff --git a/src/routes/docs/products/databases/postgresql/integrations/django/+page.markdoc b/src/routes/docs/products/databases/postgresql/integrations/django/+page.markdoc new file mode 100644 index 00000000000..717b7ebdfd4 --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/integrations/django/+page.markdoc @@ -0,0 +1,145 @@ +--- +layout: article +title: Django +description: Use Django's ORM with an Appwrite native PostgreSQL database. Configure the DATABASES setting with TLS options, run migrations on the direct PostgreSQL port, and tune persistent connections for pooling. +--- + +A native PostgreSQL database is a standard PostgreSQL engine, so Django's ORM works against it with no Appwrite-specific configuration. Point the `DATABASES` setting at the credentials from the [Connections](/docs/products/databases/postgresql/connections) page, then use migrations, models, and the rest of Django exactly as you would against any PostgreSQL server. + +{% info title="Before you start" %} +You'll need a native PostgreSQL database in a `ready` state and its credentials. See [native PostgreSQL databases](/docs/products/databases/postgresql) to create one and [Connections](/docs/products/databases/postgresql/connections) to retrieve the connection details. The primary user is `admin`, and the database name is generated for each database. +{% /info %} + +# Install a driver {% #driver %} + +Django talks to PostgreSQL through a driver. Use [psycopg 3](https://www.psycopg.org/psycopg3/): + +```bash +pip install "psycopg[binary]" +``` + +Django's PostgreSQL backend uses the `ENGINE` value `django.db.backends.postgresql`. + +# Configure `DATABASES` {% #databases %} + +In `settings.py`, point the `default` connection at your native PostgreSQL database and require TLS through `OPTIONS`. Appwrite Cloud terminates TLS at the edge, and the certificate is signed by a public certificate authority. Read every value from the environment so credentials stay out of source control: + +```python +import os + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.postgresql", + "NAME": os.environ["DB_NAME"], + "USER": os.environ["DB_USER"], + "PASSWORD": os.environ["DB_PASSWORD"], + "HOST": os.environ["DB_HOST"], + "PORT": os.environ["DB_PORT"], + "OPTIONS": { + "sslmode": "require", + }, + } +} +``` + +The PostgreSQL backend forwards everything in `OPTIONS` to the driver's connection, so `sslmode` is honored the same way `psql` honors it in a connection string. For full certificate verification, set `"sslmode": "verify-full"` with `"sslrootcert"` pointing at a trusted root store or your OS bundle, such as `/etc/ssl/certs/ca-certificates.crt`. See [Network security](/docs/products/databases/postgresql/network-security) for TLS and IP allowlist guidance. + +Populate the environment from the Console **Credentials** dialog or from `postgresql.get()` in the [API credentials flow](/docs/products/databases/postgresql/connections#credentials): + +```env +DB_NAME=<database> +DB_USER=admin +DB_PASSWORD=<password> +DB_HOST=db-<hash>.<region>.appwrite.center +DB_PORT=5432 +``` + +Port `5432` is the direct PostgreSQL port. Keep migrations on this port, and see [pooling](#pooling) below for when to add the pooler. + +# Run migrations {% #migrate %} + +Generate migrations from your models, then apply them against the direct PostgreSQL port: + +```bash +python manage.py makemigrations +python manage.py migrate +``` + +`migrate` needs a session connection with DDL privileges. The primary `admin` user owns the database and can run schema changes. Narrower [database roles](/docs/products/databases/postgresql/connections#roles) (`readonly` or `readwrite`) are intended for application traffic with reduced privileges. Always run `migrate` on the direct PostgreSQL port, not through the transaction-mode pooler. + +# Define a model {% #model %} + +Models map to tables in your native PostgreSQL database. Define one in an app's `models.py`: + +```python +from django.db import models + +class Article(models.Model): + title = models.CharField(max_length=200) + body = models.TextField() + created_at = models.DateTimeField(auto_now_add=True) + + class Meta: + ordering = ["-created_at"] +``` + +Run `makemigrations` and `migrate` again to create the table, then query it through the ORM: + +```python +from blog.models import Article + +Article.objects.create(title="Hello", body="First post") + +recent = Article.objects.order_by("-created_at")[:10] +``` + +# Persistent connections and pooling {% #pooling %} + +By default Django opens a new connection per request (`CONN_MAX_AGE = 0`). On a long-running WSGI server (Gunicorn, uWSGI) you can reuse connections by raising it. Each worker thread keeps its own connection, so the database must allow at least as many connections as you run worker threads: + +```python +DATABASES["default"]["CONN_MAX_AGE"] = 60 +DATABASES["default"]["CONN_HEALTH_CHECKS"] = True +``` + +`CONN_HEALTH_CHECKS` revalidates a reused connection once per request, avoiding errors after an engine restart. Do not enable persistent connections under the development server, it spawns a thread per request and gains nothing, and disable them under ASGI. + +{% info title="Persistent connections need a session" %} +A worker holding a connection across requests behaves like a long-lived session. Connect it to the direct PostgreSQL port or the **session-mode** pooler. The default **transaction-mode** pooler can hand each statement a different backend connection, which makes it a better fit for serverless and short-lived runtimes that open and close a connection per invocation. +{% /info %} + +If you front the database with the transaction-mode [connection pooler](/docs/products/databases/postgresql/connection-pooling), point `HOST` and `PORT` at the PostgreSQL pooler on port `6432`. Also set `DISABLE_SERVER_SIDE_CURSORS = True`, since server-side cursors can't survive being moved between backend connections: + +```python +DATABASES["default"]["DISABLE_SERVER_SIDE_CURSORS"] = True +``` + +For prepared statements, advisory locks, `LISTEN`/`NOTIFY`, or temporary tables, use **session mode** instead, see the [pooler](/docs/products/databases/postgresql/connection-pooling#modes) page for the trade-offs. + +# Use a branch for previews and CI {% #branches %} + +[Branches](/docs/products/databases/postgresql/branches) are instant, isolated copies of a database with their own hostname and connection details. They're ideal for running migrations against throwaway data in a pull-request preview or an integration-test job: + +1. Create a branch from the API and read its connection details. +2. Export them as the `DB_*` environment variables your settings read. +3. Run `python manage.py migrate` and your test suite against the branch. +4. Delete the branch when the job finishes. + +Because a branch starts from a storage snapshot, its schema and data match the source database at branch time, so migrations run against realistic data without touching production. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/postgresql/connections" title="Connections" %} +Retrieve credentials, rotate the password, and create scoped database roles. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/connection-pooling" title="Connection pooler" %} +Pool modes, ports, and read/write splitting for serverless workloads. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/branches" title="Branches" %} +Ephemeral database copies for preview environments and CI. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/network-security" title="Network" %} +TLS, certificate verification, IP allowlists, and idle timeout settings. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/postgresql/integrations/drivers/+page.markdoc b/src/routes/docs/products/databases/postgresql/integrations/drivers/+page.markdoc new file mode 100644 index 00000000000..39897535fb8 --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/integrations/drivers/+page.markdoc @@ -0,0 +1,197 @@ +--- +layout: article +title: Node.js drivers +description: Connect to an Appwrite native PostgreSQL database from Node.js with node-postgres or postgres.js. Configure pools, TLS verification, serverless connection management, and troubleshooting. +--- + +A native PostgreSQL database is a standard PostgreSQL engine, so any Node.js driver that speaks the PostgreSQL wire protocol can connect over TLS without an Appwrite-specific adapter. The [Connections](/docs/products/databases/postgresql/connections#drivers) page shows the minimal snippet to run your first query. This page covers production pool configuration, certificate verification, serverless connection management, and common connection errors. + +{% info title="Before you start" %} +You'll need a native PostgreSQL database in a `ready` state and its credentials. In the Console, open the database and click **Credentials**. Use the **Details** tab for individual values, or the **DSN**, **.env**, **Prisma**, **Drizzle**, or **psql** tab for a ready-made snippet. The primary user is `admin`, and the database name is generated for each database. Keep the password in an environment variable and never commit it. +{% /info %} + +# Raw driver, ORM, or SQL API? {% #choosing %} + +There are three common ways to reach a native PostgreSQL database from Node.js. Pick by workload: + +| Approach | Use it when | +|------------------------------|----------------------------------------------------------------------------------------------| +| **Raw driver** (this page) | You want a connection pool you control, hot-path queries, or a thin data layer with little overhead. | +| **ORM** ([Prisma](/docs/products/databases/postgresql/integrations/prisma), [Drizzle](/docs/products/databases/postgresql/integrations/drizzle)) | You want migrations, a typed schema, and query building. The ORM still uses a PostgreSQL driver underneath. | +| **[SQL API](/docs/products/databases/postgresql/quick-start#first-queries)** | You're on an edge runtime that cannot hold a TCP socket, or scripting a one-off query over HTTPS. | + +The rest of this page is about raw drivers on a long-running or serverless Node.js server that can open TCP connections. + +# node-postgres (pg) {% #node-postgres %} + +For a long-running server, create one `Pool` at startup and reuse it for every request. The pool opens connections lazily up to `max` and hands them back to your handlers. + +```js +import { Pool } from 'pg'; +import process from 'node:process'; + +const pool = new Pool({ + host: 'db-<hash>.<region>.appwrite.center', + port: 5432, + user: 'admin', + password: process.env.DB_PASSWORD, + database: '<database>', + ssl: { rejectUnauthorized: true }, + max: 10, + idleTimeoutMillis: 30000, + connectionTimeoutMillis: 5000, +}); + +const { rows } = await pool.query('SELECT id, email FROM users WHERE id = $1', [userId]); +``` + +Pool sizing is a budget. Each connection in `max`, across every running instance, counts against your specification's connection cap. A single app server with `max: 10` is fine; ten replicas with `max: 50` each is 500 connections and can exhaust many specifications. Size `max` to the connection cap divided by replica count, then put the [connection pooler](/docs/products/databases/postgresql/connection-pooling) in front if you need more client concurrency than that allows. + +`ssl: { rejectUnauthorized: true }` validates the server certificate against Node's built-in CA store. The edge proxy presents a certificate signed by a well-known public CA, so this works without supplying your own bundle. See [TLS and CA verification](#tls) for custom CA bundles and mTLS. + +## Pooled port vs direct port {% #pg-ports %} + +The pooler defaults to **transaction mode**, which does not hold a backend connection across statements, so server-side prepared statements are unavailable. In node-postgres, a query becomes a server-side prepared statement only when you pass a `name` field on the query config: + +```js +// Safe on the pooler (port 6432, transaction mode): no `name`, no server-side prepared statement. +await pool.query('SELECT * FROM events WHERE user_id = $1', [userId]); + +// Use the direct port or session-mode pooler for named prepared statements. +await pool.query({ name: 'fetch-events', text: 'SELECT * FROM events WHERE user_id = $1', values: [userId] }); +``` + +Parameterized queries without a `name` are sent fresh each time and work on the pooled port. If you rely on named prepared statements, advisory locks, `LISTEN`/`NOTIFY`, temporary tables, or `SET LOCAL`, connect on the direct port (`5432`) or switch the pooler to **session mode**. See the [pooler modes](/docs/products/databases/postgresql/connection-pooling#modes) page for the trade-offs. Run migrations on the direct port. + +# postgres.js {% #postgres-js %} + +`postgres.js` (the `postgres` package) takes its own option names. Create the client once at module scope: + +```js +import postgres from 'postgres'; +import process from 'node:process'; + +const sql = postgres({ + host: 'db-<hash>.<region>.appwrite.center', + port: 5432, + username: 'admin', + password: process.env.DB_PASSWORD, + database: '<database>', + ssl: 'verify-full', + max: 10, + idle_timeout: 30, + connect_timeout: 10, +}); + +const users = await sql`SELECT id, email FROM users WHERE id = ${userId}`; +``` + +`ssl: 'verify-full'` enables TLS and verifies the server certificate against the system CA store. If your runtime image ships without one, pass an object with a public CA bundle instead: `ssl: { rejectUnauthorized: true, ca: fs.readFileSync('./ca-bundle.crt') }`. + +When you point `postgres.js` at the **pooler port** (`6432`) in transaction mode, disable prepared statements. The library uses prepared statements by default, and documents `prepare: false` for PgBouncer transaction mode: + +```js +const sql = postgres({ + host: 'db-<hash>.<region>.appwrite.center', + port: 6432, + username: 'admin', + password: process.env.DB_PASSWORD, + database: '<database>', + ssl: 'verify-full', + prepare: false, + max: 5, +}); +``` + +Leave `prepare` at its default when you connect on the direct port or use the pooler in session mode. + +# TLS and CA verification {% #tls %} + +Connections on Appwrite Cloud are encrypted with TLS, terminated at the edge and forwarded to your database over the internal network. The connection string from the credentials dialog carries the right SSL settings for your environment, and the examples above use strict certificate verification. + +| Level | node-postgres | postgres.js | +|----------------------------------------------|-------------------------------------------------|--------------------------------------------------| +| **Encrypt and verify CA** | `ssl: { rejectUnauthorized: true }` | `ssl: 'verify-full'` | +| **Custom CA bundle** (no system trust store) | `ssl: { rejectUnauthorized: true, ca: fs.readFileSync('./ca-bundle.crt') }` | `ssl: { rejectUnauthorized: true, ca: fs.readFileSync('./ca-bundle.crt') }` | + +The certificate behind every database hostname is signed by a well-known public CA, so Appwrite does not require a custom CA download. The `ca` option is useful for runtimes without a system trust store, such as distroless or scratch images. Point it at a standard public CA bundle from your base image's `ca-certificates` package or [Mozilla's bundle](https://curl.se/docs/caextract.html): + +```js +import fs from 'node:fs'; + +const ssl = { + rejectUnauthorized: true, + ca: fs.readFileSync('./ca-bundle.crt'), +}; +``` + +When the CA comes from an environment variable, restore the newlines the variable strips: + +```js +const ssl = { + rejectUnauthorized: true, + ca: process.env.DB_SSL_CA?.replace(/\\n/g, '\n'), +}; +``` + +Do not ship `rejectUnauthorized: false` to production. It disables certificate validation and exposes the connection to interception. To require **mTLS**, where the client also presents a certificate, see the [Network security](/docs/products/databases/postgresql/network-security) page. Then add `key` and `cert` alongside `ca` in the same `ssl` object. + +# Serverless connection management {% #serverless %} + +On Lambda, Cloud Run, Vercel, and similar platforms, every cold start is a fresh instance with its own pool. A `max: 10` pool times 200 concurrent instances is 2,000 backend connections, which is more than most specifications allow. Let Appwrite's pooler absorb the fan-out: + +- **Connect on the pooler port** (`6432`) in transaction mode. +- **Keep the per-instance pool tiny**, usually `max: 1` or `2`. One invocation rarely needs more than one connection at a time. +- **Create the client once per module scope** so warm invocations reuse it. +- **Disable per-connection prepared statements** on the pooled port: `prepare: false` for postgres.js, and avoid `name` on node-postgres query configs. + +```js +import postgres from 'postgres'; +import process from 'node:process'; + +// Module scope: reused across warm invocations. +const sql = postgres({ + host: 'db-<hash>.<region>.appwrite.center', + port: 6432, + username: 'admin', + password: process.env.DB_PASSWORD, + database: '<database>', + ssl: 'verify-full', + prepare: false, + max: 1, +}); + +export async function handler(event) { + const rows = await sql`SELECT id FROM users WHERE email = ${event.email}`; + return rows[0] ?? null; +} +``` + +On **edge runtimes** (Cloudflare Workers, Vercel Edge, Deno Deploy), raw TCP sockets are usually unavailable. Use the [SQL API](/docs/products/databases/postgresql/quick-start#first-queries) for these workloads. + +# Troubleshooting {% #troubleshooting %} + +| Symptom | Cause and fix | +|-------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------| +| `self-signed certificate` / `unable to verify the first certificate` | TLS is reaching the wrong host, or the runtime has a stale or incomplete `ca` bundle. The proxy's certificate is signed by a public CA. Use driver certificate verification with the system trust store, and supply a current public CA bundle only when the runtime has no system trust store. | +| `sorry, too many clients already` / connection attempts rejected at the proxy | Aggregate pool size across all instances exceeds the database specification's connection cap. Lower `max`, or move to the [pooler](/docs/products/databases/postgresql/connection-pooling) port and shrink the per-instance pool to 1 or 2. | +| `prepared statement "S_1" does not exist` / `unnamed prepared statement does not exist` | Server-side prepared statements on the pooler in transaction mode. Set `prepare: false` for postgres.js, drop the `name` field for node-postgres, or use the direct port or [session mode](/docs/products/databases/postgresql/connection-pooling#modes). | +| `ETIMEDOUT` / `connection timed out` on connect | The database may be cold-starting on smaller specifications or blocked by an [IP allowlist](/docs/products/databases/postgresql/network-security#ip-allowlist). Confirm your egress IP is allowed and raise `connectionTimeoutMillis` or `connect_timeout` to absorb cold starts. | +| Connections drop after a period of inactivity | The proxy closes idle connections after `networkIdleTimeoutSeconds`. Keep `idleTimeoutMillis` and `idle_timeout` below that window so the driver recycles before the proxy does, or enable keep-alive. | + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/postgresql/connections" title="Connections" %} +Retrieve credentials, rotate the password, and create scoped connection users. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/connection-pooling" title="Connection pooler" %} +Pool modes, ports, and read/write splitting for serverless workloads. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/quick-start#first-queries" title="SQL API" %} +Run SQL over HTTPS with no TCP connection for edge runtimes and scripts. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/network-security" title="Network security" %} +TLS, certificate verification, mTLS, and IP allowlists. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/postgresql/integrations/drizzle/+page.markdoc b/src/routes/docs/products/databases/postgresql/integrations/drizzle/+page.markdoc new file mode 100644 index 00000000000..09c9b64105f --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/integrations/drizzle/+page.markdoc @@ -0,0 +1,193 @@ +--- +layout: article +title: Drizzle +description: Use Drizzle ORM with an Appwrite native PostgreSQL database. Configure the driver, run migrations against the direct connection, and pool runtime traffic from serverless environments. +--- + +Appwrite's native PostgreSQL database is a standard PostgreSQL engine, so [Drizzle ORM](https://orm.drizzle.team/) works against it with no Appwrite-specific configuration. Point Drizzle's driver at the connection string from the [Connections](/docs/products/databases/postgresql/connections) page and use Drizzle Kit, the query builder, and the rest of the toolchain as you would against any PostgreSQL server. + +{% info title="Before you start" %} +You'll need a native PostgreSQL database in a `ready` state and its credentials. See [native PostgreSQL databases](/docs/products/databases/postgresql) to create one and [Connections](/docs/products/databases/postgresql/connections) to retrieve the connection string. The primary user is `admin`, and the database name is generated per database. +{% /info %} + +# Set the connection string {% #connection-string %} + +In the Console, open your database and click **Credentials**. Copy the connection string from the **DSN**, **.env**, or **Drizzle** tab, or fetch it with [`postgresql.get()`](/docs/products/databases/postgresql/connections#credentials). Put it in your environment, never commit it: + +```env +DATABASE_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require" +``` + +The TLS parameter (`sslmode=require`) is part of the connection string Appwrite returns. Appwrite Cloud terminates TLS at the edge and forwards traffic to your database over the internal network, so no extra certificate configuration is needed. For full certificate verification (`verify-full`) or mTLS, see [Network security](/docs/products/databases/postgresql/network-security). + +# Install and configure the driver {% #driver %} + +Drizzle talks to PostgreSQL through one of two driver packages. Pick the one already in your stack: + +```bash +# node-postgres +npm install drizzle-orm pg +npm install -D drizzle-kit @types/pg + +# or postgres.js +npm install drizzle-orm postgres +npm install -D drizzle-kit +``` + +With **node-postgres**, import `drizzle` from `drizzle-orm/node-postgres` and hand it the connection string: + +```ts +import { drizzle } from 'drizzle-orm/node-postgres'; + +export const db = drizzle(process.env.DATABASE_URL!); +``` + +With **postgres.js**, import `drizzle` from `drizzle-orm/postgres-js`. For a direct connection to the engine you can pass the URL straight through: + +```ts +import { drizzle } from 'drizzle-orm/postgres-js'; + +export const db = drizzle(process.env.DATABASE_URL!); +``` + +When you pool through the connection pooler in transaction mode (see [Pool connections from serverless](#pooling)), postgres.js must disable prepared statements. Build the client yourself with `prepare: false` and pass it to `drizzle`: + +```ts +import { drizzle } from 'drizzle-orm/postgres-js'; +import postgres from 'postgres'; + +const client = postgres(process.env.DATABASE_URL!, { prepare: false }); + +export const db = drizzle({ client }); +``` + +Define your tables in a schema file Drizzle Kit can read: + +```ts +import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core'; + +export const users = pgTable('users', { + id: serial('id').primaryKey(), + email: text('email').notNull().unique(), + createdAt: timestamp('created_at').notNull().defaultNow() +}); +``` + +# Configure Drizzle Kit {% #config %} + +Drizzle Kit reads `drizzle.config.ts` for migrations and introspection. Set the `dialect`, point `schema` at your table definitions, and pass the connection string through `dbCredentials`: + +```ts +import { defineConfig } from 'drizzle-kit'; + +export default defineConfig({ + dialect: 'postgresql', + schema: './src/schema.ts', + out: './drizzle', + dbCredentials: { + url: process.env.DATABASE_URL! + } +}); +``` + +# Run migrations {% #migrate %} + +Generate SQL migration files from your schema, then apply them: + +```bash +npx drizzle-kit generate +npx drizzle-kit migrate +``` + +`generate` diffs your schema against the last snapshot and writes a timestamped `.sql` file into the `out` directory; `migrate` applies any pending files to the database. Point Drizzle Kit at the **direct** engine port (`5432`), not the pooler, because migrations issue DDL that needs a session-level connection. The primary `admin` user owns the generated database and can run schema changes. Use narrower [database roles](/docs/products/databases/postgresql/connections#roles) for application traffic that does not need DDL privileges. + +To apply migrations from your application at startup instead of the CLI, use the matching `migrate` helper for your driver: + +```ts +import { migrate } from 'drizzle-orm/node-postgres/migrator'; +import { db } from './db'; + +await migrate(db, { migrationsFolder: './drizzle' }); +``` + +# Query with Drizzle {% #query %} + +Once the schema is migrated, use the query builder: + +```ts +import { desc } from 'drizzle-orm'; +import { db } from './db'; +import { users } from './schema'; + +const [user] = await db + .insert(users) + .values({ email: 'ada@example.com' }) + .returning(); + +const recent = await db + .select() + .from(users) + .orderBy(desc(users.createdAt)) + .limit(10); +``` + +On long-running servers, create the `db` instance once at module scope and reuse it. On serverless, keep a single instance per module scope so warm invocations reuse it, and rely on the pooler to absorb cold-start connection churn. + +# Pool connections from serverless {% #pooling %} + +Each running instance opens its own connections to the engine. On serverless and edge platforms (Vercel, Netlify, Cloudflare), short-lived instances can fan out into more backend connections than the engine allows. Route runtime traffic through the [connection pooler](/docs/products/databases/postgresql/connection-pooling) by connecting on the pooler port (`6432`) on the same hostname. + +The pooler defaults to **transaction mode**, which does not keep a backend connection across statements, so server-side prepared statements are unavailable. Keep `drizzle.config.ts` and the startup migrator pointed at `DIRECT_URL` so DDL still runs over a session-level connection: + +```env +# Runtime: pooled, transaction mode +DATABASE_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:6432/<database>?sslmode=require" + +# Migrations & introspection: direct connection to the engine +DIRECT_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require" +``` + +With **postgres.js**, disable prepared statements by building the client with `prepare: false`, as shown in [Install and configure the driver](#driver). + +```ts +import { defineConfig } from 'drizzle-kit'; + +export default defineConfig({ + dialect: 'postgresql', + schema: './src/schema.ts', + out: './drizzle', + dbCredentials: { + url: process.env.DIRECT_URL! + } +}); +``` + +If your application relies on prepared statements, advisory locks, `LISTEN`/`NOTIFY`, temporary tables, or `SET LOCAL`, switch the pooler to **session mode** and remove `prepare: false` with postgres.js. See the [pooler](/docs/products/databases/postgresql/connection-pooling#modes) page for the trade-offs. + +# Use a branch for previews and CI {% #branches %} + +[Branches](/docs/products/databases/postgresql/branches) are isolated copies of a database with their own hostname and connection string. They're ideal for running migrations against throwaway data in a pull-request preview or an integration-test job: + +1. Create a branch from the API and read its `connectionString`. +2. Export it as `DIRECT_URL` (and the pooled variant as `DATABASE_URL`). +3. Run `drizzle-kit migrate` and your test suite against the branch. +4. Delete the branch when the job finishes. + +Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations run against realistic data without touching production. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/postgresql/connections" title="Connections" %} +Retrieve credentials, rotate the password, and create database roles. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/connection-pooling" title="Connection pooler" %} +Pool modes, ports, and read/write splitting for serverless workloads. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/branches" title="Branches" %} +Ephemeral database copies for preview environments and CI. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/network-security" title="Network security" %} +TLS modes, certificate verification, mTLS, and IP allowlists. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/postgresql/integrations/ef-core/+page.markdoc b/src/routes/docs/products/databases/postgresql/integrations/ef-core/+page.markdoc new file mode 100644 index 00000000000..b362344b0d9 --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/integrations/ef-core/+page.markdoc @@ -0,0 +1,168 @@ +--- +layout: article +title: EF Core +description: Use Entity Framework Core with an Appwrite native PostgreSQL database. Configure the Npgsql connection string, run migrations against the direct PostgreSQL host, and rely on the driver's built-in connection pool from an ASP.NET server. +--- + +A native PostgreSQL database is a standard PostgreSQL engine, so [Entity Framework Core](https://learn.microsoft.com/ef/core/) works against it with no Appwrite-specific configuration. Point the Npgsql EF Core provider at the connection string from the [Connections](/docs/products/databases/postgresql/connections) page and use `DbContext`, migrations, and the rest of the toolchain exactly as you would against any self-hosted PostgreSQL server. + +{% info title="Before you start" %} +You'll need a native PostgreSQL database in a `ready` state and its credentials. See [native PostgreSQL databases](/docs/products/databases/postgresql) to create one and [Connections](/docs/products/databases/postgresql/connections) to retrieve the connection details. Appwrite provides the generated database name, primary username `admin`, and password in the Console. +{% /info %} + +# Install the provider {% #install %} + +Add the EF Core provider for PostgreSQL: + +```bash +dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL +``` + +Then add the EF Core design-time package (used by the `dotnet ef` tools) and install the CLI tool: + +```bash +dotnet add package Microsoft.EntityFrameworkCore.Design + +dotnet tool install --global dotnet-ef +``` + +If your app targets an older framework than the current .NET release, pin the packages to the matching major version instead, for example `--version 8.*` for a .NET 8 app. + +# Set the connection string {% #connection-string %} + +ADO.NET providers use key/value connection strings rather than a URL. Store it in `appsettings.json` under `ConnectionStrings`, and keep the password out of source control (use [user secrets](https://learn.microsoft.com/aspnet/core/security/app-secrets) or an environment variable in deployments): + +```json +{ + "ConnectionStrings": { + "Default": "Host=db-<hash>.<region>.appwrite.center;Port=5432;Database=<database>;Username=admin;Password=<password>;SSL Mode=Require" + } +} +``` + +The edge proxy terminates TLS for every native PostgreSQL database, so `SSL Mode=Require` is all you need. `Require` encrypts the connection without validating the server certificate hostname. For full certificate verification, set `SSL Mode=VerifyFull`: Npgsql validates the chain and hostname against the operating system's trusted roots, and the proxy's certificate is signed by a public CA those roots already include, so no `Root Certificate` parameter is needed: + +```text +Host=...;Port=5432;Database=<database>;Username=admin;Password=<password>;SSL Mode=VerifyFull +``` + +# Configure the DbContext {% #dbcontext %} + +Define your model and a `DbContext`. The model is ordinary EF Core code, and the provider-specific call comes when you register the context in `Program.cs`: + +```csharp +using Microsoft.EntityFrameworkCore; + +public class User +{ + public int Id { get; set; } + public string Email { get; set; } = string.Empty; + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; +} + +public class AppDbContext : DbContext +{ + public AppDbContext(DbContextOptions<AppDbContext> options) + : base(options) { } + + public DbSet<User> Users => Set<User>(); +} +``` + +# Run migrations {% #migrate %} + +Migrations need a session connection and full DDL privileges, so always run them against the direct PostgreSQL host on port `5432`, not the [pooler](/docs/products/databases/postgresql/connection-pooling). The primary `admin` user owns the database; narrower [connection users](/docs/products/databases/postgresql/connections) (`readonly` / `readwrite`) intentionally cannot run DDL. + +Create the first migration, then apply it: + +```bash +dotnet ef migrations add InitialCreate + +dotnet ef database update +``` + +`dotnet ef database update` reads the same `Default` connection string and applies any pending migrations, recording each in the `__EFMigrationsHistory` table so it only applies new ones next time. In CI or production, prefer generating an idempotent SQL script with `dotnet ef migrations script --idempotent` and applying it as a deploy step. + +# Wire up an ASP.NET server {% #aspnet %} + +Register the `DbContext` in `Program.cs`, reading the connection string with `GetConnectionString`. A long-running ASP.NET server should connect to the direct PostgreSQL host and let the provider manage its own pool: + +Call `UseNpgsql(...)` with the connection string. This is the single entry point for all Npgsql options: + +```csharp +using Microsoft.EntityFrameworkCore; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddDbContext<AppDbContext>(options => + options.UseNpgsql(builder.Configuration.GetConnectionString("Default"))); +``` + +The route handlers use ordinary EF Core queries: + +```csharp +var app = builder.Build(); + +app.MapGet("/users", async (AppDbContext db) => + await db.Users.OrderByDescending(u => u.CreatedAt).Take(10).ToListAsync()); + +app.MapPost("/users", async (AppDbContext db, string email) => +{ + var user = new User { Email = email }; + db.Users.Add(user); + await db.SaveChangesAsync(); + return Results.Created($"/users/{user.Id}", user); +}); + +app.Run(); +``` + +# Connection pooling {% #pooling %} + +Npgsql has a built-in connection pool, enabled by default. Each process keeps its own pool keyed on the connection string, so a long-running ASP.NET server usually connects directly to PostgreSQL and tunes the driver pool with the connection string: + +```text +Host=...;Port=5432;Database=<database>;Username=admin;Password=<password>;SSL Mode=Require;Maximum Pool Size=50 +``` + +Set `Maximum Pool Size` to cap concurrent backend connections per process (Npgsql defaults to `100`). + +Keep the sum across all your processes under the engine's connection limit for the database's specification. + +If you instead run many short-lived instances (serverless functions, per-request containers) that each open their own pool, route them through the Appwrite [connection pooler](/docs/products/databases/postgresql/connection-pooling) on port `6432` to fan many clients onto a small backend pool. The pooler defaults to **transaction mode**, which doesn't hold a backend connection across statements, so server-side prepared statements aren't available: + +Disable Npgsql's automatic prepared statements with `Max Auto Prepare=0` when connecting to the transaction-mode pooler: + +```text +Host=...;Port=6432;Database=<database>;Username=admin;Password=<password>;SSL Mode=Require;Max Auto Prepare=0 +``` + +If your application relies on prepared statements, advisory locks, or `LISTEN`/`NOTIFY`, switch the pooler to **session mode** instead, see the [pooler](/docs/products/databases/postgresql/connection-pooling#modes) page for the trade-offs. + +# Use a branch for previews and CI {% #branches %} + +Database [branches](/docs/products/databases/postgresql/branches) are instant, isolated copies of a database with their own hostname and credentials. They're ideal for running migrations against throwaway data in a pull-request preview or an integration-test job: + +1. Create a branch from the API and read its connection details. +2. Build the connection string for the branch host and pass it as the `Default` connection string. +3. Run `dotnet ef database update` and your test suite against the branch. +4. Delete the branch when the job finishes. + +Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations run against representative data without touching production. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/postgresql/connections" title="Connect" %} +Retrieve credentials, rotate the password, and create scoped connection users. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/connection-pooling" title="Connection pooler" %} +Pool modes, ports, and read/write splitting for serverless workloads. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/branches" title="Branches" %} +Ephemeral database copies for preview environments and CI. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/network-security" title="Network" %} +TLS modes, certificate verification, mTLS, and IP allowlists. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/postgresql/integrations/fastapi/+page.markdoc b/src/routes/docs/products/databases/postgresql/integrations/fastapi/+page.markdoc new file mode 100644 index 00000000000..78c582017e5 --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/integrations/fastapi/+page.markdoc @@ -0,0 +1,189 @@ +--- +layout: article +title: FastAPI +description: Use FastAPI and SQLAlchemy 2.x with an Appwrite native PostgreSQL database. Configure the async asyncpg engine, pass TLS through connect_args, inject a session per request, and run Alembic migrations against the direct database port. +--- + +An Appwrite native PostgreSQL database is a standard PostgreSQL engine, so [FastAPI](https://fastapi.tiangolo.com/) with [SQLAlchemy](https://docs.sqlalchemy.org/) and an async driver works against it with no Appwrite-specific configuration. You point `create_async_engine` at the connection string from the [connections](/docs/products/databases/postgresql/connections) page and use the SQLAlchemy ORM, the FastAPI dependency system, and Alembic exactly as you would against any self-hosted PostgreSQL server. + +{% info title="Before you start" %} +You'll need a native PostgreSQL database in a `ready` state and its credentials. See [native PostgreSQL databases](/docs/products/databases/postgresql) to create one and [connections](/docs/products/databases/postgresql/connections) to retrieve the connection string. The primary user is `admin`, and the database name is generated for each database. +{% /info %} + +# Install dependencies {% #install %} + +```bash +pip install "fastapi[standard]" "sqlalchemy[asyncio]" asyncpg alembic +``` + +# Set the connection string {% #connection-string %} + +Copy the connection string from the Console **Credentials** dialog, or fetch it with the [API](/docs/products/databases/postgresql/connections#credentials). Put it in your environment, never commit it. SQLAlchemy's asyncpg dialect uses the `postgresql+asyncpg://` scheme, so swap the leading `postgresql://` for it: + +```env +DATABASE_URL="postgresql+asyncpg://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>" +``` + +The string Appwrite returns ends with `?sslmode=require`. Drop that query parameter for asyncpg, asyncpg does not read `sslmode` from the URL. TLS is configured through `connect_args` instead, shown below. + +This guide uses the [asyncpg](https://magicstack.github.io/asyncpg/current/) driver; the same patterns apply to the async `postgresql+psycopg` dialect by changing the URL scheme. + +# Create the async engine {% #engine %} + +The edge proxy terminates TLS for every native PostgreSQL database, so encryption is mandatory. asyncpg's `ssl` argument accepts the libpq-style strings (`require`, `verify-ca`, `verify-full`), `True`, or an `ssl.SSLContext`. Pass it through `connect_args`: + +```python +from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine + +engine = create_async_engine( + settings.database_url, + connect_args={"ssl": "require"}, + pool_size=10, + max_overflow=5, + pool_pre_ping=True, +) + +SessionLocal = async_sessionmaker(engine, expire_on_commit=False) +``` + +`ssl="require"` encrypts the connection without validating the certificate chain. For full verification, pass `ssl="verify-full"` instead; the server certificate is signed by a well-known public CA, so validation succeeds against the system trust store without a custom CA bundle, see the [Network](/docs/products/databases/postgresql/network-security) page. + +# Define a model {% #model %} + +```python +from datetime import datetime + +from sqlalchemy import func +from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column + +class Base(DeclarativeBase): + pass + +class User(Base): + __tablename__ = "users" + + id: Mapped[int] = mapped_column(primary_key=True) + email: Mapped[str] = mapped_column(unique=True) + created_at: Mapped[datetime] = mapped_column(server_default=func.now()) +``` + +# Inject a session per request {% #session %} + +FastAPI's dependency system gives each request its own `AsyncSession` and closes it when the request finishes. Define a dependency that yields a session, then annotate path operations with it: + +```python +from typing import Annotated + +from fastapi import Depends, FastAPI +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +app = FastAPI() + +async def get_session(): + async with SessionLocal() as session: + yield session + +SessionDep = Annotated[AsyncSession, Depends(get_session)] + +@app.post("/users") +async def create_user(email: str, session: SessionDep): + user = User(email=email) + session.add(user) + await session.commit() + await session.refresh(user) + return user + +@app.get("/users") +async def list_users(session: SessionDep): + result = await session.scalars(select(User).order_by(User.created_at.desc())) + return result.all() +``` + +Create the engine once at module scope and reuse it for the whole process, the pool lives inside it. Don't open a new engine per request. + +# Run migrations {% #migrate %} + +Generate Alembic's async scaffold, which opens the connection asynchronously and hands a sync connection to the migration context: + +```bash +alembic init -t async migrations +``` + +Point `target_metadata` at `Base.metadata` in `migrations/env.py`, then autogenerate and apply: + +```bash +alembic revision --autogenerate -m "init" +alembic upgrade head +``` + +Run migrations against the **direct** engine port (`5432`), not the pooler. DDL needs a real session-level connection, and Alembic's async template already uses a `NullPool`, so a fresh connection is opened and closed per run. The primary `admin` user owns the default database and can run schema changes. Narrower [database roles](/docs/products/databases/postgresql/connections#roles) should only receive the privileges your application needs. + +# Pool sizing {% #sizing %} + +A long-running `uvicorn` server holds a SQLAlchemy pool for its lifetime, so connect to the **direct** engine port (`5432`) with a sized pool. Keep `pool_size` × the number of server processes under the connection limit of your [specification](/docs/products/databases/postgresql#specifications), and let `max_overflow` absorb short bursts: + +```python +engine = create_async_engine( + settings.database_url, + connect_args={"ssl": "require"}, + pool_size=10, + max_overflow=5, +) +``` + +When the same app runs on a serverless platform (for example an Appwrite [function](/docs/products/functions)) where each invocation is a fresh instance, that fans out into far more backend connections than the engine allows. Route runtime traffic through the [connection pooler](/docs/products/databases/postgresql/connection-pooling) on port `6432` instead, and size the pool small per instance: + +```env +DATABASE_URL="postgresql+asyncpg://admin:<password>@db-<hash>.<region>.appwrite.center:6432/<database>" +``` + +# Disable prepared statements on the transaction pooler {% #prepared-statements %} + +The pooler defaults to **transaction mode**, which does not keep a backend connection across statements. asyncpg relies on server-side prepared statements, which transaction mode cannot support, so turn the caches off. This needs **two** settings: asyncpg's own `statement_cache_size` in `connect_args` and SQLAlchemy's dialect-level `prepared_statement_cache_size` in the connection URL: + +```env +DATABASE_URL="postgresql+asyncpg://admin:<password>@db-<hash>.<region>.appwrite.center:6432/<database>?prepared_statement_cache_size=0" +``` + +```python +from sqlalchemy import NullPool + +engine = create_async_engine( + settings.database_url, + connect_args={"ssl": "require", "statement_cache_size": 0}, + poolclass=NullPool, +) +``` + +`statement_cache_size=0` disables asyncpg's prepared statement cache, and `prepared_statement_cache_size=0` disables the dialect's own per-connection statement cache. Use `NullPool` so SQLAlchemy doesn't keep its own pool on top of the pooler's. + +If your application relies on prepared statements, advisory locks, `LISTEN`/`NOTIFY`, or temporary tables, switch the pooler to **session mode** instead and keep statement caching on, see the [pooler](/docs/products/databases/postgresql/connection-pooling#modes) page for the trade-offs. + +# Use a branch for previews and CI {% #branches %} + +[Branches](/docs/products/databases/postgresql/branches) are instant, isolated copies of a database with their own hostname and connection string. They're ideal for running migrations against throwaway data in a pull-request preview or an integration-test job: + +1. Create a branch from the API and read its `connectionString`. +2. Rewrite the scheme to `postgresql+asyncpg://` and export it as `DATABASE_URL`. +3. Run `alembic upgrade head` against the branch's direct port, then your test suite. +4. Delete the branch when the job finishes. + +Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations run against realistic data without touching production. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/postgresql/connections" title="Connections" %} +Retrieve credentials, rotate the password, and create scoped database roles. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/connection-pooling" title="Connection pooler" %} +Pool modes, ports, and read/write splitting for serverless workloads. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/branches" title="Branches" %} +Ephemeral database copies for preview environments and CI. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/network-security" title="Network" %} +TLS modes, certificate verification, mTLS, and IP allowlists. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/postgresql/integrations/gorm/+page.markdoc b/src/routes/docs/products/databases/postgresql/integrations/gorm/+page.markdoc new file mode 100644 index 00000000000..c2c31d100a3 --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/integrations/gorm/+page.markdoc @@ -0,0 +1,168 @@ +--- +layout: article +title: GORM +description: Use GORM with an Appwrite native PostgreSQL database in Go. Build the DSN, open a connection, size the database/sql pool against the direct connection, and run migrations with AutoMigrate or golang-migrate. +--- + +A native PostgreSQL database is a standard PostgreSQL engine, so [GORM](https://gorm.io/) talks to it with no Appwrite-specific configuration. You build a connection string from the credentials in the Console, hand it to the GORM PostgreSQL driver, and use models, `AutoMigrate`, and the query API exactly as you would against any self-hosted PostgreSQL server. + +{% info title="Before you start" %} +You'll need a native PostgreSQL database in a `ready` state and its credentials. See [PostgreSQL databases](/docs/products/databases/postgresql) to create one. To retrieve the hostname, password, database name, and connection string, open the database in the Console and click **Credentials**. The **Credentials** page includes **Details**, **DSN**, **.env**, **Prisma**, **Drizzle**, and **psql** tabs. The primary user is `admin`, and Appwrite generates the database name for each database. +{% /info %} + +# Build the DSN {% #dsn %} + +The edge proxy terminates TLS for every native PostgreSQL database, so no certificate file is needed. Keep the password out of source and read the connection details from the environment: + +```env +DB_HOST="db-<hash>.<region>.appwrite.center" +DB_NAME="<database>" +DB_PASSWORD="<password>" +``` + +GORM's PostgreSQL driver accepts the pgx key/value DSN. Set `sslmode=require`: + +```go +dsn := fmt.Sprintf( + "host=%s user=admin password=%s dbname=%s port=5432 sslmode=require", + os.Getenv("DB_HOST"), os.Getenv("DB_PASSWORD"), os.Getenv("DB_NAME"), +) +``` + +The URL form from the **DSN** tab works too, and the driver parses either format: `postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require`. For full certificate verification, add `sslmode=verify-full`, no CA file needed: Go's drivers validate against the system certificate pool, and the proxy's certificate is signed by a public CA. + +For mTLS, see the [Network](/docs/products/databases/postgresql/network-security) page. + +# Open a connection {% #open %} + +Pass the DSN to the driver's `Open` function and call `gorm.Open`: + +```go +package main + +import ( + "fmt" + "os" + + "gorm.io/driver/postgres" + "gorm.io/gorm" +) + +func main() { + dsn := fmt.Sprintf( + "host=%s user=admin password=%s dbname=%s port=5432 sslmode=require", + os.Getenv("DB_HOST"), os.Getenv("DB_PASSWORD"), os.Getenv("DB_NAME"), + ) + + db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) + if err != nil { + panic(err) + } + + _ = db +} +``` + +Connect to the **direct** engine port (`5432`) for a long-running server, see [pool sizing](#pool) below. If you route through the [connection pooler](/docs/products/databases/postgresql/connection-pooling) in its default transaction mode, use `postgres.New` with `PreferSimpleProtocol: true` so the driver stops issuing implicit prepared statements, which transaction mode cannot keep across statements: + +```go +db, err := gorm.Open(postgres.New(postgres.Config{ + DSN: dsn, // pooler host, port 6432 + PreferSimpleProtocol: true, +}), &gorm.Config{}) +``` + +# Define a model and migrate {% #model %} + +Declare your models as Go structs and let GORM create the tables with `AutoMigrate`: + +```go +type User struct { + ID uint `gorm:"primaryKey"` + Email string `gorm:"uniqueIndex"` + CreatedAt time.Time +} + +if err := db.AutoMigrate(&User{}); err != nil { + panic(err) +} +``` + +`AutoMigrate` creates the table if it's missing and adds any missing columns and indexes. It does not drop columns or change existing column types, so it's convenient in development but not a substitute for versioned migrations in production. Either way, run schema changes against the **direct** engine port: DDL needs a real session connection, and the primary `admin` user owns the generated database. Scoped connection users (`readonly` / `readwrite`) intentionally cannot run DDL. + +For versioned migrations, [golang-migrate](https://github.com/golang-migrate/migrate) runs ordered up/down files. Point it at the direct engine port: + +```bash +migrate -path ./migrations \ + -database "postgresql://admin:$DB_PASSWORD@$DB_HOST:5432/$DB_NAME?sslmode=require" \ + up +``` + +# Size the connection pool {% #pool %} + +GORM manages a `database/sql` pool under the hood. A long-running Go server holds that pool for its whole lifetime, so connect to the **direct** engine port and cap the pool yourself against the engine's connection limit. Reach the underlying `*sql.DB` with `db.DB()`: + +```go +sqlDB, err := db.DB() +if err != nil { + panic(err) +} + +sqlDB.SetMaxOpenConns(25) +sqlDB.SetMaxIdleConns(25) +sqlDB.SetConnMaxLifetime(time.Hour) +``` + +Keep the sum of `SetMaxOpenConns` across every instance below the specification's `maxConnections`. If you run many instances or a serverless/edge runtime that opens a fresh pool per invocation, route through the [connection pooler](/docs/products/databases/postgresql/connection-pooling) and keep each instance's pool small. The pooler multiplexes them onto a handful of backend connections. + +# Use sqlx instead {% #sqlx %} + +If you prefer raw SQL with light struct scanning, [sqlx](https://github.com/jmoiron/sqlx) wraps `database/sql` and uses the same DSN. + +Register the `pgx` stdlib driver (or `lib/pq`) and connect: + +```go +import ( + _ "github.com/jackc/pgx/v5/stdlib" + "github.com/jmoiron/sqlx" +) + +db, err := sqlx.Connect("pgx", + fmt.Sprintf("host=%s user=admin password=%s dbname=%s port=5432 sslmode=require", + os.Getenv("DB_HOST"), os.Getenv("DB_PASSWORD"), os.Getenv("DB_NAME"))) +if err != nil { + panic(err) +} + +db.SetMaxOpenConns(25) +``` + +The same pooler caveat applies: in transaction mode, disable implicit prepared statements (the pgx stdlib driver exposes a simple-protocol option, and `lib/pq` can be told to skip prepares), or use [session mode](/docs/products/databases/postgresql/connection-pooling#modes). `sslmode=require` needs no CA file, and neither does `verify-full`: Go's drivers fall back to the system certificate pool when `sslrootcert` is unset, and the proxy's certificate is signed by a public CA that pool already trusts. Set `sslrootcert` only when the container image ships without CA certificates. + +# Use a branch for previews and CI {% #branches %} + +[Branches](/docs/products/databases/postgresql/branches) are instant, isolated copies of a database with their own hostname and connection string, ideal for running migrations against throwaway data in a pull-request preview or an integration-test job: + +1. Create a branch from the API and read its `connectionString`. +2. Export the host and password into the environment your tests read. +3. Run `migrate ... up` (or `AutoMigrate`) and your test suite against the branch's direct port. +4. Delete the branch when the job finishes. + +Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations run against realistic data without touching production. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/postgresql/connections" title="Connect" %} +Retrieve credentials, rotate the password, and create scoped connection users. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/connection-pooling" title="Connection pooler" %} +Pool modes, ports, and read/write splitting for serverless workloads. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/branches" title="Branches" %} +Ephemeral database copies for preview environments and CI. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/network-security" title="Network" %} +TLS modes, certificate verification, mTLS, and IP allowlists. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/postgresql/integrations/grafana/+page.markdoc b/src/routes/docs/products/databases/postgresql/integrations/grafana/+page.markdoc new file mode 100644 index 00000000000..b243e714a9d --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/integrations/grafana/+page.markdoc @@ -0,0 +1,105 @@ +--- +layout: article +title: Grafana +description: Connect Grafana to an Appwrite native PostgreSQL database as a data source and build dashboards. Create a read-only reporting role, configure the PostgreSQL data source with TLS, and provision it from YAML. +--- + +An Appwrite [native PostgreSQL database](/docs/products/databases/postgresql) exposes a standard managed PostgreSQL 18 or 17 engine, so [Grafana](https://grafana.com/) connects to it through the built-in **PostgreSQL data source** with no Appwrite-specific configuration. Point the data source at your database hostname, authenticate with a read-only reporting role, and query your tables to build dashboards and alerts. + +{% info title="Before you start" %} +You'll need a native PostgreSQL database in a `ready` state and its credentials. In the Console, open the database and click **Credentials**. Use the **Details** tab for individual values, or copy a ready-made string from the **DSN**, **.env**, **Prisma**, **Drizzle**, or **psql** tab. You can also call `postgresql.get()` from the Appwrite API to read `hostname`, `connectionUser`, `connectionPassword`, and `connectionString`. The primary user is `admin`, and Appwrite generates the database name for each database. +{% /info %} + +# Create a reporting role {% #reporting-role %} + +Dashboards should not connect as the primary `admin` user, which owns the database and can run schema changes. Create a PostgreSQL role for Grafana from the **Roles** tab of your database in the Console, then grant it the `SELECT` privileges your dashboards need from the SQL editor or `psql`. + +Use that role's name and password in the Grafana data source below. For more about retrieving credentials, rotating the primary password, and managing database roles, see [Connections](/docs/products/databases/postgresql/connections). + +# Choose a connection target {% #target %} + +Grafana holds its data source connections open for the lifetime of the process. Long-lived connections should use either the direct PostgreSQL port `5432` or a [connection pooler](/docs/products/databases/postgresql/connection-pooling) running in **session mode**. + +Do not point Grafana at the **transaction-mode** pooler. Transaction mode hands a backend connection back to the pool after every statement, which breaks the session assumptions Grafana relies on for connection reuse and prepared statements. For a typical dashboard workload the direct PostgreSQL port is the simplest choice. See the [pooler modes](/docs/products/databases/postgresql/connection-pooling#modes) page for the trade-offs. + +# Add the PostgreSQL data source {% #data-source %} + +In Grafana, open **Connections** > **Data sources** > **Add data source** and select **PostgreSQL**. Fill in the connection details using the values from your native PostgreSQL database: + +| Field | Value | +| --- | --- | +| **Host URL** | `db-<hash>.<region>.appwrite.center:5432` | +| **Database name** | `<database>` | +| **Username** | `grafana_ro` | +| **Password** | the password for the reporting role | +| **TLS/SSL Mode** | `require` | +| **Version** | PostgreSQL 10+; Grafana auto-detects the server version on save when it can connect | + +Regions are `fra`, `nyc`, `sfo`, `sgp`, `syd`, and `tor`. The edge proxy terminates TLS for every native PostgreSQL database, so **TLS/SSL Mode** `require` works with no certificate upload. + +If you use Grafana Cloud and restrict database access with an IP allowlist, add the Grafana Cloud outbound IP ranges for your stack to the database allowlist. Grafana Cloud can reach the public Appwrite database hostname directly. Private connectivity features are only needed when the database is on a private network. + +Under **Connection limits**, keep the connection counts modest so Grafana doesn't exhaust the engine's connection budget. **Max open** caps total connections from this Grafana instance, **Max idle** caps pooled idle connections, and **Max lifetime** recycles connections after the given number of seconds. Select **Save & test** to verify connectivity. + +# Provision from YAML {% #provisioning %} + +Instead of configuring the data source by hand, you can [provision](https://grafana.com/docs/grafana/latest/administration/provisioning/) it declaratively. Drop a file into Grafana's `provisioning/datasources/` directory and read the password from an environment variable so it never lands in source control: + +```yaml +apiVersion: 1 + +datasources: + - name: Appwrite native PostgreSQL + type: postgres + url: db-<hash>.<region>.appwrite.center:5432 + user: grafana_ro + jsonData: + database: <database> + sslmode: require + postgresVersion: 1500 + maxOpenConns: 5 + maxIdleConns: 2 + maxIdleConnsAuto: true + connMaxLifetime: 14400 + secureJsonData: + password: $GRAFANA_DB_PASSWORD + editable: false +``` + +`postgresVersion` takes Grafana's encoded version values (`1500` for PostgreSQL 15, currently the highest option), so pick the highest value available for a native PostgreSQL database. The `database` key lives under `jsonData` in current Grafana releases, and Grafana expands `$GRAFANA_DB_PASSWORD` from the process environment when it loads the provisioning file. See the [PostgreSQL data source](https://grafana.com/docs/grafana/latest/datasources/postgres/configure/) docs for every available field. + +# Build a panel {% #panel %} + +With the data source connected, create a dashboard and add a panel backed by it. Switch the query editor to code mode and write a read-only query against your tables. For example, to plot daily sign-ups from a `users` table over time: + +```text +SELECT + date_trunc('day', created_at) AS time, + count(*) AS signups +FROM users +GROUP BY 1 +ORDER BY 1 +``` + +Grafana maps the `time` column to the panel's time axis and `signups` to the value. Because the data source authenticates as a read-only reporting role, any query that attempts to write fails at the database, which keeps a misconfigured panel from mutating production data. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/postgresql" title="PostgreSQL databases" %} +Overview of native PostgreSQL database engines, versions, and regions. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/connections" title="Connect" %} +Retrieve credentials, rotate the password, and manage database roles. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/connection-pooling" title="Connection pooler" %} +Pool modes and ports. Use the direct PostgreSQL port or session mode for Grafana. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/network-security" title="Network" %} +TLS modes, certificate verification, and IP allowlists. +{% /cards_item %} +{% /cards %} + +{% arrow_link href="/docs/products/databases/postgresql/connections" %} +Connect to a native PostgreSQL database +{% /arrow_link %} diff --git a/src/routes/docs/products/databases/postgresql/integrations/laravel/+page.markdoc b/src/routes/docs/products/databases/postgresql/integrations/laravel/+page.markdoc new file mode 100644 index 00000000000..7d3075b20a7 --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/integrations/laravel/+page.markdoc @@ -0,0 +1,204 @@ +--- +layout: article +title: Laravel +description: Use Laravel and Eloquent with an Appwrite native PostgreSQL database. Configure the connection, run migrations against the direct port, and pool serverless traffic through the connection pooler. +--- + +A native PostgreSQL database is a standard PostgreSQL engine, so [Laravel](https://laravel.com/docs) works against it with no Appwrite-specific configuration. Point the `pgsql` connection in `config/database.php` at the credentials from the [Connections](/docs/products/databases/postgresql/connections) page, then use Eloquent, the query builder, migrations, and queues as you would against any PostgreSQL server. + +{% info title="Before you start" %} +You'll need a native PostgreSQL database in a `ready` state and its credentials. See [PostgreSQL databases](/docs/products/databases/postgresql) to create one. To retrieve credentials, open the database in the Console, click **Credentials**, and use the **Details**, **DSN**, **.env**, **Prisma**, **Drizzle**, or **psql** tab. The primary username is `admin`, and the database name is generated per database. +{% /info %} + +# Configure the connection {% #connection %} + +Laravel reads database credentials from `.env`. Copy the values from the Console credentials dialog, or fetch them with [`postgresql.get()`](/docs/products/databases/postgresql/connections#credentials), and set the matching connection. Never commit `.env`: + +```env +DB_CONNECTION=pgsql +DB_HOST=db-<hash>.<region>.appwrite.center +DB_PORT=5432 +DB_DATABASE=<database> +DB_USERNAME=admin +DB_PASSWORD=<password> +DB_SSLMODE=require +``` + +The scaffolded `config/database.php` wires these variables into the `pgsql` connection, including SSL: + +```php +'pgsql' => [ + 'driver' => 'pgsql', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '5432'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => env('DB_CHARSET', 'utf8'), + 'prefix' => '', + 'prefix_indexes' => true, + 'search_path' => 'public', + 'sslmode' => env('DB_SSLMODE', 'prefer'), +], +``` + +`sslmode` is a top-level key on the PostgreSQL connection. Setting `DB_SSLMODE=require` matches the `sslmode=require` that Appwrite uses. Appwrite Cloud terminates TLS for every native PostgreSQL database, so certificate files are not needed for `require`. For full certificate verification, set `DB_SSLMODE=verify-full` and point `sslrootcert` at a trusted root store, `system` on libpq 16+, or your OS bundle such as `/etc/ssl/certs/ca-certificates.crt`. The proxy certificate is signed by a public CA, so there is no Appwrite-specific CA to download: + +```php +'sslmode' => env('DB_SSLMODE', 'prefer'), +'sslrootcert' => env('DB_SSLROOTCERT'), +``` + +# Run migrations {% #migrate %} + +Define your schema with a migration: + +```php +Schema::create('posts', function (Blueprint $table) { + $table->id(); + $table->string('title'); + $table->text('body'); + $table->timestamps(); +}); +``` + +Apply migrations from your machine or a deploy step: + +```bash +php artisan migrate + +# non-interactive, for CI and production deploys +php artisan migrate --force +``` + +Run `migrate` against the **direct** PostgreSQL port (`5432`), not the pooler. Migrations issue DDL that needs a session-level connection, and the transaction-mode pooler can't keep state across statements. The primary `admin` user owns the default database and can run schema changes. Narrower [connection users](/docs/products/databases/postgresql/connections#roles), such as read-only reporting roles, should not run DDL. + +# Query with Eloquent {% #query %} + +Once the schema is migrated, use Eloquent models and the query builder as usual: + +```php +namespace App\Models; + +use Illuminate\Database\Eloquent\Model; + +class Post extends Model +{ + protected $fillable = [ + 'title', + 'body', + ]; +} +``` + +```php +use App\Models\Post; + +$post = Post::create([ + 'title' => 'Hello from a native PostgreSQL database', + 'body' => 'Stored in a native PostgreSQL database.', +]); + +$recent = Post::query() + ->orderByDesc('created_at') + ->limit(10) + ->get(); +``` + +Nothing about the native PostgreSQL database changes how Eloquent, relationships, transactions, or the query builder behave. It is a standard PostgreSQL server behind a TLS connection. + +# Pool connections from serverless {% #pooling %} + +The right port depends on how your app runs. + +A **long-running** PHP process, traditional PHP-FPM with persistent connections, [Laravel Octane](https://laravel.com/docs/octane), or a queue worker, holds its own backend connection for its lifetime. Point these at the **direct** PostgreSQL port (`5432`), or at the [connection pooler](/docs/products/databases/postgresql/connection-pooling) in **session mode**. Don't put a long-lived process behind the transaction-mode pooler. + +A **serverless** or per-request deployment (Vercel, AWS Lambda, Cloud Run) opens a fresh connection on every invocation and fans out into far more backend connections than the engine allows. Route that traffic through the pooler's **transaction-mode** port (`6432`) on the same hostname, and give Laravel direct connection details for migrations and other schema operations: + +```env +# Runtime: pooled, transaction mode +DB_HOST=db-<hash>.<region>.appwrite.center +DB_PORT=6432 +DB_DATABASE=<database> +DB_USERNAME=admin +DB_PASSWORD=<password> +DB_POOLED=true + +# Migrations and schema operations: direct PostgreSQL port +DB_DIRECT_HOST=db-<hash>.<region>.appwrite.center +DB_DIRECT_PORT=5432 +DB_DIRECT_USERNAME=admin +DB_DIRECT_PASSWORD=<password> +DB_DIRECT_SSLMODE=require +``` + +Extend the `pgsql` connection in `config/database.php` with Laravel's pooled connection keys: + +```php +'pgsql' => [ + 'driver' => 'pgsql', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '5432'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => env('DB_CHARSET', 'utf8'), + 'prefix' => '', + 'prefix_indexes' => true, + 'search_path' => 'public', + 'sslmode' => env('DB_SSLMODE', 'prefer'), + 'pooled' => env('DB_POOLED', false), + 'direct' => array_filter([ + 'host' => env('DB_DIRECT_HOST'), + 'port' => env('DB_DIRECT_PORT'), + 'username' => env('DB_DIRECT_USERNAME'), + 'password' => env('DB_DIRECT_PASSWORD'), + 'sslmode' => env('DB_DIRECT_SSLMODE'), + ]), +], +``` + +Laravel uses the direct connection for migrations, schema dumps, restores, and database inspection commands when pooled mode is enabled. You can also call `DB::connection('pgsql::direct')` for schema operations that need the direct port. The transaction-mode pooler does not keep a backend connection across statements, so server-side prepared statements, advisory locks, `LISTEN`/`NOTIFY`, and `SET LOCAL` are unavailable. If your app relies on those, use **session mode**. See the [pooler](/docs/products/databases/postgresql/connection-pooling#modes) page for the trade-offs. + +# Queues and Horizon {% #queues %} + +A queue worker is a long-running process. `php artisan queue:work` boots once and processes jobs for its whole lifetime, holding a persistent database connection the entire time. The same applies to every worker that [Laravel Horizon](https://laravel.com/docs/horizon) supervises. Treat workers like any other long-lived process: + +- Connect them to the **direct** PostgreSQL port (`5432`) or the **session-mode** pooler, never the transaction-mode pooler. +- Restart workers periodically with `--max-time` or `--max-jobs` so a fresh process reclaims memory and reopens its connection. Supervisor or Horizon restarts them automatically. + +```bash +php artisan queue:work --max-time=3600 --max-jobs=500 +``` + +Each worker counts as one backend connection, so size your worker pool (and Horizon's `maxProcesses`) against the connection budget of your [specification](/docs/products/databases/postgresql). Horizon itself requires Redis for the queue backend; only your application's data connection touches the native PostgreSQL database. + +# Use a branch for previews and CI {% #branches %} + +PostgreSQL [branches](/docs/products/databases/postgresql/branches) are instant, isolated copies of a database with their own hostname. Create them from the API for running migrations against throwaway data in a pull-request preview or an integration-test job: + +1. Create a branch from the API and read its connection details. +2. Export them as the `DB_*` variables for the job. +3. Run `php artisan migrate --force` and your test suite against the branch. +4. Delete the branch when the job finishes. + +Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations and tests run against realistic data without touching production. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/postgresql/connections" title="Connect" %} +Retrieve credentials, rotate the password, and create scoped connection users. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/connection-pooling" title="Connection pooler" %} +Pool modes, ports, and read/write splitting for serverless workloads. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/branches" title="Branches" %} +Ephemeral database copies for preview environments and CI. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/network-security" title="Network" %} +TLS modes, certificate verification, mTLS, and IP allowlists. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/postgresql/integrations/metabase/+page.markdoc b/src/routes/docs/products/databases/postgresql/integrations/metabase/+page.markdoc new file mode 100644 index 00000000000..6a85f4d58eb --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/integrations/metabase/+page.markdoc @@ -0,0 +1,80 @@ +--- +layout: article +title: Metabase +description: Connect Metabase to an Appwrite native PostgreSQL database for analytics. Use a read-only PostgreSQL role, configure SSL, and build dashboards on a session-safe connection. +--- + +Appwrite's native PostgreSQL database is a standard PostgreSQL engine, so [Metabase](https://www.metabase.com/) can connect to it without an Appwrite-specific adapter. Add the database in Metabase, point it at the host from the [Connections](/docs/products/databases/postgresql/connections) page, and Metabase will sync the schema so your team can build questions and dashboards. + +{% info title="Before you start" %} +You'll need a native PostgreSQL database in a `ready` state and its credentials. In the Console, open the database and click **Credentials**. Copy the individual values from the **Details** tab, or use the **DSN**, **.env**, or **psql** tab. You can also fetch the same values with [`postgresql.get()`](/docs/products/databases/postgresql/connections#credentials). The primary user is `admin`, and the database name is generated per database. +{% /info %} + +# Use a read-only PostgreSQL role {% #read-only-role %} + +Dashboards should not connect as the primary `admin` user. That role owns the database and can run schema changes. For analytics, create a read-only PostgreSQL role for Metabase from the database **Roles** tab in the Console, then grant it access only to the schemas and tables Metabase should query. + +At minimum, a reporting role needs permission to connect to the database, usage on the schemas you want to expose, and `SELECT` on the tables and views in those schemas. Use the [SQL editor](/docs/products/databases/postgresql/quick-start#first-queries), `psql`, or your migration workflow to apply the grants after you create the role. See [Database roles](/docs/products/databases/postgresql/connections#roles) for how roles work with native PostgreSQL databases. + +If you plan to use Metabase features that write data, such as editable table data, use a separate connection with the specific write privileges that feature needs. Keep the analytics connection read-only. + +# Add the database in Metabase {% #add-database %} + +In Metabase, click the grid icon, then open **Admin > Databases > Add a database**. Choose **PostgreSQL** as the database type and fill in the connection form with the values from the [Connections](/docs/products/databases/postgresql/connections) page. + +| Field | Value | +|-------|-------| +| Host | `db-<hash>.<region>.appwrite.center` | +| Port | `5432` | +| Database name | `<database>` | +| Username | your read-only PostgreSQL role | +| Password | the role password | + +Turn on **Use a secure connection (SSL)** and set **SSL Mode** to `require`. Appwrite Cloud requires TLS for native PostgreSQL connections. If you choose `verify-ca` or `verify-full`, Metabase also requires a root certificate in PEM format. See [Network security](/docs/products/databases/postgresql/network-security) for Appwrite TLS and network controls. + +{% info title="Use a session-safe connection" %} +Metabase keeps database sessions open and can use session-level PostgreSQL behavior while exploring data. Connect it to the direct PostgreSQL port (`5432`) or to the [connection pooler](/docs/products/databases/postgresql/connection-pooling) in `session` mode. Transaction-mode pooling can break session-level features because a client session is not bound to one backend connection. +{% /info %} + +Click **Save changes**. Metabase verifies the connection and starts its first schema sync. + +# Build a question or dashboard {% #build %} + +Once the first sync finishes, your tables appear in the data picker. To build your first chart: + +1. Click **+ New > Question** and pick your Appwrite PostgreSQL database as the data source. +2. Choose a table, add a summary such as count, sum, or average, and group by a column such as a timestamp. +3. Switch the visualization to a line, bar, or table view, then save the question. +4. Add saved questions to a dashboard and use filters to slice data across cards. + +Because the analytics connection is read-only, native SQL questions can inspect data without changing it. Queries that attempt writes will fail unless you intentionally connect Metabase with a role that has write privileges. + +# How Metabase syncs your schema {% #sync %} + +After you connect, Metabase scans the database to discover tables, columns, constraints, and field metadata, then keeps that metadata current on a schedule: + +- A lightweight schema sync runs hourly by default. +- A more intensive field-value scan runs daily by default to populate filter dropdowns. + +New tables and columns appear after the next sync. To pull them in immediately, open **Admin > Databases > your database** and click **Sync database schema**. You can also restrict which schemas Metabase tracks and adjust the sync and scan cadence from the database settings. Refer to the [Metabase documentation](https://www.metabase.com/docs/latest/databases/sync-scan) for the full set of sync and scan options. + +# Use a branch for testing {% #branches %} + +[Branches](/docs/products/databases/postgresql/branches) are isolated copies of a native PostgreSQL database with their own host and credentials. Point a second Metabase database connection at a branch when you want to validate a dashboard against a snapshot of production data without querying the live database. Branches are managed through the API, and you can delete the branch when testing is complete. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/postgresql" title="PostgreSQL" %} +Provision and manage a native PostgreSQL database for your project. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/connections" title="Connections" %} +Retrieve credentials, rotate the primary password, and manage PostgreSQL roles. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/connection-pooling" title="Connection pooling" %} +Pool modes and ports, including why BI tools need session or direct connections. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/network-security" title="Network security" %} +TLS, certificate verification, IP allowlists, and idle connection timeouts. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/postgresql/integrations/nextjs/+page.markdoc b/src/routes/docs/products/databases/postgresql/integrations/nextjs/+page.markdoc new file mode 100644 index 00000000000..ede902ded10 --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/integrations/nextjs/+page.markdoc @@ -0,0 +1,150 @@ +--- +layout: article +title: Next.js +description: Connect a Next.js App Router application to an Appwrite native PostgreSQL database from Route Handlers, Server Actions, and Server Components, pool from serverless, and fall back to the SQL API on the Edge runtime. +--- + +An Appwrite native PostgreSQL database works with standard PostgreSQL drivers and ORMs, so a [Next.js](https://nextjs.org/) App Router application can query it from server-side code. Point your driver at the connection string from the [Connections](/docs/products/databases/postgresql/connections) page and keep all database access on the server. + +{% info title="Before you start" %} +You'll need a native PostgreSQL database in a `ready` state and its credentials. See [PostgreSQL](/docs/products/databases/postgresql) to create one and [Connections](/docs/products/databases/postgresql/connections) to retrieve the connection string. The primary user is `admin`, and Appwrite generates the database name for each database. +{% /info %} + +# Where to connect {% #where %} + +In the App Router, every server-side execution context runs on the **Node.js runtime by default**, and the Node.js runtime can open raw TCP sockets. That means you can use a standard database driver from any of these: + +- **Route Handlers** (`app/api/.../route.ts`) for public endpoints, webhooks, and REST-style APIs. +- **Server Actions** (`'use server'` functions) for form submissions and app-internal mutations. +- **Server Components** (`async` components) for read queries that render straight into the page. + +Never import a database driver into a Client Component (`'use client'`) or ship the connection string to the browser. Keep all database access on the server. + +The one exception is the **Edge runtime** (`export const runtime = 'edge'`), which runs on a constrained environment that **cannot open TCP database sockets**. If a route opts into Edge, use the [SQL API](/docs/products/databases/postgresql/quick-start#first-queries) over HTTPS instead, see [the Edge runtime section](#edge) below. + +# Environment variables {% #env %} + +Put the connection string in your environment and never commit it. For local development, use `.env.local` (Next.js loads it automatically and it's git-ignored by default): + +```env +DATABASE_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:6432/<database>?sslmode=require" +DIRECT_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require" +``` + +`DATABASE_URL` points at the [connection pooler](/docs/products/databases/postgresql/connection-pooling) port (`6432`) for runtime traffic, and `DIRECT_URL` points at the PostgreSQL engine port (`5432`) for migrations. The next section explains why. The `sslmode=require` parameter is already part of the string Appwrite returns, so no extra certificate configuration is needed. For full verification (`verify-full`) or mTLS, see the [Network security](/docs/products/databases/postgresql/network-security) page. + +# Pool serverless connections {% #pooling %} + +When you deploy to Vercel, Netlify, or any serverless platform, each invocation can spin up a fresh instance with its own connection pool. Hundreds of concurrent invocations fan out into far more backend connections than the engine allows. Route runtime traffic through the [connection pooler](/docs/products/databases/postgresql/connection-pooling) on the pooler port so it can multiplex those instances over a small number of backend connections. + +The pooler defaults to **transaction mode**, which does not keep a backend connection across statements, so server-side prepared statements aren't available. Reserve the engine port for migrations, which need a session-level connection. The two-URL split above (`DATABASE_URL` pooled, `DIRECT_URL` direct) is exactly what an ORM like Prisma expects. + +# Use a singleton client {% #singleton %} + +Instantiate one client per module scope and reuse it across invocations, so warm serverless instances don't reconnect on every request. In development, Next.js hot-reload re-evaluates modules, which can leak connections, so cache the client on `globalThis`. + +With [postgres.js](https://github.com/porsager/postgres) (note `prepare: false` for transaction-mode pooling): + +```ts +// lib/db.ts +import postgres from 'postgres'; + +const globalForDb = globalThis as unknown as { sql?: ReturnType<typeof postgres> }; + +export const sql = + globalForDb.sql ?? + postgres(process.env.DATABASE_URL!, { + prepare: false, // required: pooler transaction mode has no server-side prepared statements + }); + +if (process.env.NODE_ENV !== 'production') globalForDb.sql = sql; +``` + +Query it from a Server Component or Route Handler: + +```ts +// app/users/route.ts +import { sql } from '@/lib/db'; + +export async function GET() { + const users = await sql`SELECT id, email FROM users ORDER BY created_at DESC LIMIT 10`; + return Response.json(users); +} +``` + +# Use Prisma or Drizzle {% #orm %} + +For a typed schema, migrations, and a query builder, reach for an ORM. Both integrate with the pooled `DATABASE_URL` plus direct `DIRECT_URL` pattern above. + +- **Prisma**: point the runtime at the pooled connection string and keep the Prisma CLI on the engine port for migrations, then run `prisma migrate deploy`. See the [Prisma](/docs/products/databases/postgresql/integrations/prisma) guide for the full config and migration flow. +- **Drizzle**: use a pooled client (`prepare: false` on postgres.js) for runtime and the direct URL for `drizzle-kit` migrations. See the [Drizzle](/docs/products/databases/postgresql/integrations/drizzle) guide. + +{% arrow_link href="/docs/products/databases/postgresql/integrations/prisma" %} +Set up Prisma against native PostgreSQL +{% /arrow_link %} + +# Edge runtime: use the SQL API {% #edge %} + +If a Route Handler or route segment opts into the Edge runtime, it can't open a TCP socket, so no engine driver will work there: + +```ts +export const runtime = 'edge'; // no TCP sockets available +``` + +From the Edge runtime, you can execute one parameterised SQL statement over HTTPS and get JSON back. `SELECT`, `INSERT`, `UPDATE`, and `DELETE` statements are allowed by default. Use the global `fetch` available in the Edge runtime: + +```ts +// app/edge-users/route.ts +export const runtime = 'edge'; + +export async function GET() { + const response = await fetch( + 'https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/executions', + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Appwrite-Project': process.env.APPWRITE_PROJECT_ID!, + 'X-Appwrite-Key': process.env.APPWRITE_API_KEY!, + }, + body: JSON.stringify({ + sql: 'SELECT id, email FROM users WHERE created_at > $1 ORDER BY created_at DESC LIMIT $2', + bindings: ['2026-05-01T00:00:00Z', 10], + }), + }, + ); + + const { rows } = await response.json(); + return Response.json(rows); +} +``` + +The response is `{ rows, rowCount, columns, durationMs, truncated, bytes }`. Bindings are sent separately and never interpolated into the SQL string. Add `APPWRITE_PROJECT_ID` and `APPWRITE_API_KEY` (a key with the `databases.read` scope) to your environment alongside the database URLs. + +{% arrow_link href="/docs/products/databases/postgresql/quick-start#first-queries" %} +Read the SQL API reference +{% /arrow_link %} + +# Local development {% #local %} + +`next dev` runs on the Node.js runtime, so a local server connects to the native PostgreSQL database over TLS exactly like production. Keep `DATABASE_URL` and `DIRECT_URL` in `.env.local`. + +For throwaway data in tests or experiments, create a [branch](/docs/products/databases/postgresql/branches), an instant, isolated copy with its own connection string, and point `.env.local` at it. Delete the branch when you're done. + +# Deploy {% #deploy %} + +When you deploy, set the same `DATABASE_URL` (pooled) and `DIRECT_URL` (direct) as environment variables on your hosting platform, run your migrations in the build step, and fall back to the SQL API from any Edge Functions. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/postgresql/integrations/prisma" title="Prisma" %} +Datasource config, pooled and direct URLs, and the migration workflow. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/quick-start#first-queries" title="SQL API" %} +Query over HTTPS from the Edge runtime without a TCP connection. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/connection-pooling" title="Connection pooler" %} +Pool modes, ports, and read/write splitting for serverless workloads. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/postgresql/integrations/prisma/+page.markdoc b/src/routes/docs/products/databases/postgresql/integrations/prisma/+page.markdoc new file mode 100644 index 00000000000..074c16c40fb --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/integrations/prisma/+page.markdoc @@ -0,0 +1,222 @@ +--- +layout: article +title: Prisma +description: Use Prisma ORM with an Appwrite native PostgreSQL database. Configure the Prisma 7 datasource, run migrations through the direct connection, and route serverless runtime traffic through the connection pooler. +--- + +[Prisma ORM](https://www.prisma.io/) works with Appwrite's native PostgreSQL database as a standard PostgreSQL target. Configure Prisma with the connection string from Appwrite, run Prisma Migrate against the direct database connection, and use Prisma Client from your application code. + +{% info title="Before you start" %} +You'll need a native PostgreSQL database in a `ready` state and its credentials. Open the database in the Console and click **Credentials** to copy values from the **Details**, **DSN**, **.env**, **Prisma**, **Drizzle**, or **psql** tabs. You can also call `postgresql.get()` from the Appwrite API to read `hostname`, `connectionUser`, `connectionPassword`, and `connectionString`. +{% /info %} + +# Initialize Prisma {% #initialize %} + +Install Prisma, Prisma Client, the PostgreSQL driver adapter, and the TypeScript tools used by the examples: + +```bash +npm install -D prisma typescript tsx @types/node @types/pg +npm install @prisma/client @prisma/adapter-pg dotenv pg +``` + +Initialize Prisma for PostgreSQL: + +```bash +npx prisma init --datasource-provider postgresql --output ../generated/prisma +``` + +Prisma 7 generates a `prisma.config.ts` file and a `prisma/schema.prisma` file. Set `"type": "module"` in `package.json` if your project does not already use ECMAScript modules. + +# Configure connection strings {% #connection-strings %} + +Copy the connection string from the Console **Credentials** view. Keep it in environment variables and do not commit it: + +```env +DATABASE_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require" +DIRECT_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require" +SHADOW_DATABASE_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require&schema=prisma_shadow" +``` + +`DATABASE_URL` is the runtime connection used by Prisma Client. `DIRECT_URL` is the direct database connection used by Prisma CLI commands. `SHADOW_DATABASE_URL` is required by `prisma migrate dev`, because Appwrite's `admin` user can run schema changes but cannot create additional PostgreSQL databases for Prisma's default shadow database workflow. + +Create the shadow schema once before you run `prisma migrate dev`: + +```sql +CREATE SCHEMA IF NOT EXISTS prisma_shadow; +``` + +Appwrite Cloud uses TLS for PostgreSQL connections, so keep `sslmode=require`. For full certificate verification or mTLS, see [Network security](/docs/products/databases/postgresql/network-security). + +# Configure Prisma {% #configure-prisma %} + +In `prisma.config.ts`, read the CLI connection strings from the environment: + +```ts +import "dotenv/config"; +import { defineConfig } from "prisma/config"; + +export default defineConfig({ + schema: "prisma/schema.prisma", + migrations: { + path: "prisma/migrations", + seed: "tsx prisma/seed.ts", + }, + datasource: { + url: process.env["DIRECT_URL"] ?? process.env["DATABASE_URL"], + shadowDatabaseUrl: process.env["SHADOW_DATABASE_URL"], + }, +}); +``` + +In `prisma/schema.prisma`, keep the datasource provider in the schema file and generate Prisma Client into the output directory created by `prisma init`: + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" +} + +datasource db { + provider = "postgresql" +} + +model User { + id String @id @default(uuid()) + email String @unique + createdAt DateTime @default(now()) +} +``` + +If you are connecting to an existing database, run `npx prisma db pull` after you configure the environment variables to introspect its schema. + +# Pool connections from serverless {% #pooling %} + +Prisma Client opens database connections from each running application instance. On serverless platforms, many cold starts can quickly multiply the number of backend PostgreSQL connections. Route runtime traffic through the [connection pooler](/docs/products/databases/postgresql/connection-pooling) by using the pooler port, `6432`, for `DATABASE_URL` while keeping `DIRECT_URL` on the direct PostgreSQL port, `5432`, for migrations and introspection: + +```env +# Runtime: pooled connection through the Appwrite connection pooler +DATABASE_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:6432/<database>?sslmode=require" + +# Prisma CLI: direct PostgreSQL connection for migrations and introspection +DIRECT_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require" + +# Prisma Migrate development shadow schema +SHADOW_DATABASE_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require&schema=prisma_shadow" +``` + +The pooler defaults to **transaction mode**, which gives the highest connection multiplexing. If your application depends on session-level features such as advisory locks, `LISTEN`/`NOTIFY`, temporary tables, or session-scoped prepared statements, switch the pooler to **session mode**. See [Connection pooling](/docs/products/databases/postgresql/connection-pooling#modes) for pool mode trade-offs. + +# Run migrations {% #migrate %} + +Generate and apply a migration in development: + +```bash +npx prisma migrate dev --name init +``` + +In CI or production, apply committed migrations without prompting: + +```bash +npx prisma migrate deploy +``` + +Generate Prisma Client after you install dependencies or change `prisma/schema.prisma`: + +```bash +npx prisma generate +``` + +The primary `admin` user owns the database and can run schema changes. Scoped [database roles](/docs/products/databases/postgresql/connections#roles), such as `readonly` and `readwrite` users, are intended for application access and should not run migrations. + +# Seed data {% #seed %} + +With the seed command configured in `prisma.config.ts`, add a seed script: + +```ts +import "dotenv/config"; +import { PrismaPg } from "@prisma/adapter-pg"; +import { PrismaClient } from "../generated/prisma/client"; + +const adapter = new PrismaPg( + { connectionString: process.env.DATABASE_URL! }, + { schema: process.env.DATABASE_SCHEMA }, +); + +const prisma = new PrismaClient({ adapter }); + +await prisma.user.upsert({ + where: { email: "ada@example.com" }, + update: {}, + create: { email: "ada@example.com" }, +}); + +await prisma.$disconnect(); +``` + +Run the seed command: + +```bash +npx prisma db seed +``` + +`DATABASE_SCHEMA` is optional. Set it only if you connect to a PostgreSQL schema other than `public`. + +# Query with Prisma Client {% #query %} + +Instantiate Prisma Client with the PostgreSQL driver adapter: + +```ts +import "dotenv/config"; +import { PrismaPg } from "@prisma/adapter-pg"; +import { PrismaClient } from "./generated/prisma/client"; + +const adapter = new PrismaPg( + { connectionString: process.env.DATABASE_URL! }, + { schema: process.env.DATABASE_SCHEMA }, +); + +const prisma = new PrismaClient({ adapter }); + +const user = await prisma.user.create({ + data: { email: "grace@example.com" }, +}); + +const recent = await prisma.user.findMany({ + orderBy: { createdAt: "desc" }, + take: 10, +}); + +console.log({ user, recent }); + +await prisma.$disconnect(); +``` + +On long-running servers, instantiate `PrismaClient` once and reuse it. On serverless platforms, keep a single client per module scope so warm invocations reuse it, and rely on the pooler to absorb cold-start connection churn. + +# Use a branch for previews and CI {% #branches %} + +[Branches](/docs/products/databases/postgresql/branches) are instant, isolated copies of a database with their own hostname and connection string. They are useful for running migrations against throwaway data in a pull-request preview or integration-test job: + +1. Create a branch from the API and read its `connectionString`. +2. Export it as `DIRECT_URL`, and use the pooled variant as `DATABASE_URL` if the branch has the pooler enabled. +3. Run `npx prisma migrate deploy` and your test suite against the branch. +4. Delete the branch when the job finishes. + +Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations run against realistic data without touching production. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/postgresql/connections" title="Connections" %} +Retrieve credentials, rotate the password, and create scoped connection users. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/connection-pooling" title="Connection pooling" %} +Pool modes, ports, and read/write splitting for serverless workloads. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/branches" title="Branches" %} +Ephemeral database copies for preview environments and CI. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/network-security" title="Network security" %} +TLS modes, certificate verification, mTLS, and IP allowlists. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/postgresql/integrations/rails/+page.markdoc b/src/routes/docs/products/databases/postgresql/integrations/rails/+page.markdoc new file mode 100644 index 00000000000..125433ae833 --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/integrations/rails/+page.markdoc @@ -0,0 +1,168 @@ +--- +layout: article +title: Rails +description: Use Ruby on Rails and ActiveRecord with an Appwrite native PostgreSQL database. Configure database.yml, run migrations against the direct database port, and size the ActiveRecord pool. +--- + +A native PostgreSQL database is a standard PostgreSQL engine, so [Ruby on Rails](https://rubyonrails.org/) works against it through ActiveRecord with no Appwrite-specific configuration. Point `config/database.yml` at the connection details from the [Connections](/docs/products/databases/postgresql/connections) page and use ActiveRecord, migrations, and the rest of the Rails toolchain exactly as you would against any PostgreSQL server. + +{% info title="Before you start" %} +You'll need a native PostgreSQL database in a `ready` state and its credentials. In the Appwrite Console, open the database and click **Credentials**. Use the **Details** tab for individual values, or copy a ready-made string from the **DSN**, **.env**, **Prisma**, **Drizzle**, or **psql** tab. You can also call `postgresql.get()` from the Appwrite API to read `hostname`, `connectionUser`, `connectionPassword`, and `connectionString`. The primary user is `admin`, and the database name is generated for each database. +{% /info %} + +# Install the database driver {% #driver %} + +ActiveRecord talks to PostgreSQL through the `pg` driver gem. Add it to your `Gemfile`: + +```ruby +# Gemfile +gem 'pg' +``` + +The `pg` gem builds against `libpq`, so the PostgreSQL client headers must be available at install time. + +Then install: + +```bash +bundle install +``` + +# Set the connection string {% #connection-string %} + +Copy the connection string from the Console **Credentials** dialog, or fetch it with the [API](/docs/products/databases/postgresql/connections#credentials). Keep it in an environment variable and do not commit it: + +```env +DATABASE_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require" +``` + +The TLS parameter (`sslmode=require`) is already part of the string Appwrite returns. Appwrite Cloud terminates TLS at the edge, so no extra certificate configuration is needed. For full certificate verification (`verify-full`) or mTLS, see the [Network](/docs/products/databases/postgresql/network-security) page. + +# Configure database.yml {% #database-yml %} + +Rails reads `DATABASE_URL` automatically. The simplest configuration points the `url` at the environment variable and lets ActiveRecord parse the host, port, database, and credentials out of it: + +```yaml +# config/database.yml +production: + adapter: postgresql + url: <%= ENV["DATABASE_URL"] %> + pool: <%= ENV.fetch("RAILS_MAX_THREADS", 5) %> +``` + +If you'd rather set the fields explicitly, the discrete keys map one-to-one to the values from the [Connections](/docs/products/databases/postgresql/connections#credentials) response. Use ERB to read each value from the environment so no secret lands in source control: + +```yaml +# config/database.yml +production: + adapter: postgresql + host: <%= ENV["DB_HOST"] %> # db-<hash>.<region>.appwrite.center + port: <%= ENV.fetch("DB_PORT", 5432) %> + database: <%= ENV["DB_NAME"] %> # <database> + username: <%= ENV.fetch("DB_USER", "admin") %> + password: <%= ENV["DB_PASSWORD"] %> + sslmode: require + pool: <%= ENV.fetch("RAILS_MAX_THREADS", 5) %> +``` + +When both `DATABASE_URL` and explicit keys are present, Rails merges them. `sslmode` and `pool` can still be set in `database.yml`, so a `url`-based config can carry the TLS parameter in the string and override `pool` in the YAML. + +# Size the connection pool {% #pool %} + +ActiveRecord manages a per-process connection pool. The `pool:` value caps how many backend connections a single Rails process holds, and it defaults to `5`. It must be large enough for every thread that checks out a connection, your Puma worker threads plus any background job threads in the same process. + +```yaml +production: + adapter: postgresql + url: <%= ENV["DATABASE_URL"] %> + pool: <%= ENV.fetch("RAILS_MAX_THREADS", 5) %> +``` + +Tying `pool` to `RAILS_MAX_THREADS` keeps it aligned with Puma's thread count. Each Puma **worker** is a separate process with its own pool, so the backend connection count is roughly `pool × workers × server instances`. Keep that product within your specification's `maxConnections`, see [specifications](/docs/products/databases/postgresql#specifications). + +# Run migrations {% #migrate %} + +Generate and apply migrations the usual way: + +```bash +bin/rails db:migrate +``` + +Migrations issue DDL and need a session-level connection, so run them against the direct PostgreSQL port `5432`, not the transaction-mode pooler. Point `DATABASE_URL` (or a separate migration URL) at the direct port when you run `db:migrate`. The primary `admin` user owns the database and can run schema changes. Narrower [database roles](/docs/products/databases/postgresql/connections#roles) should only receive the privileges your application needs. + +# Use ActiveRecord {% #activerecord %} + +Once `database.yml` is configured, models work with no further setup. Define a migration and model, then query through ActiveRecord: + +```ruby +# db/migrate/20240101000000_create_users.rb +class CreateUsers < ActiveRecord::Migration[7.1] + def change + create_table :users do |t| + t.string :email, null: false + t.timestamps + end + add_index :users, :email, unique: true + end +end +``` + +```ruby +# app/models/user.rb +class User < ApplicationRecord + validates :email, presence: true, uniqueness: true +end +``` + +```ruby +User.create!(email: 'ada@example.com') + +recent = User.order(created_at: :desc).limit(10) +``` + +ActiveRecord opens connections lazily and reuses them from the pool, so a long-running Puma server keeps a small, stable set of backend connections rather than opening one per request. + +# Pooling for a long-running server {% #pooling %} + +A Rails app under Puma is a long-running process: it holds an ActiveRecord pool for its lifetime. That pairs naturally with the direct PostgreSQL port `5432`, sized so `pool × workers` stays within your connection budget. This is the recommended setup for a persistent server. + +If you instead route through the [connection pooler](/docs/products/databases/postgresql/connection-pooling) to absorb spikes or many app instances, prefer **session mode**, which keeps a backend connection for the whole client session and behaves like a direct connection to ActiveRecord. The pooler defaults to **transaction mode**, which hands out a different backend connection per transaction. ActiveRecord uses server-side prepared statements by default, and those are tied to one backend connection, so on the transaction-mode pooler you must disable them: + +```yaml +production: + adapter: postgresql + url: <%= ENV["DATABASE_URL"] %> # pooler host, port 6432 + pool: <%= ENV.fetch("RAILS_MAX_THREADS", 5) %> + prepared_statements: false +``` + +See the [pooler](/docs/products/databases/postgresql/connection-pooling#modes) page for the mode trade-offs. Whichever runtime connection you choose, always run `bin/rails db:migrate` against the direct PostgreSQL port. + +# Use a branch for previews and CI {% #branches %} + +[Branches](/docs/products/databases/postgresql/branches) are instant, isolated copies of a database with their own hostname and connection string. They're ideal for running migrations against throwaway data in a pull-request preview or an integration-test job: + +1. Create a branch from the API and read its `connectionString`. +2. Export it as `DATABASE_URL` for the job. +3. Run `bin/rails db:migrate` and your test suite against the branch. +4. Delete the branch when the job finishes. + +A branch has no pooler and exposes the PostgreSQL port directly, which gives migrations the session-level connection they need. Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations run against realistic data without touching production. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/postgresql/connections" title="Connections" %} +Retrieve credentials, rotate the password, and create database roles. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/connection-pooling" title="Connection pooler" %} +Pool modes, ports, and read/write splitting for high-concurrency workloads. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/branches" title="Branches" %} +Ephemeral database copies for preview environments and CI. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/network-security" title="Network" %} +TLS modes, certificate verification, mTLS, and IP allowlists. +{% /cards_item %} +{% /cards %} + +For ActiveRecord and migration details beyond this guide, see the [Rails configuration guide](https://guides.rubyonrails.org/configuring.html). diff --git a/src/routes/docs/products/databases/postgresql/integrations/retool/+page.markdoc b/src/routes/docs/products/databases/postgresql/integrations/retool/+page.markdoc new file mode 100644 index 00000000000..ea28a048987 --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/integrations/retool/+page.markdoc @@ -0,0 +1,95 @@ +--- +layout: article +title: Retool +description: Connect Retool to an Appwrite native PostgreSQL database to build internal and admin tools. Retrieve credentials from the Console, configure the PostgreSQL resource with TLS, and allow Retool Cloud network access when needed. +--- + +An Appwrite [native PostgreSQL database](/docs/products/databases/postgresql) exposes a standard PostgreSQL connection, so [Retool](https://retool.com/) connects to it through the built-in **PostgreSQL** resource. Use the database hostname, generated database name, and credentials from Appwrite, then build queries, tables, and forms in Retool for dashboards and admin panels. + +{% info title="Before you start" %} +You'll need a native PostgreSQL database in a `ready` state and permission to create resources in Retool. In the Appwrite Console, open the database and click **Credentials**. Use the **Details** tab for individual values, or copy a ready-made string from the **DSN**, **.env**, **Prisma**, **Drizzle**, or **psql** tab. You can also fetch credentials with the API by calling `postgresql.get()`, which returns `hostname`, `connectionUser`, `connectionPassword`, and `connectionString`. See [Connections](/docs/products/databases/postgresql/connections) for the full flow. +{% /info %} + +# Choose credentials {% #credentials %} + +The primary user is `admin`, and the database name is generated for each database. The connection string has this form: `postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>`. + +Use `admin` for initial setup or schema work because it owns the database and can run DDL. For a Retool app that only needs application-level access, create a narrower PostgreSQL role from the database **Roles** tab in the Console, then grant only the table privileges the app needs from the SQL editor or `psql`. For example, a dashboard role usually needs `SELECT`, while an admin tool may need `SELECT`, `INSERT`, `UPDATE`, and `DELETE` on specific tables. + +{% arrow_link href="/docs/products/databases/postgresql/connections#roles" %} +Read more about database roles +{% /arrow_link %} + +# Create the PostgreSQL resource {% #resource %} + +In Retool, go to **Resources**, click **Create new** > **Resource**, search for `PostgreSQL`, and select the PostgreSQL tile. Give the resource a clear **Name** and optional **Description** that identifies the Appwrite database and environment. + +In **Resource credentials**, either paste the Appwrite connection string or fill in the fields manually: + +| Retool setting | Value | +|----------------|-------| +| Host | `db-<hash>.<region>.appwrite.center` | +| Port | `5432` | +| Database name | `<database>` | +| Connection options | Optional PostgreSQL parameters, such as `application_name=retool` | +| Authentication | **Username and password** | +| Username | `admin` or a narrower PostgreSQL role | +| Password | The password from the Appwrite credentials dialog or API response | + +In **Advanced options**, choose an outbound region if your Retool organization uses regional egress and you want the resource traffic to originate near your Appwrite database. + +Enable **SSL/TLS**. Appwrite Cloud terminates TLS at the edge, and the certificate is signed by a public CA. If Retool shows **Reject unauthorized**, keep it enabled. If Retool shows **Verification mode**, choose **Full verification**. Leave **CA certificate** empty. + +Click **Test connection**. If the test succeeds, click **Create resource**. + +# Pick the connection path {% #connection-path %} + +For most Retool apps, connect directly to PostgreSQL on port `5432`. Retool uses prepared statements for PostgreSQL queries, and Appwrite's transaction-mode pooler does not preserve session-level state across statements. If you use the Appwrite [connection pooler](/docs/products/databases/postgresql/connection-pooling), configure it in **session** mode for Retool. + +{% arrow_link href="/docs/products/databases/postgresql/connection-pooling#modes" %} +Compare pooler modes +{% /arrow_link %} + +# Allow Retool Cloud through the network {% #network %} + +If you enabled an [IP allowlist](/docs/products/databases/postgresql/network-security#ip-allowlist) for the database, add the Retool Cloud egress addresses for the resource's outbound region. Retool's default outbound region is `us-west-2`, and Retool also documents `eu-central-1` and `ap-southeast-1` egress addresses. Retrieve the current list from [Retool's IP address documentation](https://docs.retool.com/data-sources/reference/ip-allowlist-cloud-orgs) instead of copying static addresses into your runbook. + +If you leave the database allowlist open, rely on TLS plus database credentials and least-privilege roles to protect access. + +{% arrow_link href="/docs/products/databases/postgresql/network-security#ip-allowlist" %} +Configure the IP allowlist +{% /arrow_link %} + +# Build an admin tool {% #build %} + +After the resource is connected, create PostgreSQL queries in Retool and wire them to components: + +- Use **SQL mode** for read queries that feed a **Table**, chart, or other display component. +- Use **GUI mode** actions such as **Insert a record**, **Update an existing record**, and **Delete a record** for forms and editable tables. +- Bind table edits to the **Save changes** event and refresh the read query after writes complete. +- Show a confirmation modal before delete actions. + +Reference component values with Retool's `{{ }}` embedded expressions. Retool converts PostgreSQL queries to prepared statements by default, which separates values from SQL text and helps prevent SQL injection. Keep that protection enabled unless you have a specific, reviewed reason to disable it. + +Use database roles as the last line of defense. A dashboard role with only `SELECT` cannot write even if a query is misconfigured. A data-entry role with table-level write privileges can create, update, and delete rows without also owning the schema. + +# Use a branch for staging {% #branches %} + +[Branches](/docs/products/databases/postgresql/branches) are API-created, isolated copies of a PostgreSQL database with their own connection details. Create a branch for staging or preview work, fetch its `connectionString`, and configure a second Retool PostgreSQL resource against that branch. Delete the branch when the staging tool is no longer needed. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/postgresql" title="PostgreSQL" %} +Create and manage a native PostgreSQL database. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/connections" title="Connections" %} +Retrieve credentials, rotate the primary password, and create database roles. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/network-security" title="Network security" %} +TLS, IP allowlists, and other network controls. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/branches" title="Branches" %} +Create isolated database copies for staging and preview environments. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/postgresql/integrations/spring-boot/+page.markdoc b/src/routes/docs/products/databases/postgresql/integrations/spring-boot/+page.markdoc new file mode 100644 index 00000000000..773cf725f09 --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/integrations/spring-boot/+page.markdoc @@ -0,0 +1,182 @@ +--- +layout: article +title: Spring Boot +description: Connect a Spring Boot application to an Appwrite native PostgreSQL database with Spring Data JPA and Hibernate. Configure the JDBC datasource and HikariCP pool, map entities, and run Flyway or Liquibase migrations against PostgreSQL. +--- + +An Appwrite native PostgreSQL database is a standard PostgreSQL engine, so a Spring Boot application connects to it through the PostgreSQL JDBC driver with no Appwrite-specific runtime configuration. Point `spring.datasource` at the JDBC URL from your database credentials, size the built-in HikariCP pool, and use Spring Data JPA, Hibernate, Flyway, or Liquibase as you would with any managed PostgreSQL server. + +{% info title="Before you start" %} +You'll need a native PostgreSQL database in a `ready` state and its credentials. In the Appwrite Console, open the database and click **Credentials**. Use the **Details** tab for individual values, or copy a ready-made string from the **DSN**, **.env**, **Prisma**, **Drizzle**, or **psql** tab. You can also fetch credentials with the API by calling `postgresql.get()`, which returns `hostname`, `connectionUser`, `connectionPassword`, and `connectionString`. See [Connections](/docs/products/databases/postgresql/connections) for the full flow. +{% /info %} + +# Add the dependencies {% #dependencies %} + +A Spring Data JPA application needs the JPA starter and the PostgreSQL JDBC driver. HikariCP ships with `spring-boot-starter-data-jpa`, and Spring Boot picks the driver class from the JDBC URL. + +```text +org.springframework.boot:spring-boot-starter-data-jpa +org.postgresql:postgresql +``` + +If you use Flyway or Liquibase, add that migration tool through Spring Initializr or your build file as well. Current Spring Boot projects that use Flyway with PostgreSQL include the Flyway integration and `org.flywaydb:flyway-database-postgresql`. + +# Configure the datasource {% #datasource %} + +Build the JDBC URL from the host and database name in the credentials dialog. Appwrite uses port `5432`, the primary user is `admin`, and the database name is generated per database. Read the password from the environment: + +```yaml +spring: + datasource: + url: jdbc:postgresql://db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require + username: admin + password: ${DB_PASSWORD} + hikari: + maximum-pool-size: 10 + minimum-idle: 2 + connection-timeout: 30000 + max-lifetime: 1200000 + jpa: + hibernate: + ddl-auto: validate +``` + +The equivalent `application.properties`: + +```ini +spring.datasource.url=jdbc:postgresql://db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require +spring.datasource.username=admin +spring.datasource.password=${DB_PASSWORD} +spring.datasource.hikari.maximum-pool-size=10 +spring.datasource.hikari.minimum-idle=2 +spring.datasource.hikari.connection-timeout=30000 +spring.datasource.hikari.max-lifetime=1200000 +spring.jpa.hibernate.ddl-auto=validate +``` + +`sslmode=require` enables TLS for the PostgreSQL connection. If your security policy requires host certificate validation, configure pgJDBC with `sslmode=verify-full` and a JVM trust configuration that trusts the certificate chain. The [Network security](/docs/products/databases/postgresql/network-security) page covers TLS, mTLS, and IP allowlists. + +# Size the HikariCP pool {% #pool %} + +A Spring Boot server is long-running, so it holds its HikariCP pool open for the lifetime of the process. Keep `maximum-pool-size` modest. HikariCP guidance is that throughput usually peaks at a small pool, roughly `(CPU cores x 2) + 1` for the database, not hundreds of connections. A pool that exceeds what PostgreSQL can serve only queues work inside the database and adds latency. + +Each replica of your application opens its own pool, so multiply `maximum-pool-size` by the number of instances and keep the total under the connection budget of your native PostgreSQL database [specification](/docs/products/databases/postgresql#specifications). Set `max-lifetime` a little below your infrastructure's idle timeout so HikariCP recycles connections before they are closed. + +# Map an entity {% #entity %} + +Define a JPA entity and a Spring Data repository as usual. This example uses a prefixed table name so it is easy to identify in a shared database: + +```java +package com.example.demo; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +@Entity +@Table(name = "users") +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, unique = true) + private String email; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } +} +``` + +```java +package com.example.demo; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserRepository extends JpaRepository<User, Long> { + Optional<User> findByEmail(String email); +} +``` + +Inject the repository wherever you need it and call `save`, `findById`, `findByEmail`, and the rest of the generated query methods. HikariCP hands each transaction a pooled connection and returns it on commit. + +# Run migrations {% #migrate %} + +Let a migration tool own the schema and set `ddl-auto: validate` so Hibernate checks the mapping against the live tables at startup but never alters them. Add Flyway or Liquibase to your build and Spring Boot runs pending migrations automatically on boot. + +For example, a Flyway migration at `src/main/resources/db/migration/V1__init.sql` can create the table used by the entity above: + +```sql +CREATE TABLE users ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + email VARCHAR(255) NOT NULL UNIQUE +); +``` + +Flyway reads versioned scripts from `src/main/resources/db/migration`. Point Flyway at the direct PostgreSQL port `5432` so migrations run on a session connection with DDL privileges. Setting `spring.flyway.url` gives Flyway its own datasource, independent of the runtime pool: + +```ini +spring.flyway.url=jdbc:postgresql://db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require +spring.flyway.user=admin +spring.flyway.password=${DB_PASSWORD} +``` + +Liquibase is equivalent: it reads a changelog from `src/main/resources/db/changelog` and accepts its own `spring.liquibase.url`, `spring.liquibase.user`, and `spring.liquibase.password` pointing at the same PostgreSQL port. + +The primary `admin` user owns the database and can run schema changes. Scoped [database roles](/docs/products/databases/postgresql/connections#roles) intentionally cannot run DDL, so run migrations as `admin`. + +# Pooling and the connection pooler {% #pooling %} + +HikariCP is already a connection pool, so a long-running Spring Boot server should connect to the direct PostgreSQL port `5432` and let HikariCP manage connections. Routing a server's traffic through the [connection pooler](/docs/products/databases/postgresql/connection-pooling) in `transaction` mode stacks HikariCP on top of a transaction pooler and can break server-side prepared statements, which the PostgreSQL JDBC driver uses. + +If you put the pooler in front of your server, use `session` mode so PostgreSQL keeps a backend connection for the whole client session and preserves prepared statements. Connect HikariCP on the pooler port `6432` and keep `maximum-pool-size` small. See the [connection pooler](/docs/products/databases/postgresql/connection-pooling#modes) page for the mode trade-offs. Always run Flyway or Liquibase against port `5432` regardless of how runtime traffic connects. + +# Use a branch for previews and CI {% #branches %} + +[Branches](/docs/products/databases/postgresql/branches) are instant, isolated copies of a database with their own hostname and connection string. They are useful for running migrations against throwaway data in a pull-request preview or integration-test job. + +1. Create a branch with the API and read its `connectionString`. +2. Convert the connection string to a JDBC URL and inject it into `spring.datasource.url`, or split it into `spring.datasource.url`, `spring.datasource.username`, and `spring.datasource.password`. +3. Boot the application so Flyway or Liquibase applies migrations, then run your test suite against the branch. +4. Delete the branch when the job finishes. + +Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations and `@DataJpaTest` integration tests run against realistic data without touching production. + +# Related {% #related %} + +{% cards %} +{% cards_item href="/docs/products/databases/postgresql/connections" title="Connections" %} +Retrieve credentials, rotate the password, and create scoped database roles. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/connection-pooling" title="Connection pooler" %} +Pool modes, ports, and read/write splitting for the connection pooler. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/branches" title="Branches" %} +Ephemeral database copies for preview environments and CI. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/network-security" title="Network security" %} +TLS modes, certificate verification, mTLS, and IP allowlists. +{% /cards_item %} +{% /cards %} + +{% arrow_link href="/docs/products/databases/postgresql" %} +Back to native PostgreSQL overview +{% /arrow_link %} diff --git a/src/routes/docs/products/databases/postgresql/maintenance/+page.markdoc b/src/routes/docs/products/databases/postgresql/maintenance/+page.markdoc new file mode 100644 index 00000000000..238b636f6a5 --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/maintenance/+page.markdoc @@ -0,0 +1,651 @@ +--- +layout: article +title: Maintenance +description: Maintenance windows, online engine version upgrades, pause and resume, and the lifecycle states of your PostgreSQL database. +--- + +Appwrite manages the infrastructure around your database: security patches, engine upgrades, and instance health. This page covers the controls you have over when and how that maintenance happens. + +# Maintenance window {% #window %} + +Routine maintenance that can briefly affect the database runs inside a weekly window that you choose. Set it under **Settings** > **Maintenance** in the Console by picking a day and start hour (UTC), or through the API: + +{% multicode %} +```server-nodejs +import { Client, Postgresql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.updateMaintenance({ + databaseId: '<DATABASE_ID>', + day: 'sun', + hourUtc: 3, +}); +``` +```server-deno +import { Client, Postgresql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.updateMaintenance({ + databaseId: '<DATABASE_ID>', + day: 'sun', + hourUtc: 3, +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Postgresql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$postgresql = new Postgresql($client); + +$postgresql->updateMaintenance( + databaseId: '<DATABASE_ID>', + day: 'sun', + hourUtc: 3, +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.postgresql import Postgresql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +postgresql = Postgresql(client) + +postgresql.update_maintenance( + database_id='<DATABASE_ID>', + day='sun', + hour_utc=3, +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +postgresql = Postgresql.new(client) + +postgresql.update_maintenance( + database_id: '<DATABASE_ID>', + day: 'sun', + hour_utc: 3, +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Postgresql postgresql = new Postgresql(client); + +await postgresql.UpdateMaintenance( + databaseId: "<DATABASE_ID>", + day: "sun", + hourUtc: 3 +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Postgresql postgresql = Postgresql(client); + +await postgresql.updateMaintenance( + databaseId: '<DATABASE_ID>', + day: 'sun', + hourUtc: 3, +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Postgresql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val postgresql = Postgresql(client) + +postgresql.updateMaintenance( + databaseId = "<DATABASE_ID>", + day = "sun", + hourUtc = 3, +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let postgresql = Postgresql(client) + +_ = try await postgresql.updateMaintenance( + databaseId: "<DATABASE_ID>", + day: "sun", + hourUtc: 3 +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewPostgresql(client) + + _, err := service.UpdateMaintenance("<DATABASE_ID>", "sun", 3) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::postgresql::Postgresql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let postgresql = Postgresql::new(&client); + + postgresql.update_maintenance("<DATABASE_ID>", "sun", 3).await?; + + Ok(()) +} +``` +```bash +curl -X PATCH \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + -H "Content-Type: application/json" \ + -d '{ + "day": "sun", + "hourUtc": 3 + }' \ + https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/maintenance +``` +{% /multicode %} + +`day` accepts `sun` through `sat`, and `hourUtc` accepts `0` to `23`. + +# Engine version upgrades {% #upgrades %} + +You can upgrade the PostgreSQL version online. A second instance is provisioned on the target version, data streams over with logical replication, and traffic cuts over once replication has caught up, with no read or write outage: + +{% multicode %} +```server-nodejs +import { Client, Postgresql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.createUpgrade({ + databaseId: '<DATABASE_ID>', + targetVersion: '18', +}); +``` +```server-deno +import { Client, Postgresql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.createUpgrade({ + databaseId: '<DATABASE_ID>', + targetVersion: '18', +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Postgresql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$postgresql = new Postgresql($client); + +$postgresql->createUpgrade( + databaseId: '<DATABASE_ID>', + targetVersion: '18', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.postgresql import Postgresql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +postgresql = Postgresql(client) + +postgresql.create_upgrade( + database_id='<DATABASE_ID>', + target_version='18', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +postgresql = Postgresql.new(client) + +postgresql.create_upgrade( + database_id: '<DATABASE_ID>', + target_version: '18', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Postgresql postgresql = new Postgresql(client); + +await postgresql.CreateUpgrade( + databaseId: "<DATABASE_ID>", + targetVersion: "18" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Postgresql postgresql = Postgresql(client); + +await postgresql.createUpgrade( + databaseId: '<DATABASE_ID>', + targetVersion: '18', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Postgresql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val postgresql = Postgresql(client) + +postgresql.createUpgrade( + databaseId = "<DATABASE_ID>", + targetVersion = "18", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let postgresql = Postgresql(client) + +_ = try await postgresql.createUpgrade( + databaseId: "<DATABASE_ID>", + targetVersion: "18" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewPostgresql(client) + + _, err := service.CreateUpgrade("<DATABASE_ID>", "18") + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::postgresql::Postgresql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let postgresql = Postgresql::new(&client); + + postgresql.create_upgrade("<DATABASE_ID>", "18").await?; + + Ok(()) +} +``` +```bash +curl -X POST \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + -H "Content-Type: application/json" \ + -d '{ + "targetVersion": "18" + }' \ + https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/upgrades +``` +{% /multicode %} + +Before a major version upgrade, check that your installed [extensions](/docs/products/databases/postgresql/extensions) support the target version. + +# Pause and resume {% #pause-resume %} + +{% only_dark %} +![Database settings status card](/images/docs/products/databases/postgresql/dark/settings.avif) +{% /only_dark %} +{% only_light %} +![Database settings status card](/images/docs/products/databases/postgresql/settings.avif) +{% /only_light %} + +A paused database stops its compute but keeps its storage, configuration, and credentials. Pause a database you are not using to stop paying for compute; resume it when you need it again. In the Console, use the **Running** toggle on the **Settings** page. From the API, update the status: + +{% multicode %} +```server-nodejs +import { Client, Postgresql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.update({ + databaseId: '<DATABASE_ID>', + status: 'paused', +}); +``` +```server-deno +import { Client, Postgresql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.update({ + databaseId: '<DATABASE_ID>', + status: 'paused', +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Postgresql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$postgresql = new Postgresql($client); + +$postgresql->update( + databaseId: '<DATABASE_ID>', + status: 'paused', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.postgresql import Postgresql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +postgresql = Postgresql(client) + +postgresql.update( + database_id='<DATABASE_ID>', + status='paused', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +postgresql = Postgresql.new(client) + +postgresql.update( + database_id: '<DATABASE_ID>', + status: 'paused', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Postgresql postgresql = new Postgresql(client); + +await postgresql.Update( + databaseId: "<DATABASE_ID>", + status: "paused" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Postgresql postgresql = Postgresql(client); + +await postgresql.update( + databaseId: '<DATABASE_ID>', + status: 'paused', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Postgresql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val postgresql = Postgresql(client) + +postgresql.update( + databaseId = "<DATABASE_ID>", + status = "paused", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let postgresql = Postgresql(client) + +_ = try await postgresql.update( + databaseId: "<DATABASE_ID>", + status: "paused" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/postgresql" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewPostgresql(client) + + _, err := service.Update( + "<DATABASE_ID>", + postgresql.WithUpdateStatus("paused"), + ) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::postgresql::Postgresql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let postgresql = Postgresql::new(&client); + + postgresql.update("<DATABASE_ID>", None, Some("paused"), None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None).await?; + + Ok(()) +} +``` +```bash +curl -X PATCH \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + -H "Content-Type: application/json" \ + -d '{ + "status": "paused" + }' \ + https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID> +``` +{% /multicode %} + +Set `status` back to `ready` to resume. Both transitions are asynchronous: the request returns immediately and the database moves through `pausing` or `resuming` before settling. A database in the `failed` state can also be recovered by setting its status to `ready`. + +# Lifecycle states {% #states %} + +| Status | Meaning | +|----------------|------------------------------------------------------------------| +| `provisioning` | Being created | +| `ready` | Online and accepting connections | +| `scaling` | A configuration or specification change is being applied | +| `pausing` | Transitioning to paused | +| `paused` | Compute stopped, storage retained | +| `resuming` | Transitioning back to ready | +| `restoring` | A backup or point-in-time restore is in progress | +| `failed` | An infrastructure error occurred; the database can be resumed | + +# Deleting a database {% #delete %} + +Delete a database from **Settings** > **Delete database** in the Console, or programmatically. Deletion stops billing and invalidates the credentials immediately. Deleting a database also deletes its backups, so export anything you need first. diff --git a/src/routes/docs/products/databases/postgresql/monitoring/+page.markdoc b/src/routes/docs/products/databases/postgresql/monitoring/+page.markdoc new file mode 100644 index 00000000000..9dfb2df04f9 --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/monitoring/+page.markdoc @@ -0,0 +1,235 @@ +--- +layout: article +title: Monitoring +description: Watch compute, connections, storage, and workload metrics live, inspect active connections, and check database health on your PostgreSQL database. +--- + +Every native database ships with built-in observability: live metrics in the Console, an active-connections inspector, and programmatic health checks. There is nothing to install; metrics collection runs next to the database. + +# Monitor tab {% #monitor %} + +{% only_dark %} +![Database monitor tab](/images/docs/products/databases/postgresql/dark/monitor.avif) +{% /only_dark %} +{% only_light %} +![Database monitor tab](/images/docs/products/databases/postgresql/monitor.avif) +{% /only_light %} + +Open your database and select the **Monitor** tab. The view is organized into sections: + +- **Overview**: key health indicators at a glance, including connection usage against your limit, storage used, cache hit ratio, uptime, and commit/rollback counts +- **Compute**: CPU and memory usage over time +- **Connections**: connection counts by state and by application +- **Storage**: disk usage and growth +- **Workload**: query throughput and activity + +Use the date range picker to zoom into an incident window, and **Refresh metrics** to pull the latest samples. + +A healthy OLTP database typically shows a cache hit ratio above 99%. A sustained lower ratio means the working set does not fit in memory, which is usually solved by moving up a [specification](/docs/products/databases/postgresql/scaling). + +# Active connections {% #connections %} + +{% only_dark %} +![Active connections tab](/images/docs/products/databases/postgresql/dark/connections.avif) +{% /only_dark %} +{% only_light %} +![Active connections tab](/images/docs/products/databases/postgresql/connections.avif) +{% /only_light %} + +The **Connections** tab lists live connections from PostgreSQL's `pg_stat_activity`, with the connection state, duration, client, application name, and current query for each. + +You can filter by state (**Active**, **Idle**, **Idle in transaction**, **Long-running**), and act on problem connections directly: cancel a running query, terminate a connection, or terminate all idle-in-transaction sessions at once. Idle-in-transaction sessions hold locks and block vacuum, so terminating them is often the fastest way to unblock a stuck workload. + +# Database health {% #status %} + +Poll the database status for live health information: readiness, uptime, connection counts, replica state, and storage volumes. Use it from deploy pipelines to wait for the database, or from your own monitoring: + +{% multicode %} +```server-nodejs +import { Client, Postgresql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +const status = await postgresql.getStatus({ + databaseId: '<DATABASE_ID>', +}); +``` +```server-deno +import { Client, Postgresql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +const status = await postgresql.getStatus({ + databaseId: '<DATABASE_ID>', +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Postgresql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$postgresql = new Postgresql($client); + +$status = $postgresql->getStatus( + databaseId: '<DATABASE_ID>', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.postgresql import Postgresql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +postgresql = Postgresql(client) + +status = postgresql.get_status( + database_id='<DATABASE_ID>', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +postgresql = Postgresql.new(client) + +status = postgresql.get_status( + database_id: '<DATABASE_ID>', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Postgresql postgresql = new Postgresql(client); + +var status = await postgresql.GetStatus( + databaseId: "<DATABASE_ID>" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Postgresql postgresql = Postgresql(client); + +final status = await postgresql.getStatus( + databaseId: '<DATABASE_ID>', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Postgresql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val postgresql = Postgresql(client) + +val status = postgresql.getStatus( + databaseId = "<DATABASE_ID>", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let postgresql = Postgresql(client) + +let status = try await postgresql.getStatus( + databaseId: "<DATABASE_ID>" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewPostgresql(client) + + result, err := service.GetStatus("<DATABASE_ID>") + if err != nil { + panic(err) + } + _ = result +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::postgresql::Postgresql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let postgresql = Postgresql::new(&client); + + let status = postgresql.get_status("<DATABASE_ID>").await?; + + Ok(()) +} +``` +```bash +curl -X GET \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/status +``` +{% /multicode %} + +For the database lifecycle state (`ready`, `scaling`, `restoring`, and friends), read the `status` field of the database object itself; see [lifecycle states](/docs/products/databases/postgresql/maintenance#states). + +# Explain a query {% #explain %} + +The Console's **SQL editor** tab has an **Explain** button that shows the execution plan for the query in the editor, without leaving the browser. Use it to check whether a slow query uses your indexes before reaching for `EXPLAIN ANALYZE` in `psql`. diff --git a/src/routes/docs/products/databases/postgresql/network-security/+page.markdoc b/src/routes/docs/products/databases/postgresql/network-security/+page.markdoc new file mode 100644 index 00000000000..0763884894a --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/network-security/+page.markdoc @@ -0,0 +1,463 @@ +--- +layout: article +title: Network security +description: TLS by default, IP allowlists, and idle timeouts for your PostgreSQL database. +--- + +Every native database is reachable through a unique public hostname, secured with TLS, and protected by network controls that you configure per database. + +# Hostname {% #hostname %} + +Each database gets a stable hostname in the form: + +``` +db-<hash>.<region>.appwrite.center +``` + +The hostname does not change for the lifetime of the database, across restarts, resizes, failovers, and version upgrades. You can copy it from the Console credentials dialog or the database response. + +# TLS {% #tls %} + +Connections on Appwrite Cloud are encrypted with TLS, terminated at Appwrite's edge and forwarded to your database over the internal network. The connection string from the credentials dialog carries the right SSL settings for your environment, so drivers need no extra configuration. + +# IP allowlist {% #ip-allowlist %} + +{% only_dark %} +![Network settings](/images/docs/products/databases/postgresql/dark/settings-network.avif) +{% /only_dark %} +{% only_light %} +![Network settings](/images/docs/products/databases/postgresql/settings-network.avif) +{% /only_light %} + +By default, any host that has your credentials can reach the database over the public internet. To restrict access to known networks, configure an IP allowlist. Connections from addresses outside the allowlist are dropped at the network layer, before authentication. + +In the Console, add entries under **Settings** > **Network**. + +From the API, pass CIDR blocks or single addresses: + +{% multicode %} +```server-nodejs +import { Client, Postgresql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.update({ + databaseId: '<DATABASE_ID>', + networkIPAllowlist: ['203.0.113.0/24', '198.51.100.7'], +}); +``` +```server-deno +import { Client, Postgresql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.update({ + databaseId: '<DATABASE_ID>', + networkIPAllowlist: ['203.0.113.0/24', '198.51.100.7'], +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Postgresql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$postgresql = new Postgresql($client); + +$postgresql->update( + databaseId: '<DATABASE_ID>', + networkIPAllowlist: ['203.0.113.0/24', '198.51.100.7'], +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.postgresql import Postgresql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +postgresql = Postgresql(client) + +postgresql.update( + database_id='<DATABASE_ID>', + network_i_p_allowlist=['203.0.113.0/24', '198.51.100.7'], +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +postgresql = Postgresql.new(client) + +postgresql.update( + database_id: '<DATABASE_ID>', + network_i_p_allowlist: ['203.0.113.0/24', '198.51.100.7'], +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Postgresql postgresql = new Postgresql(client); + +await postgresql.Update( + databaseId: "<DATABASE_ID>", + networkIPAllowlist: new List<string> { "203.0.113.0/24", "198.51.100.7" } +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Postgresql postgresql = Postgresql(client); + +await postgresql.update( + databaseId: '<DATABASE_ID>', + networkIPAllowlist: ['203.0.113.0/24', '198.51.100.7'], +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Postgresql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val postgresql = Postgresql(client) + +postgresql.update( + databaseId = "<DATABASE_ID>", + networkIPAllowlist = listOf("203.0.113.0/24", "198.51.100.7"), +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let postgresql = Postgresql(client) + +_ = try await postgresql.update( + databaseId: "<DATABASE_ID>", + networkIPAllowlist: ["203.0.113.0/24", "198.51.100.7"] +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/postgresql" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewPostgresql(client) + + _, err := service.Update( + "<DATABASE_ID>", + postgresql.WithUpdateNetworkIPAllowlist([]string{"203.0.113.0/24", "198.51.100.7"}), + ) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::postgresql::Postgresql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let postgresql = Postgresql::new(&client); + + postgresql.update("<DATABASE_ID>", None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None).await?; + + Ok(()) +} +``` +```bash +curl -X PATCH \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + -H "Content-Type: application/json" \ + -d '{ + "networkIPAllowlist": [ + "203.0.113.0/24", + "198.51.100.7" + ] + }' \ + https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID> +``` +{% /multicode %} + +Rules: + +- Entries are IPv4 or IPv6 addresses or CIDR blocks, up to 100 entries per database. +- An empty allowlist means the database accepts connections from any address. +- The allowlist applies to the database and pooler ports. Appwrite's internal infrastructure, backups, monitoring, and replication, is unaffected. + +{% info title="Don't lock yourself out" %} +If you connect from networks with changing addresses (home ISPs, mobile networks, serverless platforms without static egress), an allowlist can block you. Add your serverless provider's egress ranges, or leave the allowlist empty and rely on strong credentials and rotation. +{% /info %} + +# Idle timeout {% #idle-timeout %} + +Connections that stay idle are closed by the edge proxy after `networkIdleTimeoutSeconds`, 900 seconds by default. Long-lived driver pools usually send periodic keepalives and are not affected. Raise the timeout if you hold connections open across long pauses; lower it to reclaim connection slots faster. + +{% multicode %} +```server-nodejs +import { Client, Postgresql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.update({ + databaseId: '<DATABASE_ID>', + networkIdleTimeoutSeconds: 900, +}); +``` +```server-deno +import { Client, Postgresql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.update({ + databaseId: '<DATABASE_ID>', + networkIdleTimeoutSeconds: 900, +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Postgresql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$postgresql = new Postgresql($client); + +$postgresql->update( + databaseId: '<DATABASE_ID>', + networkIdleTimeoutSeconds: 900, +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.postgresql import Postgresql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +postgresql = Postgresql(client) + +postgresql.update( + database_id='<DATABASE_ID>', + network_idle_timeout_seconds=900, +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +postgresql = Postgresql.new(client) + +postgresql.update( + database_id: '<DATABASE_ID>', + network_idle_timeout_seconds: 900, +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Postgresql postgresql = new Postgresql(client); + +await postgresql.Update( + databaseId: "<DATABASE_ID>", + networkIdleTimeoutSeconds: 900 +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Postgresql postgresql = Postgresql(client); + +await postgresql.update( + databaseId: '<DATABASE_ID>', + networkIdleTimeoutSeconds: 900, +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Postgresql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val postgresql = Postgresql(client) + +postgresql.update( + databaseId = "<DATABASE_ID>", + networkIdleTimeoutSeconds = 900, +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let postgresql = Postgresql(client) + +_ = try await postgresql.update( + databaseId: "<DATABASE_ID>", + networkIdleTimeoutSeconds: 900 +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/postgresql" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewPostgresql(client) + + _, err := service.Update( + "<DATABASE_ID>", + postgresql.WithUpdateNetworkIdleTimeoutSeconds(900), + ) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::postgresql::Postgresql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let postgresql = Postgresql::new(&client); + + postgresql.update("<DATABASE_ID>", None, None, None, None, None, Some(900), None, None, None, None, None, None, None, None, None, None, None, None, None, None).await?; + + Ok(()) +} +``` +```bash +curl -X PATCH \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + -H "Content-Type: application/json" \ + -d '{ + "networkIdleTimeoutSeconds": 900 + }' \ + https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID> +``` +{% /multicode %} + +The same setting is available in the Console under **Settings** > **Network**. + +# Locking down access {% #checklist %} + +For a production database: + +1. Set an IP allowlist covering only your application's egress addresses. +2. Rotate the [primary password](/docs/products/databases/postgresql/connections#rotate) on a schedule, and after anyone with access leaves your team. +3. Watch the [Connections tab](/docs/products/databases/postgresql/monitoring#connections) for unexpected clients. diff --git a/src/routes/docs/products/databases/postgresql/quick-start/+page.markdoc b/src/routes/docs/products/databases/postgresql/quick-start/+page.markdoc new file mode 100644 index 00000000000..0fb0c48570f --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/quick-start/+page.markdoc @@ -0,0 +1,96 @@ +--- +layout: article +title: Quick start +description: Create your first native PostgreSQL database in the Appwrite Console, run your first queries in the SQL editor, and connect with psql. +--- + +You can create a PostgreSQL database and run your first query in a few minutes. + +# Create a database {% #create %} + +1. In your project, go to **Databases**. +2. Click **Create database**. +3. Give your database a name, and optionally a custom database ID. +4. Under **Choose database type**, select **PostgreSQL** from the **Native databases** group. + +{% only_dark %} +![Create database type selection](/images/docs/products/databases/postgresql/dark/create-database-type.avif) +{% /only_dark %} +{% only_light %} +![Create database type selection](/images/docs/products/databases/postgresql/create-database-type.avif) +{% /only_light %} + +5. Under **Specifications**, select your preferred tier. +6. Optionally configure **Read replicas** and **Point-in-time recovery (PITR)**. You can change both later. + +{% only_dark %} +![Database specifications](/images/docs/products/databases/postgresql/dark/create-database-specs.avif) +{% /only_dark %} +{% only_light %} +![Database specifications](/images/docs/products/databases/postgresql/create-database-specs.avif) +{% /only_light %} + +7. Review the database summary and click **Create database**. + +Your database starts provisioning and becomes ready shortly after. You land in the SQL editor once it is created. + +# Run your first queries {% #first-queries %} + +{% only_dark %} +![SQL editor](/images/docs/products/databases/postgresql/dark/sql-editor.avif) +{% /only_dark %} +{% only_light %} +![SQL editor](/images/docs/products/databases/postgresql/sql-editor.avif) +{% /only_light %} + +The **SQL editor** tab gives you an editor with formatting, query explain, and a results panel, without leaving the browser. + +Create a table, insert a row, and read it back: + +```sql +CREATE TABLE books ( + id SERIAL PRIMARY KEY, + title TEXT NOT NULL, + author TEXT NOT NULL +); + +INSERT INTO books (title, author) +VALUES ('The Hitchhiker''s Guide to the Galaxy', 'Douglas Adams'); + +SELECT * FROM books; +``` + +# Get your connection details {% #credentials %} + +To connect from outside the Console: + +1. Open your database and click **Credentials**. +2. The **Details** tab shows the host, port, username, password, and database name. +3. The **DSN**, **.env**, **Prisma**, **Drizzle**, and **psql** tabs have ready-made snippets for each workflow. + +# Connect with psql {% #psql %} + +Copy the command from the **psql** tab of the credentials dialog, or build it from the details: + +```bash +psql -h <host> -p 5432 -U admin -d <database> +``` + +Enter the password when prompted. You can run the same queries from your terminal that you ran in the SQL editor, or connect from your application with any PostgreSQL driver. See [Connections](/docs/products/databases/postgresql/connections) for driver examples. + +# Next steps {% #next-steps %} + +{% cards %} +{% cards_item href="/docs/products/databases/postgresql/connections" title="Connections" %} +Connect from your application with any PostgreSQL driver or ORM. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/extensions" title="Extensions" %} +Install PostgreSQL extensions like PostGIS and pgvector. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/backups" title="Backups" %} +Configure backup policies and point-in-time recovery. +{% /cards_item %} +{% cards_item href="/docs/products/databases/postgresql/monitoring" title="Monitoring" %} +Watch compute, connections, storage, and workload metrics live. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/postgresql/scaling/+page.markdoc b/src/routes/docs/products/databases/postgresql/scaling/+page.markdoc new file mode 100644 index 00000000000..d3a39170fa1 --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/scaling/+page.markdoc @@ -0,0 +1,658 @@ +--- +layout: article +title: Scaling +description: Resize the compute specification of your PostgreSQL database with zero downtime and grow storage automatically as your data grows. +--- + +Native databases scale in two dimensions: the compute specification (CPU, memory, and connection limit) and storage. Both can change after creation, without dump-and-restore migrations. + +# List available specifications {% #specifications %} + +Each database runs against a specification that defines its CPU, memory, included storage, and maximum connections. List the specifications available to your plan: + +{% multicode %} +```server-nodejs +import { Client, Postgresql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +const specifications = await postgresql.listSpecifications({ + +}); +``` +```server-deno +import { Client, Postgresql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +const specifications = await postgresql.listSpecifications({ + +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Postgresql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$postgresql = new Postgresql($client); + +$specifications = $postgresql->listSpecifications( + +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.postgresql import Postgresql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +postgresql = Postgresql(client) + +specifications = postgresql.list_specifications( + +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +postgresql = Postgresql.new(client) + +specifications = postgresql.list_specifications( + +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Postgresql postgresql = new Postgresql(client); + +var specifications = await postgresql.ListSpecifications( + +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Postgresql postgresql = Postgresql(client); + +final specifications = await postgresql.listSpecifications( + +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Postgresql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val postgresql = Postgresql(client) + +val specifications = postgresql.listSpecifications( + +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let postgresql = Postgresql(client) + +let specifications = try await postgresql.listSpecifications( + +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewPostgresql(client) + + result, err := service.ListSpecifications() + if err != nil { + panic(err) + } + _ = result +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::postgresql::Postgresql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let postgresql = Postgresql::new(&client); + + let specifications = postgresql.list_specifications().await?; + + Ok(()) +} +``` +```bash +curl -X GET \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + https://<REGION>.cloud.appwrite.io/v1/postgresql/specifications +``` +{% /multicode %} + +# Change the compute specification {% #resize %} + +{% only_dark %} +![Compute tier settings](/images/docs/products/databases/postgresql/dark/settings-compute-tier.avif) +{% /only_dark %} +{% only_light %} +![Compute tier settings](/images/docs/products/databases/postgresql/settings-compute-tier.avif) +{% /only_light %} + +To resize in the Console, open your database, go to **Settings** > **Compute**, and select the new tier. + +From the API, pass the new specification ID: + +{% multicode %} +```server-nodejs +import { Client, Postgresql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.update({ + databaseId: '<DATABASE_ID>', + specification: '<SPECIFICATION>', +}); +``` +```server-deno +import { Client, Postgresql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.update({ + databaseId: '<DATABASE_ID>', + specification: '<SPECIFICATION>', +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Postgresql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$postgresql = new Postgresql($client); + +$postgresql->update( + databaseId: '<DATABASE_ID>', + specification: '<SPECIFICATION>', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.postgresql import Postgresql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +postgresql = Postgresql(client) + +postgresql.update( + database_id='<DATABASE_ID>', + specification='<SPECIFICATION>', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +postgresql = Postgresql.new(client) + +postgresql.update( + database_id: '<DATABASE_ID>', + specification: '<SPECIFICATION>', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Postgresql postgresql = new Postgresql(client); + +await postgresql.Update( + databaseId: "<DATABASE_ID>", + specification: "<SPECIFICATION>" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Postgresql postgresql = Postgresql(client); + +await postgresql.update( + databaseId: '<DATABASE_ID>', + specification: '<SPECIFICATION>', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Postgresql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val postgresql = Postgresql(client) + +postgresql.update( + databaseId = "<DATABASE_ID>", + specification = "<SPECIFICATION>", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let postgresql = Postgresql(client) + +_ = try await postgresql.update( + databaseId: "<DATABASE_ID>", + specification: "<SPECIFICATION>" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/postgresql" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewPostgresql(client) + + _, err := service.Update( + "<DATABASE_ID>", + postgresql.WithUpdateSpecification("<SPECIFICATION>"), + ) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::postgresql::Postgresql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let postgresql = Postgresql::new(&client); + + postgresql.update("<DATABASE_ID>", None, None, Some("<SPECIFICATION>"), None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None).await?; + + Ok(()) +} +``` +```bash +curl -X PATCH \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + -H "Content-Type: application/json" \ + -d '{ + "specification": "<SPECIFICATION>" + }' \ + https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID> +``` +{% /multicode %} + +Resizes apply with zero downtime through a rolling cutover: a new instance is provisioned on the target specification, data is streamed over, and traffic cuts over once it has caught up. The database status shows `scaling` while the resize is in progress. + +# Storage {% #storage %} + +Each specification includes a storage allowance, and storage beyond the allowance is billed per GB. Storage only grows; you cannot shrink a database's storage after it has expanded. To reclaim a smaller footprint, restore a [backup](/docs/products/databases/postgresql/backups) into a new database. + +# Storage autoscaling {% #autoscaling %} + +With storage autoscaling enabled, Appwrite grows the storage automatically when usage crosses a threshold, so the database never hits a full disk. Configure it under **Settings** > **Storage** in the Console, or through the API: + +{% multicode %} +```server-nodejs +import { Client, Postgresql } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.update({ + databaseId: '<DATABASE_ID>', + storageAutoscaling: true, + storageAutoscalingThresholdPercent: 85, + storageAutoscalingMaxGb: 500, +}); +``` +```server-deno +import { Client, Postgresql } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +const postgresql = new Postgresql(client); + +await postgresql.update({ + databaseId: '<DATABASE_ID>', + storageAutoscaling: true, + storageAutoscalingThresholdPercent: 85, + storageAutoscalingMaxGb: 500, +}); +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\Postgresql; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$postgresql = new Postgresql($client); + +$postgresql->update( + databaseId: '<DATABASE_ID>', + storageAutoscaling: true, + storageAutoscalingThresholdPercent: 85, + storageAutoscalingMaxGb: 500, +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.postgresql import Postgresql + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<YOUR_API_KEY>') + +postgresql = Postgresql(client) + +postgresql.update( + database_id='<DATABASE_ID>', + storage_autoscaling=True, + storage_autoscaling_threshold_percent=85, + storage_autoscaling_max_gb=500, +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +postgresql = Postgresql.new(client) + +postgresql.update( + database_id: '<DATABASE_ID>', + storage_autoscaling: true, + storage_autoscaling_threshold_percent: 85, + storage_autoscaling_max_gb: 500, +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +Postgresql postgresql = new Postgresql(client); + +await postgresql.Update( + databaseId: "<DATABASE_ID>", + storageAutoscaling: true, + storageAutoscalingThresholdPercent: 85, + storageAutoscalingMaxGb: 500 +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +Postgresql postgresql = Postgresql(client); + +await postgresql.update( + databaseId: '<DATABASE_ID>', + storageAutoscaling: true, + storageAutoscalingThresholdPercent: 85, + storageAutoscalingMaxGb: 500, +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Postgresql + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +val postgresql = Postgresql(client) + +postgresql.update( + databaseId = "<DATABASE_ID>", + storageAutoscaling = true, + storageAutoscalingThresholdPercent = 85, + storageAutoscalingMaxGb = 500, +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let postgresql = Postgresql(client) + +_ = try await postgresql.update( + databaseId: "<DATABASE_ID>", + storageAutoscaling: true, + storageAutoscalingThresholdPercent: 85, + storageAutoscalingMaxGb: 500 +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/postgresql" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<YOUR_API_KEY>"), + ) + + service := appwrite.NewPostgresql(client) + + _, err := service.Update( + "<DATABASE_ID>", + postgresql.WithUpdateStorageAutoscaling(true), + postgresql.WithUpdateStorageAutoscalingThresholdPercent(85), + postgresql.WithUpdateStorageAutoscalingMaxGb(500), + ) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::client::Client; +use appwrite::services::postgresql::Postgresql; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let postgresql = Postgresql::new(&client); + + postgresql.update("<DATABASE_ID>", None, None, None, None, None, None, None, None, None, None, Some(true), Some(85), Some(500), None, None, None, None, None, None, None).await?; + + Ok(()) +} +``` +```bash +curl -X PATCH \ + -H "X-Appwrite-Project: <PROJECT_ID>" \ + -H "X-Appwrite-Key: <API_KEY>" \ + -H "Content-Type: application/json" \ + -d '{ + "storageAutoscaling": true, + "storageAutoscalingThresholdPercent": 85, + "storageAutoscalingMaxGb": 500 + }' \ + https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID> +``` +{% /multicode %} + +| Parameter | Range | Description | +|---------------------------------------|--------------------|-----------------------------------------------------------| +| `storageAutoscaling` | boolean | Enable automatic storage growth | +| `storageAutoscalingThresholdPercent` | 50 - 95 | Usage percentage that triggers an expansion (default 85) | +| `storageAutoscalingMaxGb` | integer, 0 = no cap | Upper bound for automatic growth | + +Set a cap if you want a hard ceiling on storage cost; without one, autoscaling grows storage as needed and the overage is billed per GB. + +# Picking a specification {% #picking %} + +Guidelines for choosing a starting tier: + +- **Connections**: count the maximum concurrent connections your application opens, including all replicas of your app server. If it exceeds the specification's connection cap, either move up a tier or put the [connection pooler](/docs/products/databases/postgresql/connection-pooling) in front. +- **Memory**: PostgreSQL performs best when the working set fits in memory. Watch the cache hit ratio in the [Monitor tab](/docs/products/databases/postgresql/monitoring); a sustained ratio below ~99% for an OLTP workload is a sign to add memory. +- **CPU**: sustained CPU above 70-80% at normal load leaves no headroom for spikes, migrations, or backups. + +Start small and resize up when the metrics say so; resizes are online, so there is no penalty for growing later. diff --git a/src/routes/docs/products/databases/+layout.svelte b/src/routes/docs/products/databases/tablesdb/+layout.svelte similarity index 70% rename from src/routes/docs/products/databases/+layout.svelte rename to src/routes/docs/products/databases/tablesdb/+layout.svelte index 6c1f5158d47..b4c67d17e54 100644 --- a/src/routes/docs/products/databases/+layout.svelte +++ b/src/routes/docs/products/databases/tablesdb/+layout.svelte @@ -10,7 +10,7 @@ let { children } = $props(); const parent: NavParent = { - href: '/docs', + href: '/docs/products/databases', label: 'Databases' }; @@ -20,11 +20,11 @@ items: [ { label: 'Overview', - href: '/docs/products/databases' + href: '/docs/products/databases/tablesdb' }, { label: 'Quick start', - href: '/docs/products/databases/quick-start' + href: '/docs/products/databases/tablesdb/quick-start' } ] }, @@ -33,45 +33,45 @@ items: [ { label: 'Databases', - href: '/docs/products/databases/databases' + href: '/docs/products/databases/tablesdb/databases' }, { label: 'Tables', - href: '/docs/products/databases/tables' + href: '/docs/products/databases/tablesdb/tables' }, { label: 'Rows', - href: '/docs/products/databases/rows' + href: '/docs/products/databases/tablesdb/rows' }, { label: 'Permissions', - href: '/docs/products/databases/permissions' + href: '/docs/products/databases/tablesdb/permissions' }, { label: 'Relationships', - href: '/docs/products/databases/relationships' + href: '/docs/products/databases/tablesdb/relationships' }, { label: 'Queries', - href: '/docs/products/databases/queries' + href: '/docs/products/databases/tablesdb/queries' }, { label: 'Order', - href: '/docs/products/databases/order' + href: '/docs/products/databases/tablesdb/order' }, { label: 'Operators', - href: '/docs/products/databases/operators', + href: '/docs/products/databases/tablesdb/operators', new: isNewUntil('31 Dec 2025') }, { label: 'Geo queries', - href: '/docs/products/databases/geo-queries', + href: '/docs/products/databases/tablesdb/geo-queries', new: isNewUntil('30 Sep 2025') }, { label: 'Backups', - href: '/docs/products/databases/backups' + href: '/docs/products/databases/tablesdb/backups' } ] }, @@ -80,50 +80,50 @@ items: [ { label: 'Pagination', - href: '/docs/products/databases/pagination' + href: '/docs/products/databases/tablesdb/pagination' }, { label: 'Transactions', - href: '/docs/products/databases/transactions', + href: '/docs/products/databases/tablesdb/transactions', new: isNewUntil('31 Oct 2025') }, { label: 'Type generation', - href: '/docs/products/databases/type-generation', + href: '/docs/products/databases/tablesdb/type-generation', new: isNewUntil('31 Jul 2025') }, { label: 'Offline sync', - href: '/docs/products/databases/offline' + href: '/docs/products/databases/tablesdb/offline' }, { label: 'Bulk operations', - href: '/docs/products/databases/bulk-operations', + href: '/docs/products/databases/tablesdb/bulk-operations', new: isNewUntil('31 Jul 2025') }, { label: 'Atomic numeric operations', - href: '/docs/products/databases/atomic-numeric-operations', + href: '/docs/products/databases/tablesdb/atomic-numeric-operations', new: isNewUntil('31 Jul 2025') }, { label: 'CSV imports', - href: '/docs/products/databases/csv-imports', + href: '/docs/products/databases/tablesdb/csv-imports', new: isNewUntil('31 Jul 2025') }, { label: 'CSV exports', - href: '/docs/products/databases/csv-exports', + href: '/docs/products/databases/tablesdb/csv-exports', new: isNewUntil('28 Feb 2026') }, { label: 'AI suggestions', - href: '/docs/products/databases/ai-suggestions', + href: '/docs/products/databases/tablesdb/ai-suggestions', new: isNewUntil('31 Dec 2025') }, { label: 'Timestamp overrides', - href: '/docs/products/databases/timestamp-overrides' + href: '/docs/products/databases/tablesdb/timestamp-overrides' } ] }, @@ -145,16 +145,16 @@ const legacyUrl = $derived( page.url.pathname - .replace('/products/databases', '/products/databases/legacy') - .replace('rows', 'documents') - .replace('tables', 'collections') + .replace('/tablesdb/', '/tablesdb/legacy/') + .replace(/\/rows(\/|$)/, '/documents$1') + .replace(/\/tables(\/|$)/, '/collections$1') ); const hideSubtitleRoutes = ['offline', 'backups', 'csv-imports', 'csv-exports']; const shouldShowSubtitle = $derived( !hideSubtitleRoutes.some((segment) => page.route.id?.includes(segment)) && - !page.url.pathname.endsWith('products/databases') + !page.url.pathname.endsWith('products/databases/tablesdb') ); const headerSectionInfoAlert = writable<HeaderSectionInfoAlert | null>(null); diff --git a/src/routes/docs/products/databases/+page.markdoc b/src/routes/docs/products/databases/tablesdb/+page.markdoc similarity index 70% rename from src/routes/docs/products/databases/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/+page.markdoc index 9217bde1e68..e9f01089844 100644 --- a/src/routes/docs/products/databases/+page.markdoc +++ b/src/routes/docs/products/databases/tablesdb/+page.markdoc @@ -1,7 +1,7 @@ --- layout: article -title: Databases -description: Store and query structured data with Appwrite Databases. Databases provide performant and scalable storage for your application, business, and user data. +title: TablesDB +description: Store and query structured data with Appwrite TablesDB. Tables provide performant and scalable storage for your application, business, and user data. --- Appwrite Databases let you store and query structured data. @@ -14,6 +14,6 @@ Databases store data, if you need to store files like images, PDFs or videos, us You can organize data into databases, tables, and rows. You can also paginate, order, and query rows. For complex business logic, Appwrite supports relationships to help you model your data. -{% arrow_link href="/docs/products/databases/quick-start" %} +{% arrow_link href="/docs/products/databases/tablesdb/quick-start" %} Quick start {% /arrow_link %} \ No newline at end of file diff --git a/src/routes/docs/products/databases/ai-suggestions/+page.markdoc b/src/routes/docs/products/databases/tablesdb/ai-suggestions/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/ai-suggestions/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/ai-suggestions/+page.markdoc diff --git a/src/routes/docs/products/databases/atomic-numeric-operations/+page.markdoc b/src/routes/docs/products/databases/tablesdb/atomic-numeric-operations/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/atomic-numeric-operations/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/atomic-numeric-operations/+page.markdoc diff --git a/src/routes/docs/products/databases/backups/+page.markdoc b/src/routes/docs/products/databases/tablesdb/backups/+page.markdoc similarity index 87% rename from src/routes/docs/products/databases/backups/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/backups/+page.markdoc index 202133529f6..6dca288f054 100644 --- a/src/routes/docs/products/databases/backups/+page.markdoc +++ b/src/routes/docs/products/databases/tablesdb/backups/+page.markdoc @@ -8,14 +8,14 @@ Appwrite Backups enable seamless, **encrypted** database backups on Cloud. All backups are **hot** backups, ensuring zero downtime and fast recovery. Learn how to efficiently back up your databases to ensure data security and smooth recovery. -{% info title="Backups are available on Appwrite Cloud for all Pro, Scale, and Enterprise customers." %} +{% info title="Backups are available on Appwrite Cloud for all Pro and Enterprise customers." %} {% /info %} Appwrite Backups allow you to automate database backups using backup policies, supporting pre-defined, custom retention & other options. You can also create manual backups whenever necessary. # Backup policies {% #backup-policies %} -Backup policies allow you to automate your backup process. The Scale and Enterprise plans allow for more customization and offer options like how often backups should occur, how long they should be retained, and when they should run. +Backup policies allow you to automate your backup process. The Enterprise plan allows for more customization and offers options like how often backups should occur, how long they should be retained, and when they should run. ## Creating a backup policy {% #creating-backup-policy %} @@ -40,21 +40,21 @@ To automate your database backups, you need to create backup policies that run a ![Pro plan policy](/images/docs/databases/pro-policy.avif) {% /only_light %} - * On **Scale** and **Enterprise** plans, you get access to more & custom policies\ + * On the **Enterprise** plan, you get access to more & custom policies\   * Select a pre-defined policy {% only_dark %} - ![Scale plan policies](/images/docs/databases/dark/scale-policies.avif) + ![Backup policies](/images/docs/databases/dark/scale-policies.avif) {% /only_dark %} {% only_light %} - ![Scale plan policies](/images/docs/databases/scale-policies.avif) + ![Backup policies](/images/docs/databases/scale-policies.avif) {% /only_light %} * Or create a custom policy and adjust the settings as you like {% only_dark %} - ![Custom policies for Scale plan](/images/docs/databases/dark/scale-custom-policies.avif) + ![Custom backup policies](/images/docs/databases/dark/scale-custom-policies.avif) {% /only_dark %} {% only_light %} - ![Custom policies for Scale plan](/images/docs/databases/scale-custom-policies.avif) + ![Custom backup policies](/images/docs/databases/scale-custom-policies.avif) {% /only_light %} 4. Click on **Create** diff --git a/src/routes/docs/products/databases/bulk-operations/+page.markdoc b/src/routes/docs/products/databases/tablesdb/bulk-operations/+page.markdoc similarity index 96% rename from src/routes/docs/products/databases/bulk-operations/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/bulk-operations/+page.markdoc index 07a93db88c0..d1574894303 100644 --- a/src/routes/docs/products/databases/bulk-operations/+page.markdoc +++ b/src/routes/docs/products/databases/tablesdb/bulk-operations/+page.markdoc @@ -56,10 +56,10 @@ const client = new sdk.Client() const tablesDB = new sdk.TablesDB(client); -const result = await tablesDB.createRows( - '<DATABASE_ID>', - '<TABLE_ID>', - [ +const result = await tablesDB.createRows({ + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + rows: [ { $id: sdk.ID.unique(), name: 'Row 1' @@ -69,7 +69,7 @@ const result = await tablesDB.createRows( name: 'Row 2' } ] -); +}); ``` ```server-python @@ -155,16 +155,16 @@ const client = new sdk.Client() const tablesDB = new sdk.TablesDB(client); -const result = await tablesDB.updateRows( - '<DATABASE_ID>', - '<TABLE_ID>', - { +const result = await tablesDB.updateRows({ + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + data: { status: 'published' }, - [ + queries: [ sdk.Query.equal('status', 'draft') ] -); +}); ``` ```server-python @@ -242,10 +242,10 @@ const client = new sdk.Client() const tablesDB = new sdk.TablesDB(client); -const result = await tablesDB.upsertRows( - '<DATABASE_ID>', - '<TABLE_ID>', - [ +const result = await tablesDB.upsertRows({ + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + rows: [ { $id: sdk.ID.unique(), name: 'New Row 1' @@ -255,7 +255,7 @@ const result = await tablesDB.upsertRows( name: 'New Row 2' } ] -); +}); ``` ```server-python @@ -342,13 +342,13 @@ const client = new sdk.Client() const tablesDB = new sdk.TablesDB(client); -const result = await tablesDB.deleteRows( - '<DATABASE_ID>', - '<TABLE_ID>', - [ +const result = await tablesDB.deleteRows({ + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + queries: [ sdk.Query.equal('status', 'archived') ] -); +}); ``` ```server-python diff --git a/src/routes/docs/products/databases/csv-exports/+page.markdoc b/src/routes/docs/products/databases/tablesdb/csv-exports/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/csv-exports/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/csv-exports/+page.markdoc diff --git a/src/routes/docs/products/databases/csv-imports/+page.markdoc b/src/routes/docs/products/databases/tablesdb/csv-imports/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/csv-imports/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/csv-imports/+page.markdoc diff --git a/src/routes/docs/products/databases/databases/+page.markdoc b/src/routes/docs/products/databases/tablesdb/databases/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/databases/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/databases/+page.markdoc diff --git a/src/routes/docs/products/databases/geo-queries/+page.markdoc b/src/routes/docs/products/databases/tablesdb/geo-queries/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/geo-queries/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/geo-queries/+page.markdoc diff --git a/src/routes/docs/products/databases/legacy/+layout.svelte b/src/routes/docs/products/databases/tablesdb/legacy/+layout.svelte similarity index 100% rename from src/routes/docs/products/databases/legacy/+layout.svelte rename to src/routes/docs/products/databases/tablesdb/legacy/+layout.svelte diff --git a/src/routes/docs/products/databases/legacy/+page.ts b/src/routes/docs/products/databases/tablesdb/legacy/+page.ts similarity index 100% rename from src/routes/docs/products/databases/legacy/+page.ts rename to src/routes/docs/products/databases/tablesdb/legacy/+page.ts diff --git a/src/routes/docs/products/databases/legacy/atomic-numeric-operations/+page.markdoc b/src/routes/docs/products/databases/tablesdb/legacy/atomic-numeric-operations/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/legacy/atomic-numeric-operations/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/legacy/atomic-numeric-operations/+page.markdoc diff --git a/src/routes/docs/products/databases/legacy/bulk-operations/+page.markdoc b/src/routes/docs/products/databases/tablesdb/legacy/bulk-operations/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/legacy/bulk-operations/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/legacy/bulk-operations/+page.markdoc diff --git a/src/routes/docs/products/databases/legacy/collections/+page.markdoc b/src/routes/docs/products/databases/tablesdb/legacy/collections/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/legacy/collections/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/legacy/collections/+page.markdoc diff --git a/src/routes/docs/products/databases/legacy/databases/+page.markdoc b/src/routes/docs/products/databases/tablesdb/legacy/databases/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/legacy/databases/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/legacy/databases/+page.markdoc diff --git a/src/routes/docs/products/databases/legacy/documents/+page.markdoc b/src/routes/docs/products/databases/tablesdb/legacy/documents/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/legacy/documents/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/legacy/documents/+page.markdoc diff --git a/src/routes/docs/products/databases/legacy/order/+page.markdoc b/src/routes/docs/products/databases/tablesdb/legacy/order/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/legacy/order/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/legacy/order/+page.markdoc diff --git a/src/routes/docs/products/databases/legacy/pagination/+page.markdoc b/src/routes/docs/products/databases/tablesdb/legacy/pagination/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/legacy/pagination/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/legacy/pagination/+page.markdoc diff --git a/src/routes/docs/products/databases/legacy/permissions/+page.markdoc b/src/routes/docs/products/databases/tablesdb/legacy/permissions/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/legacy/permissions/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/legacy/permissions/+page.markdoc diff --git a/src/routes/docs/products/databases/legacy/queries/+page.markdoc b/src/routes/docs/products/databases/tablesdb/legacy/queries/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/legacy/queries/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/legacy/queries/+page.markdoc diff --git a/src/routes/docs/products/databases/legacy/quick-start/+page.markdoc b/src/routes/docs/products/databases/tablesdb/legacy/quick-start/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/legacy/quick-start/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/legacy/quick-start/+page.markdoc diff --git a/src/routes/docs/products/databases/legacy/relationships/+page.markdoc b/src/routes/docs/products/databases/tablesdb/legacy/relationships/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/legacy/relationships/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/legacy/relationships/+page.markdoc diff --git a/src/routes/docs/products/databases/legacy/type-generation/+page.markdoc b/src/routes/docs/products/databases/tablesdb/legacy/type-generation/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/legacy/type-generation/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/legacy/type-generation/+page.markdoc diff --git a/src/routes/docs/products/databases/offline/+page.markdoc b/src/routes/docs/products/databases/tablesdb/offline/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/offline/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/offline/+page.markdoc diff --git a/src/routes/docs/products/databases/operators/+page.markdoc b/src/routes/docs/products/databases/tablesdb/operators/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/operators/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/operators/+page.markdoc diff --git a/src/routes/docs/products/databases/order/+page.markdoc b/src/routes/docs/products/databases/tablesdb/order/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/order/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/order/+page.markdoc diff --git a/src/routes/docs/products/databases/pagination/+page.markdoc b/src/routes/docs/products/databases/tablesdb/pagination/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/pagination/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/pagination/+page.markdoc diff --git a/src/routes/docs/products/databases/permissions/+page.markdoc b/src/routes/docs/products/databases/tablesdb/permissions/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/permissions/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/permissions/+page.markdoc diff --git a/src/routes/docs/products/databases/queries/+page.markdoc b/src/routes/docs/products/databases/tablesdb/queries/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/queries/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/queries/+page.markdoc diff --git a/src/routes/docs/products/databases/quick-start/+page.markdoc b/src/routes/docs/products/databases/tablesdb/quick-start/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/quick-start/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/quick-start/+page.markdoc diff --git a/src/routes/docs/products/databases/relationships/+page.markdoc b/src/routes/docs/products/databases/tablesdb/relationships/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/relationships/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/relationships/+page.markdoc diff --git a/src/routes/docs/products/databases/rows/+page.markdoc b/src/routes/docs/products/databases/tablesdb/rows/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/rows/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/rows/+page.markdoc diff --git a/src/routes/docs/products/databases/tables/+page.markdoc b/src/routes/docs/products/databases/tablesdb/tables/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/tables/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/tables/+page.markdoc diff --git a/src/routes/docs/products/databases/timestamp-overrides/+page.markdoc b/src/routes/docs/products/databases/tablesdb/timestamp-overrides/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/timestamp-overrides/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/timestamp-overrides/+page.markdoc diff --git a/src/routes/docs/products/databases/transactions/+page.markdoc b/src/routes/docs/products/databases/tablesdb/transactions/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/transactions/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/transactions/+page.markdoc diff --git a/src/routes/docs/products/databases/type-generation/+page.markdoc b/src/routes/docs/products/databases/tablesdb/type-generation/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/type-generation/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/type-generation/+page.markdoc diff --git a/src/routes/docs/products/databases/vectorsdb/+layout.svelte b/src/routes/docs/products/databases/vectorsdb/+layout.svelte new file mode 100644 index 00000000000..174edc4ed4e --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/+layout.svelte @@ -0,0 +1,103 @@ +<script lang="ts"> + import Docs from '$lib/layouts/Docs.svelte'; + import Sidebar, { type NavParent, type NavTree } from '$lib/layouts/Sidebar.svelte'; + + let { children } = $props(); + + const parent: NavParent = { + href: '/docs/products/databases', + label: 'VectorsDB' + }; + + const navigation: NavTree = [ + { + label: 'Getting started', + items: [ + { + label: 'Overview', + href: '/docs/products/databases/vectorsdb' + }, + { + label: 'Quick start', + href: '/docs/products/databases/vectorsdb/quick-start' + } + ] + }, + { + label: 'Concepts', + items: [ + { + label: 'Databases', + href: '/docs/products/databases/vectorsdb/databases' + }, + { + label: 'Collections', + href: '/docs/products/databases/vectorsdb/collections' + }, + { + label: 'Documents', + href: '/docs/products/databases/vectorsdb/documents' + }, + { + label: 'Embeddings', + href: '/docs/products/databases/vectorsdb/embeddings' + }, + { + label: 'Permissions', + href: '/docs/products/databases/vectorsdb/permissions' + }, + { + label: 'Queries', + href: '/docs/products/databases/vectorsdb/queries' + }, + { + label: 'Order', + href: '/docs/products/databases/vectorsdb/order' + }, + { + label: 'Backups', + href: '/docs/products/databases/vectorsdb/backups' + } + ] + }, + { + label: 'Journeys', + items: [ + { + label: 'Pagination', + href: '/docs/products/databases/vectorsdb/pagination' + }, + { + label: 'Vector search', + href: '/docs/products/databases/vectorsdb/vector-search' + }, + { + label: 'Transactions', + href: '/docs/products/databases/vectorsdb/transactions' + }, + { + label: 'Bulk operations', + href: '/docs/products/databases/vectorsdb/bulk-operations' + }, + { + label: 'Timestamp overrides', + href: '/docs/products/databases/vectorsdb/timestamp-overrides' + }, + { + label: 'CSV imports', + href: '/docs/products/databases/vectorsdb/csv-imports' + }, + { + label: 'CSV exports', + href: '/docs/products/databases/vectorsdb/csv-exports' + } + ] + } + ]; +</script> + +<Docs variant="two-side-navs"> + <Sidebar {navigation} {parent} /> + + {@render children()} +</Docs> diff --git a/src/routes/docs/products/databases/vectorsdb/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/+page.markdoc new file mode 100644 index 00000000000..dcc7f83401f --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/+page.markdoc @@ -0,0 +1,29 @@ +--- +layout: article +title: VectorsDB +description: Store vector embeddings and run similarity search with Appwrite VectorsDB to power semantic search, recommendations, and other AI features. +--- + +Appwrite VectorsDB lets you store vector embeddings and run similarity search over them. +A collection is created with a fixed `dimension`, every document holds an `embeddings` vector of that length plus optional `metadata`, and an HNSW index keeps similarity search fast as your data grows. + +{% info title="Looking for file storage?" %} +Databases store data, if you need to store files like images, PDFs or videos, use [Appwrite Storage](/docs/products/storage). +{% /info %} + +You organize data into databases, collections, and documents, the same way you do across Appwrite Databases. What sets VectorsDB apart is the fixed schema built for vectors and the ability to generate text embeddings and search by similarity. + +# Key concepts {% #key-concepts %} +[Collections](/docs/products/databases/vectorsdb/collections) are created with a required `dimension`, the length of the vectors they hold. Instead of a typed-attribute schema, every collection is provisioned with a fixed shape: a required `embeddings` vector and an optional `metadata` object. + +[Documents](/docs/products/databases/vectorsdb/documents) store a single embedding under `embeddings` and any associated JSON under `metadata`. The embedding length must match the collection's `dimension`. + +[Embeddings](/docs/products/databases/vectorsdb/embeddings) can be generated from text with built-in models, so you can turn strings into vectors and store them without running a separate embedding service. + +[Vector search](/docs/products/databases/vectorsdb/vector-search) ranks documents by how close their `embeddings` are to a query vector. You create an HNSW index on the `embeddings` field, then pass a vector query to list documents by cosine, dot product, or Euclidean distance. + +VectorsDB also shares the [permissions](/docs/products/databases/vectorsdb/permissions), [queries](/docs/products/databases/vectorsdb/queries), [pagination](/docs/products/databases/vectorsdb/pagination), [transactions](/docs/products/databases/vectorsdb/transactions), and [bulk operations](/docs/products/databases/vectorsdb/bulk-operations) used across Appwrite Databases. + +{% arrow_link href="/docs/products/databases/vectorsdb/quick-start" %} +Quick start +{% /arrow_link %} diff --git a/src/routes/docs/products/databases/vectorsdb/backups/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/backups/+page.markdoc new file mode 100644 index 00000000000..745a382dc09 --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/backups/+page.markdoc @@ -0,0 +1,89 @@ +--- +layout: article +title: Backups +description: Learn how to back up and restore your VectorsDB databases, ensuring data security and seamless recovery. +--- + +Appwrite Backups enable seamless, **encrypted** database backups. +All backups are **hot** backups, ensuring zero downtime and fast recovery. + +{% info title="Backups are available for all Pro and Enterprise customers." %} +{% /info %} + +You manage backups from a database's **Backups** tab, where you can automate backups with policies or create manual backups on demand. A backup captures the database along with its collections, documents, and embeddings. + +{% only_dark %} +![Backups tab](/images/docs/databases/documentsdb/dark/backups-tab.avif) +{% /only_dark %} +{% only_light %} +![Backups tab](/images/docs/databases/documentsdb/backups-tab.avif) +{% /only_light %} + +# Backup policies {% #backup-policies %} + +Backup policies automate your backups on a schedule. To create one, open your database's **Backups** tab and click **Create policy**, then choose a preset policy or add a custom one. + +{% only_dark %} +![Create backup policy](/images/docs/databases/documentsdb/dark/backup-policy.avif) +{% /only_dark %} +{% only_light %} +![Create backup policy](/images/docs/databases/documentsdb/backup-policy.avif) +{% /only_light %} + +The available options depend on your plan: + +- On the **Pro** plan, you get a **Daily** backup policy retained for 7 days. +- On the **Enterprise** plan, you get access to additional preset policies and custom policies, where you control how often backups run and how long they are retained. + +Click **Create** to save the policy. Your database is now set up for automated backups. + +# Manual backups {% #manual-backups %} + +You can create an on-demand backup whenever necessary. In your database's **Backups** tab, click **Manual backup**, then click **Create**. + +{% only_dark %} +![Manual backup](/images/docs/databases/documentsdb/dark/manual-backup.avif) +{% /only_dark %} +{% only_light %} +![Manual backup](/images/docs/databases/documentsdb/manual-backup.avif) +{% /only_light %} + +Manual backups are retained until you delete them. Depending on the size of your database, the backup may take some time to complete. You can monitor its progress in the backups list. + +# Restoring backups {% #restoring-backups %} + +To restore a database, you need an existing backup. + +1. Open your database's **Backups** tab. +2. In the backups list, open the **Actions** menu for the backup you want to restore. +3. Click **Restore**. +4. Enter a name for the new database and an optional database ID. +5. Click **Restore**. + +Depending on the size of your database, the restoration may take some time. The restore creates a new database from the backup, leaving the original untouched. + +# Backup security & performance {% #backup-security-and-performance %} + +All backups created with Appwrite are: + +1. **Encrypted**: + All backups are securely encrypted to ensure your data remains protected at all times. + +2. **Remotely stored**: + Backups are stored in a remote location, providing an additional layer of security and ensuring your data is always recoverable. + +3. **Hot backups**: + Backups are hot, meaning they occur with zero downtime, allowing you to recover data quickly without interrupting your projects and services. + +# Best practices {% #best-practices %} + +To ensure your backups are robust and effective, consider the following best practices: + +1. **Schedule regular backups**: + Add backup policies based on the frequency of database changes. Daily backups are often sufficient for most use cases. + +2. **Retain critical backups longer**: + Use custom policies with longer retention to keep backups of critical data for extended periods, ensuring historical records are available when needed. + +3. **Optimize backup policies based on data sensitivity**: + Tailor your backup frequency and retention settings according to the sensitivity and importance of the data. diff --git a/src/routes/docs/products/databases/vectorsdb/bulk-operations/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/bulk-operations/+page.markdoc new file mode 100644 index 00000000000..d7c7e15950e --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/bulk-operations/+page.markdoc @@ -0,0 +1,475 @@ +--- +layout: article +title: Bulk operations +description: Perform bulk operations on documents within your collections for efficient data handling in Appwrite VectorsDB. +--- + +Appwrite VectorsDB supports bulk operations for documents, allowing you to create, update, or delete multiple documents in a single request. This can significantly improve performance for apps as it allows you to reduce the number of API calls needed while working with large data sets. + +Bulk operations can only be performed via the server-side SDKs. The client-side SDKs do not support bulk operations by design to prevent abuse and protect against unexpected costs. This ensures that only trusted server environments can perform large-scale data operations. + +For client applications that need bulk-like functionality, consider using [Appwrite Functions](/docs/products/functions) with proper rate limiting and validation. + +Each document's data follows the fixed schema provisioned by its collection: an `embeddings` vector whose length must equal the collection's `dimension`, and an optional free-form `metadata` object. The examples on this page use a collection with `dimension: 4` to keep the arrays readable. + +{% info title="Important notes" %} +Bulk operations trigger Functions, Webhooks, or Realtime events for each document manipulated. Rather than a single event for the entire bulk operation, each document generates a separate event on the existing realtime channels for its operation type. +{% /info %} + +# Atomic behavior {% #atomic-behavior %} + +Bulk operations in Appwrite are **atomic**, meaning they follow an all-or-nothing approach. Either all documents in your bulk request succeed, or all documents fail. + +This atomicity ensures: +- **Data consistency**: Your database remains in a consistent state even if some operations would fail. +- **Race condition prevention**: Multiple clients can safely perform bulk operations simultaneously. +- **Simplified error handling**: You only need to handle complete success or complete failure scenarios. + +For example, if you attempt to create 100 documents and one fails due to a validation error, none of the 100 documents will be created. + +# Plan limits {% #plan-limits %} + +Bulk operations have different limits based on your Appwrite plan: + +| Plan | Documents per request | +|------|----------------------| +| Free | 100 | +| Pro | 1,000 | + +These limits apply to all bulk operations including create, update, upsert, and delete operations. If you need higher limits than what the Pro plan offers, you can [inquire](/contact-us/enterprise) about a custom plan. + +# Create documents {% #create-documents %} + +You can create multiple documents in a single request using the `createDocuments` method. + +{% info title="Custom timestamps" %} +When creating, updating or upserting in bulk, you can set `$createdAt` and `$updatedAt` for each document in the payload. Values must be ISO 8601 date-time strings. If omitted, Appwrite sets them automatically. +{% /info %} + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<API_KEY>'); + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documents: [ + { + $id: sdk.ID.unique(), + embeddings: [0.12, 0.84, 0.33, 0.57], + metadata: { title: 'Hamlet', genre: 'tragedy' } + }, + { + $id: sdk.ID.unique(), + embeddings: [0.91, 0.22, 0.14, 0.65], + metadata: { title: 'Macbeth', genre: 'tragedy' } + } + ] +}); +``` + +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.id import ID + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<API_KEY>') + +vectors_db = VectorsDB(client) + +result = vectors_db.create_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + documents = [ + { + '$id': ID.unique(), + 'embeddings': [0.12, 0.84, 0.33, 0.57], + 'metadata': { 'title': 'Hamlet', 'genre': 'tragedy' } + }, + { + '$id': ID.unique(), + 'embeddings': [0.91, 0.22, 0.14, 0.65], + 'metadata': { 'title': 'Macbeth', 'genre': 'tragedy' } + } + ] +) +``` + +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::id::ID; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); + client.set_project("<PROJECT_ID>"); + client.set_key("<API_KEY>"); + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + vec![ + json!({ + "$id": ID::unique(), + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": { "title": "Hamlet", "genre": "tragedy" } + }), + json!({ + "$id": ID::unique(), + "embeddings": [0.91, 0.22, 0.14, 0.65], + "metadata": { "title": "Macbeth", "genre": "tragedy" } + }), + ], + ).await?; + + Ok(()) +} +``` + +{% /multicode %} + +# Update documents {% #update-documents %} + +{% info title="Permissions required" %} +You must grant **update** permissions to users at the **collection level** before users can update documents. +[Learn more about permissions](/docs/products/databases/vectorsdb/permissions) +{% /info %} + +You can update multiple documents in a single request using the `updateDocuments` method. Pass the fields to change in `data`, and use `queries` to select which documents are affected. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<API_KEY>'); + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.updateDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + data: { + metadata: { genre: 'drama' } + }, + queries: [ + sdk.Query.equal('metadata.genre', 'tragedy') + ] +}); +``` + +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<API_KEY>') + +vectors_db = VectorsDB(client) + +result = vectors_db.update_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + data = { + 'metadata': { 'genre': 'drama' } + }, + queries = [ + Query.equal('metadata.genre', 'tragedy') + ] +) +``` + +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::query::Query; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); + client.set_project("<PROJECT_ID>"); + client.set_key("<API_KEY>"); + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.update_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(json!({ + "metadata": { "genre": "drama" } + })), + Some(vec![ + Query::equal("metadata.genre", "tragedy").to_string(), + ]), + None, + ).await?; + + Ok(()) +} +``` + +{% /multicode %} + +# Upsert documents {% #upsert-documents %} + +{% info title="Permissions required" %} +You must grant **create** and **update** permissions to users at the **collection level** before users can create documents. +[Learn more about permissions](/docs/products/databases/vectorsdb/permissions) +{% /info %} + +You can upsert multiple documents in a single request using the `upsertDocuments` method. Documents with a new `$id` are created, while documents with an existing `$id` are updated. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<API_KEY>'); + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.upsertDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documents: [ + { + $id: sdk.ID.unique(), + embeddings: [0.40, 0.40, 0.40, 0.40], + metadata: { title: 'Othello', genre: 'tragedy' } + }, + { + $id: 'document-id-2', // Existing document ID + embeddings: [0.10, 0.10, 0.10, 0.10], + metadata: { title: 'Hamlet', genre: 'tragedy' } + } + ] +}); +``` + +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.id import ID + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<API_KEY>') + +vectors_db = VectorsDB(client) + +result = vectors_db.upsert_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + documents = [ + { + '$id': ID.unique(), + 'embeddings': [0.40, 0.40, 0.40, 0.40], + 'metadata': { 'title': 'Othello', 'genre': 'tragedy' } + }, + { + '$id': 'document-id-2', # Existing document ID + 'embeddings': [0.10, 0.10, 0.10, 0.10], + 'metadata': { 'title': 'Hamlet', 'genre': 'tragedy' } + } + ] +) +``` + +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::id::ID; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); + client.set_project("<PROJECT_ID>"); + client.set_key("<API_KEY>"); + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.upsert_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + vec![ + json!({ + "$id": ID::unique(), + "embeddings": [0.40, 0.40, 0.40, 0.40], + "metadata": { "title": "Othello", "genre": "tragedy" } + }), + json!({ + "$id": "document-id-2", // Existing document ID + "embeddings": [0.10, 0.10, 0.10, 0.10], + "metadata": { "title": "Hamlet", "genre": "tragedy" } + }), + ], + None, + ).await?; + + Ok(()) +} +``` + +{% /multicode %} + +# Delete documents {% #delete-documents %} + +{% info title="Permissions required" %} +You must grant **delete** permissions to users at the **collection level** before users can delete documents. +[Learn more about permissions](/docs/products/databases/vectorsdb/permissions) +{% /info %} + +You can delete multiple documents in a single request using the `deleteDocuments` method. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<API_KEY>'); + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.deleteDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.equal('metadata.genre', 'drama') + ] +}); +``` + +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<API_KEY>') + +vectors_db = VectorsDB(client) + +result = vectors_db.delete_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + queries = [ + Query.equal('metadata.genre', 'drama') + ] +) +``` + +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::query::Query; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); + client.set_project("<PROJECT_ID>"); + client.set_key("<API_KEY>"); + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.delete_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![ + Query::equal("metadata.genre", "drama").to_string(), + ]), + None, + ).await?; + + Ok(()) +} +``` + +{% /multicode %} + +{% info title="Queries for deletion" %} + +When deleting documents, you must specify queries to filter which documents to delete. +If no queries are provided, all documents in the collection will be deleted. +[Learn more about queries](/docs/products/databases/vectorsdb/queries). + +{% /info %} + +# Use transactions {% #use-transactions %} + +`updateDocuments`, `upsertDocuments`, and `deleteDocuments` accept a `transactionId`. When provided, Appwrite stages the bulk request and applies it on commit. See [Transactions](/docs/products/databases/vectorsdb/transactions). + +{% multicode %} +```server-nodejs +await vectorsDB.upsertDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documents: [ + { $id: sdk.ID.unique(), embeddings: [0.12, 0.84, 0.33, 0.57], metadata: { title: 'One' } }, + { $id: sdk.ID.unique(), embeddings: [0.91, 0.22, 0.14, 0.65], metadata: { title: 'Two' } } + ], + transactionId: '<TRANSACTION_ID>' +}); +``` +```python +vectors_db.upsert_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + documents = [ + { '$id': ID.unique(), 'embeddings': [0.12, 0.84, 0.33, 0.57], 'metadata': { 'title': 'One' } }, + { '$id': ID.unique(), 'embeddings': [0.91, 0.22, 0.14, 0.65], 'metadata': { 'title': 'Two' } } + ], + transaction_id = '<TRANSACTION_ID>' +) +``` +```server-rust +let result = vectors_db.upsert_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + vec![ + json!({ + "$id": ID::unique(), + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": { "title": "One" } + }), + json!({ + "$id": ID::unique(), + "embeddings": [0.91, 0.22, 0.14, 0.65], + "metadata": { "title": "Two" } + }), + ], + Some("<TRANSACTION_ID>"), +).await?; +``` +{% /multicode %} diff --git a/src/routes/docs/products/databases/vectorsdb/collections/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/collections/+page.markdoc new file mode 100644 index 00000000000..bf246bde573 --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/collections/+page.markdoc @@ -0,0 +1,1144 @@ +--- +layout: article +title: Collections +description: Organize embeddings with Appwrite VectorsDB collections. Learn how to create collections with a fixed dimension, manage them, and configure permissions. +--- +Appwrite uses collections as containers of documents. +A VectorsDB collection stores embeddings, so every collection is created with a fixed **`dimension`**, the length of the embedding vectors it holds. All documents in the collection must use vectors of that exact length. + +Unlike TablesDB, you don't define a schema for a VectorsDB collection. The schema is fixed and provisioned for you when the collection is created: + +| Attribute | Type | Description | +|--------------|----------|-----------------------------------------------------------------------------| +| `embeddings` | `vector` | The embedding vector. Required, and sized to the collection's `dimension`. | +| `metadata` | `object` | Arbitrary JSON stored alongside the vector. Optional. | + +Because the schema is fixed, VectorsDB has no typed-attribute endpoints like TablesDB columns. You cannot add, update, or delete attributes on a collection. You store your vector under `embeddings` and any associated data under `metadata`. + +# Create collection {% #create-collection %} +You can create collections using the Appwrite Console, a [Server SDK](/docs/sdks#server), or using the [CLI](/docs/tooling/command-line/installation). + +Head to the **Databases** page, open a [database](/docs/products/databases/vectorsdb/databases), and click **Create collection**. Set a **dimension** that matches the embedding model you plan to use. + +You can also create collections programmatically using a [Server SDK](/docs/sdks#server). Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/platform/api-keys). + +The `dimension` is required and must match the length of the vectors you'll store. For example, the default `nomic-embed-text` model produces 768-dimensional vectors, so you would create the collection with `dimension: 768`. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createCollection({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + name: '<NAME>', + dimension: 768, + permissions: [sdk.Permission.read(sdk.Role.any())], // optional + documentSecurity: false, // optional + enabled: true // optional +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createCollection({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + name: '<NAME>', + dimension: 768, + permissions: [sdk.Permission.read(sdk.Role.any())], // optional + documentSecurity: false, // optional + enabled: true // optional +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Permission; +use Appwrite\Role; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->createCollection( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + name: '<NAME>', + dimension: 768, + permissions: [Permission::read(Role::any())], // optional + documentSecurity: false, // optional + enabled: true // optional +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.permission import Permission +from appwrite.role import Role + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.create_collection( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + name = '<NAME>', + dimension = 768, + permissions = [Permission.read(Role.any())], # optional + document_security = False, # optional + enabled = True # optional +) +``` +```ruby +require 'appwrite' + +include Appwrite +include Appwrite::Permission +include Appwrite::Role + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.create_collection( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + name: '<NAME>', + dimension: 768, + permissions: [Permission.read(Role.any())], # optional + document_security: false, # optional + enabled: true # optional +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Collection result = await vectorsDB.CreateCollection( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + name: "<NAME>", + dimension: 768, + permissions: new List<string> { Permission.Read(Role.Any()) }, // optional + documentSecurity: false, // optional + enabled: true // optional +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; +import 'package:dart_appwrite/permission.dart'; +import 'package:dart_appwrite/role.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Collection result = await vectorsDB.createCollection( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + name: '<NAME>', + dimension: 768, + permissions: [Permission.read(Role.any())], // (optional) + documentSecurity: false, // (optional) + enabled: true, // (optional) +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Permission +import io.appwrite.Role +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.createCollection( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + name = "<NAME>", + dimension = 768, + permissions = listOf(Permission.read(Role.any())), // optional + documentSecurity = false, // optional + enabled = true, // optional +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.createCollection( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<NAME>", + 768, + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let collection = try await vectorsDB.createCollection( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + name: "<NAME>", + dimension: 768, + permissions: [Permission.read(Role.any())], // optional + documentSecurity: false, // optional + enabled: true // optional +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::VectorsDB; +use appwrite::permission::Permission; +use appwrite::role::Role; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create_collection( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<NAME>", + 768, + Some(vec![Permission::read(Role::any()).to_string()]), // permissions (optional) + Some(false), // documentSecurity (optional) + Some(true), // enabled (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db create-collection \ + --database-id "<DATABASE_ID>" \ + --collection-id "<COLLECTION_ID>" \ + --name "<NAME>" \ + --dimension 768 +``` +{% /multicode %} + +{% info title="Dimension is fixed" %} +The `dimension` is set when the collection is created and applies to every document in it. Choose a value that matches your embedding model, between 1 and 16000. To store vectors of a different length, create a separate collection. +{% /info %} + +# Schema {% #schema %} +A VectorsDB collection's schema is provisioned automatically and cannot be changed. Every collection has exactly two attributes: + +- `embeddings`, a required `vector` attribute sized to the collection's `dimension`. +- `metadata`, an optional `object` attribute that holds arbitrary JSON. + +A document's `data` therefore looks like this, where the vector length equals the collection's `dimension`: + +```json +{ + "embeddings": [0.12, 0.84, 0.05, 0.63], + "metadata": { "title": "Introduction", "source": "docs" } +} +``` + +An `object` index on `metadata` is also created for you. There are no endpoints to add, modify, or remove attributes. To learn how to write and read this data, see [Documents](/docs/products/databases/vectorsdb/documents). + +# Manage collections {% #manage-collections %} +Use a [Server SDK](/docs/sdks#server) to list, retrieve, update, and delete collections. + +## List collections {% #list-collections %} +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listCollections({ + databaseId: '<DATABASE_ID>' +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listCollections({ + databaseId: '<DATABASE_ID>' +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->listCollections( + databaseId: '<DATABASE_ID>' +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.list_collections( + database_id = '<DATABASE_ID>' +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.list_collections( + database_id: '<DATABASE_ID>' +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +CollectionList result = await vectorsDB.ListCollections( + databaseId: "<DATABASE_ID>" +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +CollectionList result = await vectorsDB.listCollections( + databaseId: '<DATABASE_ID>', +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.listCollections( + databaseId = "<DATABASE_ID>", +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.listCollections( + "<DATABASE_ID>", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let collectionList = try await vectorsDB.listCollections( + databaseId: "<DATABASE_ID>" +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.list_collections( + "<DATABASE_ID>", + None, // queries (optional) + None, // search (optional) + None, // total (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db list-collections \ + --database-id "<DATABASE_ID>" +``` +{% /multicode %} + +## Get collection {% #get-collection %} +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.getCollection({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>' +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.getCollection({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>' +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->getCollection( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>' +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.get_collection( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>' +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.get_collection( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>' +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Collection result = await vectorsDB.GetCollection( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>" +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Collection result = await vectorsDB.getCollection( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.getCollection( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.getCollection( + "<DATABASE_ID>", + "<COLLECTION_ID>", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let collection = try await vectorsDB.getCollection( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>" +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.get_collection( + "<DATABASE_ID>", + "<COLLECTION_ID>", + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db get-collection \ + --database-id "<DATABASE_ID>" \ + --collection-id "<COLLECTION_ID>" +``` +{% /multicode %} + +## Update collection {% #update-collection %} +You can rename a collection and change its permissions, `documentSecurity`, or `enabled` state. The `name` is required when updating. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.updateCollection({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + name: '<NAME>', + documentSecurity: true // optional +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.updateCollection({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + name: '<NAME>', + documentSecurity: true // optional +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->updateCollection( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + name: '<NAME>', + documentSecurity: true // optional +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.update_collection( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + name = '<NAME>', + document_security = True # optional +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.update_collection( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + name: '<NAME>', + document_security: true # optional +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Collection result = await vectorsDB.UpdateCollection( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + name: "<NAME>", + documentSecurity: true // optional +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Collection result = await vectorsDB.updateCollection( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + name: '<NAME>', + documentSecurity: true, // (optional) +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.updateCollection( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + name = "<NAME>", + documentSecurity = true, // optional +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.updateCollection( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<NAME>", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let collection = try await vectorsDB.updateCollection( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + name: "<NAME>", + documentSecurity: true // optional +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.update_collection( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<NAME>", + None, // dimension (optional) + None, // permissions (optional) + Some(true), // documentSecurity (optional) + None, // enabled (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db update-collection \ + --database-id "<DATABASE_ID>" \ + --collection-id "<COLLECTION_ID>" \ + --name "<NAME>" +``` +{% /multicode %} + +## Delete collection {% #delete-collection %} +Deleting a collection permanently removes it and all of its documents. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.deleteCollection({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>' +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.deleteCollection({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>' +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->deleteCollection( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>' +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.delete_collection( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>' +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.delete_collection( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>' +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +await vectorsDB.DeleteCollection( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>" +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +await vectorsDB.deleteCollection( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +vectorsDB.deleteCollection( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.deleteCollection( + "<DATABASE_ID>", + "<COLLECTION_ID>", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +try await vectorsDB.deleteCollection( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>" +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + vectors_db.delete_collection( + "<DATABASE_ID>", + "<COLLECTION_ID>", + ).await?; + + Ok(()) +} +``` +```bash +appwrite vectors-db delete-collection \ + --database-id "<DATABASE_ID>" \ + --collection-id "<COLLECTION_ID>" +``` +{% /multicode %} + +# Permissions {% #permissions %} +Appwrite uses permissions to control data access. +For security, only users that are granted permissions can access a resource. + +By default, Appwrite doesn't grant permissions to any users when a new collection is created. +This means users can't create documents or read, update, and delete existing documents until you grant access. + +Set `documentSecurity` to `true` on a collection to configure permissions on individual documents. A user then needs either collection level or document level permissions to access a document. + +[Learn about configuring permissions](/docs/products/databases/vectorsdb/permissions). diff --git a/src/routes/docs/products/databases/vectorsdb/csv-exports/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/csv-exports/+page.markdoc new file mode 100644 index 00000000000..c80841bc888 --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/csv-exports/+page.markdoc @@ -0,0 +1,102 @@ +--- +layout: article +title: CSV exports +description: Export VectorsDB documents to a CSV file. Share embeddings and metadata as a portable dataset without writing custom scripts. +--- + +Appwrite's CSV export feature lets you export documents from a VectorsDB collection to a CSV file. This is useful for reporting, sharing a dataset with your team, creating custom backups, or handing embeddings and their metadata off to other tools. + +# Exported columns {% #exported-columns %} + +A VectorsDB collection has a fixed schema, so every export has the same shape. Each row carries the document's system fields together with the two collection attributes: + +| Column | Type | Description | +|--------------|--------|--------------------------------------------------------------------------| +| `$id` | string | The document ID. | +| `embeddings` | JSON | The embedding vector, serialized as a JSON array of numbers. | +| `metadata` | JSON | The metadata stored alongside the vector, serialized as a JSON object. | + +System columns like `$id`, `$createdAt`, and `$updatedAt` are included automatically. Because `embeddings` is the full vector for each document, exported files can be large for high dimension collections. + +An example of exported data, where each `embeddings` value is the full vector for that document: + +```text +$id,embeddings,metadata +doc-1,"[0.12,0.84,0.33,0.57]","{""title"":""Hamlet"",""year"":1601}" +doc-2,"[0.2,0.1,0.6,0.1]","{""title"":""Macbeth"",""year"":1606}" +doc-3,"[0.5,0.5,0.5,0.5]","{""title"":""Othello"",""year"":1603}" +``` + +# Export configuration {% #export-configuration %} + +Before exporting, you can configure several options to control the output format and contents. + +## Apply filters {% #apply-filters %} + +You can pass [queries](/docs/products/databases/vectorsdb/queries) to export only the documents you need rather than the whole collection. This is useful when you want a subset of your data for a specific use case. + +## Select columns {% #select-columns %} + +You can choose which columns to include in the export. By default, all columns are exported. Selecting specific columns creates more focused datasets, for example exporting only `metadata` when you don't need the raw vectors. + +## Custom delimiter {% #custom-delimiter %} + +You can set a custom delimiter for the CSV file. While commas are standard, you can use tabs, semicolons, or other delimiters based on the tools you import into. + +Common delimiters: +- **Comma (`,`)**: Standard format, compatible with most tools +- **Tab**: Useful when your data contains many commas +- **Semicolon (`;`)**: Common in European Excel versions +- **Pipe (`|`)**: Useful when your data contains many semicolons + +## Header row {% #header-row %} + +You can choose whether to include a header row with column names. Headers make the data easier to read in spreadsheets, but some import tools work better without them. + +# Timestamps {% #timestamps %} + +The `$createdAt` and `$updatedAt` columns are exported in ISO 8601 format, making them compatible with most spreadsheet and database tools. + +# Permissions {% #permissions %} + +If [document security](/docs/products/databases/vectorsdb/permissions) is enabled for your collection, the `$permissions` column is included in the export. Permission strings are formatted as comma-separated role definitions within quotes. + +```text +$id,embeddings,$permissions +doc-1,"[0.12,0.84,0.33,0.57]","read(""any""),update(""user:user-123"")" +doc-2,"[0.2,0.1,0.6,0.1]","read(""team:team-456""),update(""team:team-456"")" +``` + +The roles used are API strings that can be found in the [permissions documentation](/docs/products/databases/vectorsdb/permissions). + +# Background processing {% #background-processing %} + +Large exports run as background tasks so they don't block your workflow. When an export completes, you receive an email with a short-lived download link to retrieve your CSV file. + +This means you can start an export, close the Console, and return later to download your file. The Console displays a floating progress bar while the export is active. + +# Use cases {% #use-cases %} + +CSV exports are useful for many common workflows: + +- **Reporting**: Generate reports for stakeholders who need data in spreadsheet format +- **Data sharing**: Share an embeddings dataset with teammates +- **Analytics hand-off**: Provide vectors and metadata to analysts using other tools +- **Custom backups**: Archive specific data subsets for record-keeping +- **Migration preparation**: Extract data to move it into another system + +# Best practices {% #best-practices %} + +To get the most out of CSV exports: + +1. **Filter your data**: Export only the documents you need to reduce file size and processing time +2. **Select specific columns**: Export only `metadata` when you don't need the raw vectors, since high dimension `embeddings` columns make files much larger +3. **Choose appropriate delimiters**: Use tabs or semicolons if your data contains many commas +4. **Consider header requirements**: Include headers for human readability, exclude them for automated imports + +# Additional resources {% #additional-resources %} + +- [CSV imports](/docs/products/databases/vectorsdb/csv-imports) - Import data from CSV files +- [Documents](/docs/products/databases/vectorsdb/documents) - Create, read, update, and delete documents +- [Permissions](/docs/products/databases/vectorsdb/permissions) - Configure document-level security +- [Backups](/docs/products/databases/vectorsdb/backups) - Automated backup policies diff --git a/src/routes/docs/products/databases/vectorsdb/csv-imports/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/csv-imports/+page.markdoc new file mode 100644 index 00000000000..6e94bf6e5d4 --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/csv-imports/+page.markdoc @@ -0,0 +1,118 @@ +--- +layout: article +title: CSV imports +description: Import embeddings into Appwrite VectorsDB by uploading a CSV file. Learn how to format the embeddings and metadata columns for a bulk import. +--- + +Appwrite's CSV Import feature allows you to create multiple documents in a collection by uploading a single CSV file. This is especially useful for loading precomputed embeddings, seeding test environments, or migrating vectors from another system. + +# Prepare your CSV {% #prepare-csv %} + +A VectorsDB collection has a fixed schema, so every CSV maps to the same two columns: + +| Column | Type | Description | +|--------------|----------|----------------------------------------------------------------------------------| +| `embeddings` | `vector` | The embedding vector, written as a JSON array. Required, and its length must equal the collection's `dimension`. | +| `metadata` | `object` | Arbitrary JSON stored alongside the vector, written as a JSON object. Optional. | + +Each row represents a new document. The `embeddings` value is parsed as a JSON array of numbers, and the `metadata` value is parsed as a JSON object. Each row is validated before being imported. + +{% info title="Good to know" %} +You can optionally include the `$id` column to define custom document IDs. If not provided, Appwrite will generate unique IDs for each document automatically. + +Appwrite imports documents in batches of 100 documents at a time. If a provided ID already exists in the collection, the entire batch containing that document will fail, but documents in other batches will continue to be imported successfully. +{% /info %} + +An example of a valid CSV file for a collection created with `dimension: 4`: + +```text +$id,embeddings,metadata +vec_1,"[0.12,0.04,0.88,0.31]","{""title"":""First vector"",""year"":2024}" +vec_2,"[0.55,0.61,0.07,0.42]","{""title"":""Second vector"",""year"":2025}" +vec_3,"[0.20,0.20,0.20,0.20]","{""title"":""Third vector"",""year"":2025}" +``` + +The double quotes around each value let you include the commas inside the JSON array and object. The inner double quotes of the JSON keys and string values are escaped by doubling them (`""`). See [Special characters](#special-characters) for the escaping rules. + +{% info title="Vector length" %} +Every `embeddings` array must contain exactly as many numbers as the collection's `dimension`. A row whose vector length does not match the dimension is rejected. +{% /info %} + +# Metadata values {% #metadata-values %} + +The `metadata` column is an object, so each value must be a valid JSON object or the literal `null`: + +- **A JSON object**, for example `"{""key"":""value""}"`. An empty object `"{}"` is also valid. +- **`null`** (the unquoted literal) stores no metadata for that document. + +A blank `metadata` value is not accepted. To omit metadata for a row, use `null` rather than leaving the field empty: + +```text +$id,embeddings,metadata +vec_1,"[0.12,0.04,0.88,0.31]","{""title"":""With metadata""}" +vec_2,"[0.55,0.61,0.07,0.42]",null +``` + +# Create and update timestamps {% #created-at-and-updated-at %} + +You can also optionally include `$createdAt` and `$updatedAt` columns to set custom timestamps for imported documents. If omitted, Appwrite sets these automatically during import. + +An example of a valid CSV file with `$createdAt` and `$updatedAt` timestamps: + +```text +$id,$createdAt,$updatedAt,embeddings,metadata +vec_1,2025-08-10T12:34:56.000Z,2025-08-10T12:34:56.000Z,"[0.12,0.04,0.88,0.31]","{""title"":""First vector""}" +vec_2,2025-08-11T09:15:00.000Z,2025-08-11T10:00:00.000Z,"[0.55,0.61,0.07,0.42]","{""title"":""Second vector""}" +``` + +{% info title="Timestamps format" %} +`$createdAt` and `$updatedAt` must be valid ISO 8601 date-time strings, for example: `2025-08-10T12:34:56.000Z`. +{% /info %} + +# Permissions {% #permissions %} + +You can set permissions for documents in your CSV file by adding data for the `$permissions` column. Make sure document level security is enabled for your collection. + +An example of a valid permissions string: + +```text +"read(""any""),update(""users""),delete(""user:user_id"")" +``` + +The roles used are API strings that can be found in the [permissions documentation](/docs/apis/rest#roles). + +A full example of a valid CSV file with document permissions: + +```text +$id,embeddings,metadata,$permissions +vec_1,"[0.12,0.04,0.88,0.31]","{""title"":""First vector""}","read(""any""),update(""user:user_id""),delete(""user:user_id"")" +vec_2,"[0.55,0.61,0.07,0.42]","{""title"":""Second vector""}","read(""users""),update(""user:user_id"")" +``` + +# Special characters {% #special-characters %} + +The `embeddings` and `metadata` values are JSON written inside CSV fields, so you need to escape the characters that CSV treats specially. + +## Comma + +The JSON array and object both contain commas, so wrap each value in double quotes (`"[0.1,0.2,0.3,0.4]"`). Without the surrounding quotes, the commas would be read as column separators. + +## Double quotes + +JSON keys and string values use double quotes, and a double quote inside a quoted CSV field must be escaped by doubling it (`""`). For example, the object `{"title":"hello"}` is written in the CSV as `"{""title"":""hello""}"`. + +# Run the import {% #run-import %} + +CSV imports run as a background migration. First upload your CSV to a [storage bucket](/docs/products/storage/buckets), then start the import against the target collection. The import is triggered through the migrations endpoint at `POST /v1/migrations/csv/imports` with an [API key](/docs/advanced/platform/api-keys) that has the `migrations.write` scope. The request takes the storage `bucketId` and `fileId` that hold your CSV, and the target collection as a composite `resourceId` in the form `databaseId:collectionId`. + +The `onDuplicate` option controls what happens when a document with an existing `$id` is encountered: `fail` (the default) aborts on the first conflict, `skip` ignores it, and `overwrite` replaces the existing document. + +{% info title="Console import" %} +The VectorsDB collection view in the Appwrite Console offers JSON import and export. To import a CSV into a VectorsDB collection, use the migrations endpoint described above. +{% /info %} + +# Additional resources {% #additional-resources %} + +- [Appwrite CLI](/docs/command-line) +- [Database permissions](/docs/products/databases/vectorsdb/permissions) +- [Embeddings](/docs/products/databases/vectorsdb/embeddings) diff --git a/src/routes/docs/products/databases/vectorsdb/databases/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/databases/+page.markdoc new file mode 100644 index 00000000000..7802e672768 --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/databases/+page.markdoc @@ -0,0 +1,975 @@ +--- +layout: article +title: Databases +description: Dive deeper into Appwrite VectorsDB and database configuration. Learn how to create and manage multiple vector databases for your application. +--- +Databases are the largest organizational unit in Appwrite. +Each database contains a group of [collections](/docs/products/databases/vectorsdb/collections). + +# Shared and dedicated databases {% #shared-and-dedicated %} +VectorsDB databases run on either shared or dedicated infrastructure. + +Shared databases run on infrastructure that Appwrite manages and scales for you. They are the fastest way to get started and you can create them from the Console or programmatically with a [Server SDK](/docs/sdks#server). + +Dedicated databases run on infrastructure provisioned for your project alone. They can only be created from the Appwrite Console. Once created, you manage their collections and documents with the same SDK methods as a shared database. + +# Create in Console {% #create-in-console %} +The easiest way to create a database is using the Appwrite Console. +Navigate to the **Databases** page and click **Create database**, choose **VectorsDB** as the database type, and select your preferred tier. + +# Create using Server SDKs {% #create-using-server-sdks %} +You can programmatically create a shared database using a [Server SDK](/docs/sdks#server). Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/platform/api-keys). + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.create({ + databaseId: '<DATABASE_ID>', + name: '<NAME>' +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.create({ + databaseId: '<DATABASE_ID>', + name: '<NAME>' +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->create( + databaseId: '<DATABASE_ID>', + name: '<NAME>' +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.create( + database_id = '<DATABASE_ID>', + name = '<NAME>' +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.create( + database_id: '<DATABASE_ID>', + name: '<NAME>' +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Database result = await vectorsDB.Create( + databaseId: "<DATABASE_ID>", + name: "<NAME>" +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Database result = await vectorsDB.create( + databaseId: '<DATABASE_ID>', + name: '<NAME>', +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.create( + databaseId = "<DATABASE_ID>", + name = "<NAME>", +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.create( + "<DATABASE_ID>", + "<NAME>", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let database = try await vectorsDB.create( + databaseId: "<DATABASE_ID>", + name: "<NAME>" +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create( + "<DATABASE_ID>", + "<NAME>", + None, // enabled (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db create \ + --database-id <DATABASE_ID> \ + --name <NAME> +``` +{% /multicode %} + +# List databases {% #list-databases %} +Use the `list` method to retrieve all databases in your project. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.list({}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.list({}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->list(); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.list() +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.list() +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +DatabaseList result = await vectorsDB.List(); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +DatabaseList result = await vectorsDB.list(); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.list() +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.list( + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let databaseList = try await vectorsDB.list() +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.list( + None, // queries (optional) + None, // search (optional) + None, // total (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db list +``` +{% /multicode %} + +# Get a database {% #get-database %} +Use the `get` method to retrieve a single database by its ID. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.get({ + databaseId: '<DATABASE_ID>' +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.get({ + databaseId: '<DATABASE_ID>' +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->get( + databaseId: '<DATABASE_ID>' +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.get( + database_id = '<DATABASE_ID>' +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.get( + database_id: '<DATABASE_ID>' +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Database result = await vectorsDB.Get( + databaseId: "<DATABASE_ID>" +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Database result = await vectorsDB.get( + databaseId: '<DATABASE_ID>', +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.get( + databaseId = "<DATABASE_ID>", +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.get( + "<DATABASE_ID>", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let database = try await vectorsDB.get( + databaseId: "<DATABASE_ID>" +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.get( + "<DATABASE_ID>", + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db get \ + --database-id <DATABASE_ID> +``` +{% /multicode %} + +# Update a database {% #update-database %} +Use the `update` method to change a database's name or enabled state. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.update({ + databaseId: '<DATABASE_ID>', + name: '<NAME>' +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.update({ + databaseId: '<DATABASE_ID>', + name: '<NAME>' +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->update( + databaseId: '<DATABASE_ID>', + name: '<NAME>' +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.update( + database_id = '<DATABASE_ID>', + name = '<NAME>' +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.update( + database_id: '<DATABASE_ID>', + name: '<NAME>' +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Database result = await vectorsDB.Update( + databaseId: "<DATABASE_ID>", + name: "<NAME>" +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Database result = await vectorsDB.update( + databaseId: '<DATABASE_ID>', + name: '<NAME>', +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.update( + databaseId = "<DATABASE_ID>", + name = "<NAME>", +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.update( + "<DATABASE_ID>", + "<NAME>", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let database = try await vectorsDB.update( + databaseId: "<DATABASE_ID>", + name: "<NAME>" +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.update( + "<DATABASE_ID>", + "<NAME>", + None, // enabled (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db update \ + --database-id <DATABASE_ID> \ + --name <NAME> +``` +{% /multicode %} + +# Delete a database {% #delete-database %} +Use the `delete` method to permanently remove a database and all of its collections and documents. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.delete({ + databaseId: '<DATABASE_ID>' +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.delete({ + databaseId: '<DATABASE_ID>' +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->delete( + databaseId: '<DATABASE_ID>' +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.delete( + database_id = '<DATABASE_ID>' +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.delete( + database_id: '<DATABASE_ID>' +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +await vectorsDB.Delete( + databaseId: "<DATABASE_ID>" +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +await vectorsDB.delete( + databaseId: '<DATABASE_ID>', +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +vectorsDB.delete( + databaseId = "<DATABASE_ID>", +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.delete( + "<DATABASE_ID>", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +try await vectorsDB.delete( + databaseId: "<DATABASE_ID>" +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + vectors_db.delete( + "<DATABASE_ID>", + ).await?; + + Ok(()) +} +``` +```bash +appwrite vectors-db delete \ + --database-id <DATABASE_ID> +``` +{% /multicode %} diff --git a/src/routes/docs/products/databases/vectorsdb/documents/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/documents/+page.markdoc new file mode 100644 index 00000000000..ea820edce39 --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/documents/+page.markdoc @@ -0,0 +1,1541 @@ +--- +layout: article +title: Documents +description: Create, read, update, and delete documents in Appwrite VectorsDB. Learn how to store embedding vectors and metadata in your collections. +--- +Each piece of data in Appwrite VectorsDB is a document. +A document's data follows the fixed schema provisioned by its collection: an `embeddings` vector and an optional `metadata` object. + +The `embeddings` array is required, and its length must equal the `dimension` you set when [creating the collection](/docs/products/databases/vectorsdb/collections). +The `metadata` field is free-form JSON, so you can attach any data you want to keep alongside each vector. + +{% info title="Embedding length must match the dimension" %} +If the `embeddings` array is longer or shorter than the collection's `dimension`, the request is rejected. +The examples on this page use a collection with `dimension: 4` to keep the arrays readable. +{% /info %} + +# Create documents {% #create-documents %} +{% info title="Permissions required" %} +You must grant _create_ permissions to users at the _collection level_ before users can create documents. +[Learn more about permissions](#permissions) +{% /info %} + +Use the `createDocument` method to add a document to a collection. Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/platform/api-keys). + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: sdk.ID.unique(), + data: { + embeddings: [0.12, 0.84, 0.33, 0.57], + metadata: { title: 'Hamlet', year: 1601 } + }, + permissions: [sdk.Permission.read(sdk.Role.any())] // optional +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: sdk.ID.unique(), + data: { + embeddings: [0.12, 0.84, 0.33, 0.57], + metadata: { title: 'Hamlet', year: 1601 } + }, + permissions: [sdk.Permission.read(sdk.Role.any())] // optional +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\ID; +use Appwrite\Permission; +use Appwrite\Role; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: ID::unique(), + data: [ + 'embeddings' => [0.12, 0.84, 0.33, 0.57], + 'metadata' => ['title' => 'Hamlet', 'year' => 1601] + ], + permissions: [Permission::read(Role::any())] // optional +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.id import ID +from appwrite.permission import Permission +from appwrite.role import Role + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.create_document( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = ID.unique(), + data = { + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": { "title": "Hamlet", "year": 1601 } + }, + permissions = [Permission.read(Role.any())] # optional +) +``` +```ruby +require 'appwrite' + +include Appwrite +include Appwrite::Permission +include Appwrite::Role + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.create_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: ID.unique(), + data: { + "embeddings" => [0.12, 0.84, 0.33, 0.57], + "metadata" => { "title" => "Hamlet", "year" => 1601 } + }, + permissions: [Permission.read(Role.any())] # optional +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Document result = await vectorsDB.CreateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: ID.Unique(), + data: new Dictionary<string, object> { + { "embeddings", new List<double> { 0.12, 0.84, 0.33, 0.57 } }, + { "metadata", new Dictionary<string, object> { { "title", "Hamlet" }, { "year", 1601 } } } + }, + permissions: new List<string> { Permission.Read(Role.Any()) } // optional +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Document result = await vectorsDB.createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: ID.unique(), + data: { + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": { "title": "Hamlet", "year": 1601 } + }, + permissions: [Permission.read(Role.any())], // (optional) +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.ID +import io.appwrite.Permission +import io.appwrite.Role +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.createDocument( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = ID.unique(), + data = mapOf( + "embeddings" to listOf(0.12, 0.84, 0.33, 0.57), + "metadata" to mapOf("title" to "Hamlet", "year" to 1601) + ), + permissions = listOf(Permission.read(Role.any())), // optional +) +``` +```java +import io.appwrite.Client; +import io.appwrite.ID; +import io.appwrite.Permission; +import io.appwrite.Role; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; +import java.util.Map; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.createDocument( + "<DATABASE_ID>", + "<COLLECTION_ID>", + ID.unique(), + Map.of( + "embeddings", List.of(0.12, 0.84, 0.33, 0.57), + "metadata", Map.of("title", "Hamlet", "year", 1601) + ), + List.of(Permission.read(Role.any())), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let document = try await vectorsDB.createDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: ID.unique(), + data: [ + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": ["title": "Hamlet", "year": 1601] + ], + permissions: [Permission.read(Role.any())] // optional +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::id::ID; +use appwrite::permission::Permission; +use appwrite::role::Role; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + ID::unique(), + json!({ + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": { "title": "Hamlet", "year": 1601 } + }), + Some(vec![Permission::read(Role::any()).to_string()]), // optional + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db create-document \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --document-id 'unique()' \ + --data '{ "embeddings": [0.12, 0.84, 0.33, 0.57], "metadata": { "title": "Hamlet", "year": 1601 } }' +``` +{% /multicode %} + +To insert many documents in a single request, use [bulk operations](/docs/products/databases/vectorsdb/bulk-operations) instead of calling `createDocument` in a loop. + +# Get document {% #get-document %} +Use the `getDocument` method to read a single document by its ID. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.getDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>' +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.getDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>' +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->getDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>' +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.get_document( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = '<DOCUMENT_ID>' +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.get_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: '<DOCUMENT_ID>' +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Document result = await vectorsDB.GetDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>" +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Document result = await vectorsDB.getDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.getDocument( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = "<DOCUMENT_ID>", +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.getDocument( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let document = try await vectorsDB.getDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>" +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.get_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + None, // queries (optional) + None, // transactionId (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db get-document \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --document-id <DOCUMENT_ID> +``` +{% /multicode %} + +# List documents {% #list-documents %} +Use the `listDocuments` method to read documents from a collection. Pass [queries](/docs/products/databases/vectorsdb/queries) to filter, order, and paginate the results. + +To rank documents by similarity to a query vector, use a vector search query instead. See [Vector search](/docs/products/databases/vectorsdb/vector-search). + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.limit(10) + ] +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.limit(10) + ] +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Query; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query::limit(10) + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.list_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + queries = [ + Query.limit(10) + ] +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.list_documents( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + queries: [ + Query.limit(10) + ] +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +DocumentList result = await vectorsDB.ListDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: new List<string> { + Query.Limit(10) + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +DocumentList result = await vectorsDB.listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.limit(10) + ], +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.listDocuments( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + queries = listOf( + Query.limit(10) + ), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.Query; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.listDocuments( + "<DATABASE_ID>", + "<COLLECTION_ID>", + List.of( + Query.limit(10) + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let documentList = try await vectorsDB.listDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: [ + Query.limit(10) + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::query::Query; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.list_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![Query::limit(10).to_string()]), + None, // transactionId (optional) + None, // total (optional) + None, // ttl (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"limit","values":[10]}' +``` +{% /multicode %} + +# Update document {% #update-document %} +Use the `updateDocument` method to update a document. The update is a patch, so you only pass the fields you want to change. To change the vector, pass a new `embeddings` array of the same length as the collection's `dimension`. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.updateDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { + embeddings: [0.20, 0.10, 0.60, 0.10], + metadata: { title: 'Hamlet', year: 1602 } + } +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.updateDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { + embeddings: [0.20, 0.10, 0.60, 0.10], + metadata: { title: 'Hamlet', year: 1602 } + } +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->updateDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: [ + 'embeddings' => [0.20, 0.10, 0.60, 0.10], + 'metadata' => ['title' => 'Hamlet', 'year' => 1602] + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.update_document( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = '<DOCUMENT_ID>', + data = { + "embeddings": [0.20, 0.10, 0.60, 0.10], + "metadata": { "title": "Hamlet", "year": 1602 } + } +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.update_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: '<DOCUMENT_ID>', + data: { + "embeddings" => [0.20, 0.10, 0.60, 0.10], + "metadata" => { "title" => "Hamlet", "year" => 1602 } + } +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Document result = await vectorsDB.UpdateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + data: new Dictionary<string, object> { + { "embeddings", new List<double> { 0.20, 0.10, 0.60, 0.10 } }, + { "metadata", new Dictionary<string, object> { { "title", "Hamlet" }, { "year", 1602 } } } + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Document result = await vectorsDB.updateDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { + "embeddings": [0.20, 0.10, 0.60, 0.10], + "metadata": { "title": "Hamlet", "year": 1602 } + }, +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.updateDocument( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = "<DOCUMENT_ID>", + data = mapOf( + "embeddings" to listOf(0.20, 0.10, 0.60, 0.10), + "metadata" to mapOf("title" to "Hamlet", "year" to 1602) + ), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; +import java.util.Map; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.updateDocument( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + Map.of( + "embeddings", List.of(0.20, 0.10, 0.60, 0.10), + "metadata", Map.of("title", "Hamlet", "year", 1602) + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let document = try await vectorsDB.updateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + data: [ + "embeddings": [0.20, 0.10, 0.60, 0.10], + "metadata": ["title": "Hamlet", "year": 1602] + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.update_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + Some(json!({ + "embeddings": [0.20, 0.10, 0.60, 0.10], + "metadata": { "title": "Hamlet", "year": 1602 } + })), + None, // permissions (optional) + None, // transactionId (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db update-document \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --document-id <DOCUMENT_ID> \ + --data '{ "embeddings": [0.20, 0.10, 0.60, 0.10], "metadata": { "title": "Hamlet", "year": 1602 } }' +``` +{% /multicode %} + +# Upsert document {% #upsert-document %} +Use the `upsertDocument` method to create a document if it doesn't exist, or update it if it does. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.upsertDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { + embeddings: [0.50, 0.50, 0.50, 0.50], + metadata: { title: 'Hamlet', year: 1603 } + } +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.upsertDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { + embeddings: [0.50, 0.50, 0.50, 0.50], + metadata: { title: 'Hamlet', year: 1603 } + } +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->upsertDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: [ + 'embeddings' => [0.50, 0.50, 0.50, 0.50], + 'metadata' => ['title' => 'Hamlet', 'year' => 1603] + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.upsert_document( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = '<DOCUMENT_ID>', + data = { + "embeddings": [0.50, 0.50, 0.50, 0.50], + "metadata": { "title": "Hamlet", "year": 1603 } + } +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.upsert_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: '<DOCUMENT_ID>', + data: { + "embeddings" => [0.50, 0.50, 0.50, 0.50], + "metadata" => { "title" => "Hamlet", "year" => 1603 } + } +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Document result = await vectorsDB.UpsertDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + data: new Dictionary<string, object> { + { "embeddings", new List<double> { 0.50, 0.50, 0.50, 0.50 } }, + { "metadata", new Dictionary<string, object> { { "title", "Hamlet" }, { "year", 1603 } } } + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Document result = await vectorsDB.upsertDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { + "embeddings": [0.50, 0.50, 0.50, 0.50], + "metadata": { "title": "Hamlet", "year": 1603 } + }, +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.upsertDocument( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = "<DOCUMENT_ID>", + data = mapOf( + "embeddings" to listOf(0.50, 0.50, 0.50, 0.50), + "metadata" to mapOf("title" to "Hamlet", "year" to 1603) + ), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; +import java.util.Map; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.upsertDocument( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + Map.of( + "embeddings", List.of(0.50, 0.50, 0.50, 0.50), + "metadata", Map.of("title", "Hamlet", "year", 1603) + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let document = try await vectorsDB.upsertDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + data: [ + "embeddings": [0.50, 0.50, 0.50, 0.50], + "metadata": ["title": "Hamlet", "year": 1603] + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.upsert_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + Some(json!({ + "embeddings": [0.50, 0.50, 0.50, 0.50], + "metadata": { "title": "Hamlet", "year": 1603 } + })), + None, // permissions (optional) + None, // transactionId (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db upsert-document \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --document-id <DOCUMENT_ID> \ + --data '{ "embeddings": [0.50, 0.50, 0.50, 0.50], "metadata": { "title": "Hamlet", "year": 1603 } }' +``` +{% /multicode %} + +# Delete document {% #delete-document %} +Use the `deleteDocument` method to remove a document from a collection. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.deleteDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>' +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.deleteDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>' +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->deleteDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>' +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.delete_document( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = '<DOCUMENT_ID>' +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.delete_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: '<DOCUMENT_ID>' +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +await vectorsDB.DeleteDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>" +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +await vectorsDB.deleteDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +vectorsDB.deleteDocument( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = "<DOCUMENT_ID>", +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.deleteDocument( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println("Deleted"); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +try await vectorsDB.deleteDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>" +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + vectors_db.delete_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + None, // transactionId (optional) + ).await?; + + Ok(()) +} +``` +```bash +appwrite vectors-db delete-document \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --document-id <DOCUMENT_ID> +``` +{% /multicode %} + +# Permissions {% #permissions %} +To access documents through a [Client SDK](/docs/sdks#client), you must grant the relevant permissions. +By default, Appwrite doesn't grant any user permissions when a new collection is created. + +You can configure permissions at the collection level, or enable `documentSecurity` on the collection to also set permissions on individual documents. + +[Learn about configuring permissions](/docs/products/databases/vectorsdb/permissions). + +# Next steps {% #next-steps %} + +Continue learning with these related guides: + +{% cards %} +{% cards_item href="/docs/products/databases/vectorsdb/vector-search" title="Vector search" %} +Rank documents by similarity to a query vector using cosine, dot product, or Euclidean distance. +{% /cards_item %} + +{% cards_item href="/docs/products/databases/vectorsdb/bulk-operations" title="Bulk operations" %} +Create, update, upsert, and delete many documents in a single request. +{% /cards_item %} + +{% cards_item href="/docs/products/databases/vectorsdb/collections" title="Collections" %} +Configure the dimension and indexes that define how your vectors are stored and searched. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/vectorsdb/embeddings/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/embeddings/+page.markdoc new file mode 100644 index 00000000000..5730c4365fc --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/embeddings/+page.markdoc @@ -0,0 +1,612 @@ +--- +layout: article +title: Embeddings +description: Generate text embeddings with Appwrite VectorsDB. Turn text into vector embeddings with built-in models and store them in your documents for vector search. +--- +An embedding is a list of numbers that represents the meaning of a piece of text. +VectorsDB can generate embeddings for you with built-in models, so you can turn text into vectors and store them in a collection without running a separate embedding service. + +The typical flow is two steps: generate an embedding from your text, then store that embedding in a document's `embeddings` field. Once stored, you can run [vector search](/docs/products/databases/vectorsdb/vector-search) over your documents. + +# Generate embeddings {% #generate-embeddings %} +Use the `createTextEmbeddings` method to turn one or more strings into vector embeddings. Pass an array of `texts` and, optionally, a `model`. When you omit `model`, VectorsDB uses the default `nomic-embed-text` model. Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/platform/api-keys). + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createTextEmbeddings({ + texts: ['The quick brown fox jumps over the lazy dog'], + model: sdk.EmbeddingModel.Nomicembedtext // optional, defaults to nomic-embed-text +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createTextEmbeddings({ + texts: ['The quick brown fox jumps over the lazy dog'], + model: sdk.EmbeddingModel.Nomicembedtext // optional, defaults to nomic-embed-text +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Enums\EmbeddingModel; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->createTextEmbeddings( + texts: ['The quick brown fox jumps over the lazy dog'], + model: EmbeddingModel::NOMICEMBEDTEXT() // optional, defaults to nomic-embed-text +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.enums import EmbeddingModel + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.create_text_embeddings( + texts = ['The quick brown fox jumps over the lazy dog'], + model = EmbeddingModel.NOMIC_EMBED_TEXT # optional, defaults to nomic-embed-text +) +``` +```ruby +require 'appwrite' + +include Appwrite +include Appwrite::Enums + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.create_text_embeddings( + texts: ['The quick brown fox jumps over the lazy dog'], + model: EmbeddingModel::NOMIC_EMBED_TEXT # optional, defaults to nomic-embed-text +) +``` +```csharp +using Appwrite; +using Appwrite.Enums; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +EmbeddingList result = await vectorsDB.CreateTextEmbeddings( + texts: new List<string> { "The quick brown fox jumps over the lazy dog" }, + model: EmbeddingModel.NomicEmbedText // optional, defaults to nomic-embed-text +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; +import 'package:dart_appwrite/enums.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +EmbeddingList result = await vectorsDB.createTextEmbeddings( + texts: ['The quick brown fox jumps over the lazy dog'], + model: EmbeddingModel.nomicEmbedText, // optional, defaults to nomic-embed-text +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.enums.EmbeddingModel +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.createTextEmbeddings( + texts = listOf("The quick brown fox jumps over the lazy dog"), + model = EmbeddingModel.NOMIC_EMBED_TEXT, // optional, defaults to nomic-embed-text +) +``` +```java +import io.appwrite.Client; +import io.appwrite.enums.EmbeddingModel; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.createTextEmbeddings( + List.of("The quick brown fox jumps over the lazy dog"), + EmbeddingModel.NOMIC_EMBED_TEXT, // optional, defaults to nomic-embed-text + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite +import AppwriteEnums + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let embeddingList = try await vectorsDB.createTextEmbeddings( + texts: ["The quick brown fox jumps over the lazy dog"], + model: .nomicEmbedText // optional, defaults to nomic-embed-text +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::enums::EmbeddingModel; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create_text_embeddings( + vec!["The quick brown fox jumps over the lazy dog"], + Some(EmbeddingModel::NomicEmbedText), // optional, defaults to nomic-embed-text + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db create-text-embeddings \ + --texts 'The quick brown fox jumps over the lazy dog' \ + --model 'nomic-embed-text' +``` +{% /multicode %} + +The response is an embedding list. The `embeddings` array holds one entry per input text, in the same order you passed them. + +```json +{ + "total": 1, + "embeddings": [ + { + "model": "nomic-embed-text", + "dimension": 768, + "embedding": [-0.012246467, 0.02621112, -0.15247375, ...], + "error": "" + } + ] +} +``` + +Each entry contains: + +| Field | Description | +| --- | --- | +| `model` | The model that generated this embedding. | +| `dimension` | The number of values in the embedding vector. | +| `embedding` | The embedding vector as an array of floats. If generation fails, this is an empty array. | +| `error` | An error message if this text could not be embedded. An empty string means there was no error. | + +# Available models {% #available-models %} +VectorsDB ships with the following text embedding models. The `dimension` of a model is the length of the vector it produces, and it must match the `dimension` you set on the [collection](/docs/products/databases/vectorsdb/collections) where you store the embeddings. + +| Model | Dimension | Notes | +| --- | --- | --- | +| `nomic-embed-text` | 768 | Default model, used when you omit `model`. | +| `embedding-gemma` | 768 | | +| `all-minilm` | 384 | | +| `bge-small` | 384 | | + +{% info title="Match the model to your collection dimension" %} +Pick a model before you create your collection, then set the collection's `dimension` to that model's dimension. All documents in a collection must use vectors of the same dimension, so a single collection works with models of one dimension only. +{% /info %} + +# Store embeddings {% #store-embeddings %} +Once you have an embedding, store it in a document's `embeddings` field. The collection's `dimension` must match the embedding's `dimension`. You can store any related data alongside the vector in the document's `metadata` field. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const text = 'The quick brown fox jumps over the lazy dog'; + +const embeddings = await vectorsDB.createTextEmbeddings({ + texts: [text] +}); + +const result = await vectorsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: sdk.ID.unique(), + data: { + embeddings: embeddings.embeddings[0].embedding, + metadata: { text } + } +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const text = 'The quick brown fox jumps over the lazy dog'; + +const embeddings = await vectorsDB.createTextEmbeddings({ + texts: [text] +}); + +const result = await vectorsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: sdk.ID.unique(), + data: { + embeddings: embeddings.embeddings[0].embedding, + metadata: { text } + } +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\ID; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$text = 'The quick brown fox jumps over the lazy dog'; + +$embeddings = $vectorsDB->createTextEmbeddings( + texts: [$text] +); + +$result = $vectorsDB->createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: ID::unique(), + data: [ + 'embeddings' => $embeddings['embeddings'][0]['embedding'], + 'metadata' => ['text' => $text] + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.id import ID + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +text = 'The quick brown fox jumps over the lazy dog' + +embeddings = vectors_db.create_text_embeddings( + texts = [text] +) + +result = vectors_db.create_document( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = ID.unique(), + data = { + "embeddings": embeddings["embeddings"][0]["embedding"], + "metadata": { "text": text } + } +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +text = 'The quick brown fox jumps over the lazy dog' + +embeddings = vectors_db.create_text_embeddings( + texts: [text] +) + +result = vectors_db.create_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: ID.unique(), + data: { + "embeddings" => embeddings.embeddings[0].embedding, + "metadata" => { "text" => text } + } +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +var text = "The quick brown fox jumps over the lazy dog"; + +EmbeddingList embeddings = await vectorsDB.CreateTextEmbeddings( + texts: new List<string> { text } +); + +Document result = await vectorsDB.CreateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: ID.Unique(), + data: new { + embeddings = embeddings.Embeddings[0].Embedding, + metadata = new { text } + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +final text = 'The quick brown fox jumps over the lazy dog'; + +EmbeddingList embeddings = await vectorsDB.createTextEmbeddings( + texts: [text], +); + +Document result = await vectorsDB.createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: ID.unique(), + data: { + "embeddings": embeddings.embeddings[0].embedding, + "metadata": { "text": text } + }, +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.ID +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val text = "The quick brown fox jumps over the lazy dog" + +val embeddings = vectorsDB.createTextEmbeddings( + texts = listOf(text), +) + +val response = vectorsDB.createDocument( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = ID.unique(), + data = mapOf( + "embeddings" to embeddings.embeddings[0].embedding, + "metadata" to mapOf("text" to text) + ), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.ID; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; +import java.util.Map; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +String text = "The quick brown fox jumps over the lazy dog"; + +vectorsDB.createTextEmbeddings( + List.of(text), + new CoroutineCallback<>((embeddings, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + vectorsDB.createDocument( + "<DATABASE_ID>", + "<COLLECTION_ID>", + ID.unique(), + Map.of( + "embeddings", embeddings.getEmbeddings().get(0).getEmbedding(), + "metadata", Map.of("text", text) + ), + new CoroutineCallback<>((result, err) -> { + if (err != null) { + err.printStackTrace(); + return; + } + + System.out.println(result); + }) + ); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let text = "The quick brown fox jumps over the lazy dog" + +let embeddings = try await vectorsDB.createTextEmbeddings( + texts: [text] +) + +let document = try await vectorsDB.createDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: ID.unique(), + data: [ + "embeddings": embeddings.embeddings[0].embedding, + "metadata": ["text": text] + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::id::ID; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let text = "The quick brown fox jumps over the lazy dog"; + + let embeddings = vectors_db.create_text_embeddings( + vec![text], + None, // model (optional) + ).await?; + + let result = vectors_db.create_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + ID::unique(), + json!({ + "embeddings": embeddings.embeddings[0].embedding, + "metadata": { "text": text } + }), + None, // permissions (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db create-document \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --document-id 'unique()' \ + --data '{ "embeddings": [-0.012246467, 0.02621112, -0.15247375], "metadata": { "text": "The quick brown fox jumps over the lazy dog" } }' +``` +{% /multicode %} + +{% info title="Embed in batches" %} +You can pass several strings in one `createTextEmbeddings` call to embed them together. The response returns one entry per input text, in order, so you can map each embedding back to its source text before storing. +{% /info %} + +# Next steps {% #next-steps %} +With embeddings stored in your documents, you can find the most similar documents to a query vector with vector search. + +{% arrow_link href="/docs/products/databases/vectorsdb/vector-search" %} +Learn about vector search +{% /arrow_link %} diff --git a/src/routes/docs/products/databases/vectorsdb/order/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/order/+page.markdoc new file mode 100644 index 00000000000..160513898fa --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/order/+page.markdoc @@ -0,0 +1,1006 @@ +--- +layout: article +title: Order +description: Order documents returned by Appwrite VectorsDB. Learn how to sort by system fields like $createdAt and $sequence, and why metadata sub-fields can't be ordered. +--- +You can order the documents returned by [listDocuments](/docs/products/databases/vectorsdb/documents#list-documents) using the `Query.orderAsc()` and `Query.orderDesc()` query methods. + +VectorsDB orders on the system fields that Appwrite maintains on every document, such as `$createdAt`, `$updatedAt`, `$sequence`, and `$id`. + +{% info title="Metadata sub-fields can't be ordered" %} +A VectorsDB collection has a [fixed schema](/docs/products/databases/vectorsdb/collections#schema): an `embeddings` vector and a `metadata` object. Because `metadata` is stored as a single JSON object rather than typed columns, you can't order by a value inside it. Ordering by a nested path like `metadata.title` is rejected with `Invalid query: Cannot order by nested attribute: metadata`. Order by the system fields below instead, or keep an orderable value in a system field such as `$createdAt`. +{% /info %} + +# Order by a system field {% #one-field %} +Pass `Query.orderAsc()` or `Query.orderDesc()` with a system field to sort the documents returned. The example below orders documents from newest to oldest by `$createdAt`. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.orderDesc('$createdAt') + ] +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.orderDesc('$createdAt') + ] +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Query; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query::orderDesc('$createdAt') + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.list_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + queries = [ + Query.order_desc('$createdAt') + ] +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.list_documents( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + queries: [ + Query.order_desc('$createdAt') + ] +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +DocumentList result = await vectorsDB.ListDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: new List<string> { + Query.OrderDesc("$createdAt") + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +DocumentList result = await vectorsDB.listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.orderDesc('\$createdAt') + ], +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.listDocuments( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + queries = listOf( + Query.orderDesc("\$createdAt") + ), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.Query; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.listDocuments( + "<DATABASE_ID>", + "<COLLECTION_ID>", + List.of( + Query.orderDesc("$createdAt") + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let documentList = try await vectorsDB.listDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: [ + Query.orderDesc("$createdAt") + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::query::Query; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.list_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![Query::order_desc("$createdAt").to_string()]), + None, // transactionId (optional) + None, // total (optional) + None, // ttl (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"orderDesc","values":["$createdAt"]}' +``` +{% /multicode %} + +# Order by multiple fields {% #multiple-fields %} +To sort by more than one field, pass multiple order queries. They are applied in order, so the first query is the primary sort and each later query breaks ties. + +In the example below, documents are sorted first by `$createdAt` in descending order, then by `$id` in ascending order to break ties. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.orderDesc('$createdAt'), // Order first by creation time, newest first + sdk.Query.orderAsc('$id') // Then break ties by document ID + ] +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.orderDesc('$createdAt'), // Order first by creation time, newest first + sdk.Query.orderAsc('$id') // Then break ties by document ID + ] +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Query; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query::orderDesc('$createdAt'), + Query::orderAsc('$id') + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.list_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + queries = [ + Query.order_desc('$createdAt'), + Query.order_asc('$id') + ] +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.list_documents( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + queries: [ + Query.order_desc('$createdAt'), + Query.order_asc('$id') + ] +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +DocumentList result = await vectorsDB.ListDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: new List<string> { + Query.OrderDesc("$createdAt"), + Query.OrderAsc("$id") + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +DocumentList result = await vectorsDB.listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.orderDesc('\$createdAt'), + Query.orderAsc('\$id') + ], +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.listDocuments( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + queries = listOf( + Query.orderDesc("\$createdAt"), + Query.orderAsc("\$id") + ), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.Query; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.listDocuments( + "<DATABASE_ID>", + "<COLLECTION_ID>", + List.of( + Query.orderDesc("$createdAt"), + Query.orderAsc("$id") + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let documentList = try await vectorsDB.listDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: [ + Query.orderDesc("$createdAt"), + Query.orderAsc("$id") + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::query::Query; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.list_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![ + Query::order_desc("$createdAt").to_string(), + Query::order_asc("$id").to_string(), + ]), + None, // transactionId (optional) + None, // total (optional) + None, // ttl (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"orderDesc","values":["$createdAt"]}' '{"method":"orderAsc","values":["$id"]}' +``` +{% /multicode %} + +# Order by sequence {% #sequence-ordering %} +For ordering based on insertion order, use the `$sequence` field, which Appwrite adds to every document. Sorting by `$sequence` returns documents in the order they were created. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.orderAsc('$sequence') + ] +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.orderAsc('$sequence') + ] +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Query; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query::orderAsc('$sequence') + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.list_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + queries = [ + Query.order_asc('$sequence') + ] +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.list_documents( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + queries: [ + Query.order_asc('$sequence') + ] +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +DocumentList result = await vectorsDB.ListDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: new List<string> { + Query.OrderAsc("$sequence") + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +DocumentList result = await vectorsDB.listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.orderAsc('\$sequence') + ], +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.listDocuments( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + queries = listOf( + Query.orderAsc("\$sequence") + ), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.Query; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.listDocuments( + "<DATABASE_ID>", + "<COLLECTION_ID>", + List.of( + Query.orderAsc("$sequence") + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let documentList = try await vectorsDB.listDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: [ + Query.orderAsc("$sequence") + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::query::Query; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.list_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![Query::order_asc("$sequence").to_string()]), + None, // transactionId (optional) + None, // total (optional) + None, // ttl (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"orderAsc","values":["$sequence"]}' +``` +{% /multicode %} + +The `$sequence` field is useful when you need: +- Consistent ordering for pagination, especially with high-frequency inserts +- Reliable insertion order tracking when timestamps might not be precise enough +- Simple insertion-order sorting without managing custom counter fields + +# Order randomly {% #random-ordering %} +Use `Query.orderRandom()` to return documents in a random order. This is useful for sampling documents or surfacing varied results on each request. It takes no field. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.orderRandom() + ] +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.orderRandom() + ] +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Query; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query::orderRandom() + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.list_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + queries = [ + Query.order_random() + ] +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.list_documents( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + queries: [ + Query.order_random() + ] +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +DocumentList result = await vectorsDB.ListDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: new List<string> { + Query.OrderRandom() + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +DocumentList result = await vectorsDB.listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.orderRandom() + ], +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.listDocuments( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + queries = listOf( + Query.orderRandom() + ), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.Query; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.listDocuments( + "<DATABASE_ID>", + "<COLLECTION_ID>", + List.of( + Query.orderRandom() + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let documentList = try await vectorsDB.listDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: [ + Query.orderRandom() + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::query::Query; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.list_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![Query::order_random().to_string()]), + None, // transactionId (optional) + None, // total (optional) + None, // ttl (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"orderRandom","values":[]}' +``` +{% /multicode %} + +To rank documents by similarity to a query vector instead of ordering by a field, use a vector search query. See [Vector search](/docs/products/databases/vectorsdb/vector-search). diff --git a/src/routes/docs/products/databases/vectorsdb/pagination/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/pagination/+page.markdoc new file mode 100644 index 00000000000..c12e0d4cddc --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/pagination/+page.markdoc @@ -0,0 +1,1041 @@ +--- +layout: article +title: Pagination +description: Implement pagination for large data sets in Appwrite VectorsDB. Explore techniques for splitting and displaying documents across multiple pages. +--- + +As your collection grows in size, you'll need to paginate the documents returned. +Pagination improves performance by returning a subset of documents that match a query at a time, called a page. + +By default, list operations return 25 documents per page, which can be changed using the `Query.limit()` query method. +There is no hard limit on the number of documents you can request. However, beware that **large pages can degrade performance**. + +# Offset pagination {% #offset-pagination %} + +Offset pagination divides documents into pages of `N` documents each. +To read page number `P`, skip `offset = N * (P - 1)` documents, then read the next `N`. + +Using `Query.limit()` and `Query.offset()` you can achieve offset pagination. +With `Query.limit()` you define how many documents can be returned from one request. +The `Query.offset()` is the number of documents you wish to skip before selecting documents. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +// Page 1 +const page1 = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.limit(25), + sdk.Query.offset(0) + ] +}); + +// Page 2 +const page2 = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.limit(25), + sdk.Query.offset(25) + ] +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +// Page 1 +const page1 = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.limit(25), + sdk.Query.offset(0) + ] +}); + +// Page 2 +const page2 = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.limit(25), + sdk.Query.offset(25) + ] +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Query; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +// Page 1 +$page1 = $vectorsDB->listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query::limit(25), + Query::offset(0) + ] +); + +// Page 2 +$page2 = $vectorsDB->listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query::limit(25), + Query::offset(25) + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +# Page 1 +page1 = vectors_db.list_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + queries = [ + Query.limit(25), + Query.offset(0) + ] +) + +# Page 2 +page2 = vectors_db.list_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + queries = [ + Query.limit(25), + Query.offset(25) + ] +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +# Page 1 +page1 = vectors_db.list_documents( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + queries: [ + Query.limit(25), + Query.offset(0) + ] +) + +# Page 2 +page2 = vectors_db.list_documents( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + queries: [ + Query.limit(25), + Query.offset(25) + ] +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +// Page 1 +DocumentList page1 = await vectorsDB.ListDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: new List<string> { + Query.Limit(25), + Query.Offset(0) + } +); + +// Page 2 +DocumentList page2 = await vectorsDB.ListDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: new List<string> { + Query.Limit(25), + Query.Offset(25) + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +// Page 1 +DocumentList page1 = await vectorsDB.listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.limit(25), + Query.offset(0) + ], +); + +// Page 2 +DocumentList page2 = await vectorsDB.listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.limit(25), + Query.offset(25) + ], +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +// Page 1 +val page1 = vectorsDB.listDocuments( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + queries = listOf( + Query.limit(25), + Query.offset(0) + ), +) + +// Page 2 +val page2 = vectorsDB.listDocuments( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + queries = listOf( + Query.limit(25), + Query.offset(25) + ), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.Query; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.listDocuments( + "<DATABASE_ID>", + "<COLLECTION_ID>", + List.of( + Query.limit(25), + Query.offset(0) + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +// Page 1 +let page1 = try await vectorsDB.listDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: [ + Query.limit(25), + Query.offset(0) + ] +) + +// Page 2 +let page2 = try await vectorsDB.listDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: [ + Query.limit(25), + Query.offset(25) + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::query::Query; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + // Page 1 + let page1 = vectors_db.list_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![ + Query::limit(25).to_string(), + Query::offset(0).to_string(), + ]), + None, // transactionId (optional) + None, // total (optional) + None, // ttl (optional) + ).await?; + + // Page 2 + let page2 = vectors_db.list_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![ + Query::limit(25).to_string(), + Query::offset(25).to_string(), + ]), + None, // transactionId (optional) + None, // total (optional) + None, // ttl (optional) + ).await?; + + Ok(()) +} +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"limit","values":[25]}' '{"method":"offset","values":[25]}' +``` +{% /multicode %} + +{% info title="Drawbacks" %} +While traditional offset pagination is familiar, it comes with some drawbacks. +The request gets slower as the offset increases because the database has to skip over all the preceding documents before it can start selecting data. +If the data changes frequently, offset pagination will also produce **missing and duplicate** results. +{% /info %} + +# Cursor pagination {% #cursor-pagination %} + +The cursor is a unique identifier for a document that points to where the next page should start. +After reading a page of documents, pass the last document's ID into the `Query.cursorAfter(lastId)` query method to get the next page of documents. +Pass the first document's ID into the `Query.cursorBefore(firstId)` query method to retrieve the previous page. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +// Page 1 +const page1 = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.limit(25) + ] +}); + +const lastId = page1.documents[page1.documents.length - 1].$id; + +// Page 2 +const page2 = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.limit(25), + sdk.Query.cursorAfter(lastId) + ] +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +// Page 1 +const page1 = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.limit(25) + ] +}); + +const lastId = page1.documents[page1.documents.length - 1].$id; + +// Page 2 +const page2 = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.limit(25), + sdk.Query.cursorAfter(lastId) + ] +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Query; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +// Page 1 +$page1 = $vectorsDB->listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query::limit(25) + ] +); + +$lastId = $page1['documents'][count($page1['documents']) - 1]['$id']; + +// Page 2 +$page2 = $vectorsDB->listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query::limit(25), + Query::cursorAfter($lastId) + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +# Page 1 +page1 = vectors_db.list_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + queries = [ + Query.limit(25) + ] +) + +last_id = page1['documents'][-1]['$id'] + +# Page 2 +page2 = vectors_db.list_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + queries = [ + Query.limit(25), + Query.cursor_after(last_id) + ] +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +# Page 1 +page1 = vectors_db.list_documents( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + queries: [ + Query.limit(25) + ] +) + +last_id = page1.documents.last.id + +# Page 2 +page2 = vectors_db.list_documents( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + queries: [ + Query.limit(25), + Query.cursor_after(last_id) + ] +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +// Page 1 +DocumentList page1 = await vectorsDB.ListDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: new List<string> { + Query.Limit(25) + } +); + +string lastId = page1.Documents.Last().Id; + +// Page 2 +DocumentList page2 = await vectorsDB.ListDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: new List<string> { + Query.Limit(25), + Query.CursorAfter(lastId) + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +// Page 1 +DocumentList page1 = await vectorsDB.listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.limit(25) + ], +); + +final lastId = page1.documents.last.$id; + +// Page 2 +DocumentList page2 = await vectorsDB.listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.limit(25), + Query.cursorAfter(lastId) + ], +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +// Page 1 +val page1 = vectorsDB.listDocuments( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + queries = listOf( + Query.limit(25) + ), +) + +val lastId = page1.documents.last().id + +// Page 2 +val page2 = vectorsDB.listDocuments( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + queries = listOf( + Query.limit(25), + Query.cursorAfter(lastId) + ), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.Query; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.listDocuments( + "<DATABASE_ID>", + "<COLLECTION_ID>", + List.of( + Query.limit(25), + Query.cursorAfter("<LAST_DOCUMENT_ID>") + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +// Page 1 +let page1 = try await vectorsDB.listDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: [ + Query.limit(25) + ] +) + +let lastId = page1.documents.last!.id + +// Page 2 +let page2 = try await vectorsDB.listDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: [ + Query.limit(25), + Query.cursorAfter(lastId) + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::query::Query; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + // Page 1 + let page1 = vectors_db.list_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![Query::limit(25).to_string()]), + None, // transactionId (optional) + None, // total (optional) + None, // ttl (optional) + ).await?; + + let last_id = page1.documents.last().unwrap().id.clone(); + + // Page 2 + let page2 = vectors_db.list_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![ + Query::limit(25).to_string(), + Query::cursor_after(last_id).to_string(), + ]), + None, // transactionId (optional) + None, // total (optional) + None, // ttl (optional) + ).await?; + + Ok(()) +} +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"limit","values":[25]}' '{"method":"cursorAfter","values":["<LAST_DOCUMENT_ID>"]}' +``` +{% /multicode %} + +# When to use what? {% #when-to-use %} +Offset pagination should be used for collections that rarely change. +Offset pagination lets you build an indicator of the current page number and the total page count. +For example, a list with up to 20 pages or static data like a list of countries or currencies. +Using offset pagination on large and frequently updated collections may result in slow performance and **missing and duplicate** results. + +Cursor pagination should be used for frequently updated collections. +It is best suited for lazy-loaded pages with infinite scrolling. +For example, a feed, comment section, chat history, or high volume datasets. + +# Cache list responses {% #cache-list-responses %} + +You can cache list responses by passing a `ttl` (time-to-live) value in seconds. Subsequent identical requests return the cached result until the TTL expires. The cache is permission-aware, so users with different roles never see each other's cached data. + +Set `ttl` between `1` and `86400` (24 hours). The default is `0` (caching disabled). The response includes an `X-Appwrite-Cache` header with value `hit` or `miss`. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const page = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.limit(25) + ], + ttl: 60 // Cache for 60 seconds +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const page = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.limit(25) + ], + ttl: 60 // Cache for 60 seconds +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Query; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$page = $vectorsDB->listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query::limit(25) + ], + ttl: 60 // Cache for 60 seconds +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +page = vectors_db.list_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + queries = [ + Query.limit(25) + ], + ttl = 60 # Cache for 60 seconds +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +page = vectors_db.list_documents( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + queries: [ + Query.limit(25) + ], + ttl: 60 # Cache for 60 seconds +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +DocumentList page = await vectorsDB.ListDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: new List<string> { + Query.Limit(25) + }, + ttl: 60 // Cache for 60 seconds +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +DocumentList page = await vectorsDB.listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.limit(25) + ], + ttl: 60, // Cache for 60 seconds +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val page = vectorsDB.listDocuments( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + queries = listOf( + Query.limit(25) + ), + ttl = 60 // Cache for 60 seconds +) +``` +```java +import io.appwrite.Client; +import io.appwrite.Query; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.listDocuments( + "<DATABASE_ID>", + "<COLLECTION_ID>", + List.of( + Query.limit(25) + ), + null, // transactionId + null, // total + 60, // ttl - Cache for 60 seconds + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let page = try await vectorsDB.listDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: [ + Query.limit(25) + ], + ttl: 60 // Cache for 60 seconds +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::query::Query; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let page = vectors_db.list_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![Query::limit(25).to_string()]), + None, // transactionId (optional) + None, // total (optional) + Some(60), // ttl - Cache for 60 seconds + ).await?; + + println!("{:?}", page); + Ok(()) +} +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"limit","values":[25]}' \ + --ttl 60 +``` +{% /multicode %} + +Document writes do **not** invalidate the cache, so cached responses may contain stale data until the TTL expires. Use a short TTL for collections that change often, or skip caching entirely when you always need the latest documents. diff --git a/src/routes/docs/products/databases/vectorsdb/permissions/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/permissions/+page.markdoc new file mode 100644 index 00000000000..2b3adc1c581 --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/permissions/+page.markdoc @@ -0,0 +1,1241 @@ +--- +layout: article +title: Database permissions +description: Control access to your VectorsDB data with permissions. Learn how to set collection level and document level access rules. +--- + +Permissions define who can access documents in a collection. By default **no permissions** are granted to any users, so no user can access any documents. +Permissions exist at two levels, collection level and document level permissions. + +In Appwrite, permissions are **granted**, meaning a user has no access by default and receives access when granted. +A user with access granted at either collection level or document level will be able to access a document. +Users **don't need access at both levels** to access documents. + +Permissions are evaluated when documents are accessed through a [Client SDK](/docs/sdks#client). [Server SDKs](/docs/sdks#server) authenticated with an [API key](/docs/advanced/platform/api-keys) bypass permissions, so the examples below use a Server SDK to set and read back the permissions that a client would then be evaluated against. + +# Collection level {% #collection-level %} +Collection level permissions apply to every document in the collection. +If a user has read, create, update, or delete permissions at the collection level, the user can access **all documents** inside the collection. + +Configure collection level permissions by navigating to **Your collection** > **Security** > **Permissions**, or pass a `permissions` array when you create or update the collection. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createCollection({ + databaseId: '<DATABASE_ID>', + collectionId: sdk.ID.unique(), + name: 'documents', + dimension: 4, + permissions: [ + sdk.Permission.read(sdk.Role.any()), + sdk.Permission.create(sdk.Role.users()), + sdk.Permission.update(sdk.Role.users()), + sdk.Permission.delete(sdk.Role.users()) + ], + documentSecurity: true // optional +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createCollection({ + databaseId: '<DATABASE_ID>', + collectionId: sdk.ID.unique(), + name: 'documents', + dimension: 4, + permissions: [ + sdk.Permission.read(sdk.Role.any()), + sdk.Permission.create(sdk.Role.users()), + sdk.Permission.update(sdk.Role.users()), + sdk.Permission.delete(sdk.Role.users()) + ], + documentSecurity: true // optional +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\ID; +use Appwrite\Permission; +use Appwrite\Role; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->createCollection( + databaseId: '<DATABASE_ID>', + collectionId: ID::unique(), + name: 'documents', + dimension: 4, + permissions: [ + Permission::read(Role::any()), + Permission::create(Role::users()), + Permission::update(Role::users()), + Permission::delete(Role::users()) + ], + documentSecurity: true // optional +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.id import ID +from appwrite.permission import Permission +from appwrite.role import Role + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.create_collection( + database_id = '<DATABASE_ID>', + collection_id = ID.unique(), + name = 'documents', + dimension = 4, + permissions = [ + Permission.read(Role.any()), + Permission.create(Role.users()), + Permission.update(Role.users()), + Permission.delete(Role.users()) + ], + document_security = True # optional +) +``` +```ruby +require 'appwrite' + +include Appwrite +include Appwrite::Permission +include Appwrite::Role + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.create_collection( + database_id: '<DATABASE_ID>', + collection_id: ID.unique(), + name: 'documents', + dimension: 4, + permissions: [ + Permission.read(Role.any()), + Permission.create(Role.users()), + Permission.update(Role.users()), + Permission.delete(Role.users()) + ], + document_security: true # optional +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Collection result = await vectorsDB.CreateCollection( + databaseId: "<DATABASE_ID>", + collectionId: ID.Unique(), + name: "documents", + dimension: 4, + permissions: new List<string> { + Permission.Read(Role.Any()), + Permission.Create(Role.Users()), + Permission.Update(Role.Users()), + Permission.Delete(Role.Users()) + }, + documentSecurity: true // optional +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; +import 'package:dart_appwrite/permission.dart'; +import 'package:dart_appwrite/role.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Collection result = await vectorsDB.createCollection( + databaseId: '<DATABASE_ID>', + collectionId: ID.unique(), + name: 'documents', + dimension: 4, + permissions: [ + Permission.read(Role.any()), + Permission.create(Role.users()), + Permission.update(Role.users()), + Permission.delete(Role.users()) + ], // (optional) + documentSecurity: true, // (optional) +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.ID +import io.appwrite.Permission +import io.appwrite.Role +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.createCollection( + databaseId = "<DATABASE_ID>", + collectionId = ID.unique(), + name = "documents", + dimension = 4, + permissions = listOf( + Permission.read(Role.any()), + Permission.create(Role.users()), + Permission.update(Role.users()), + Permission.delete(Role.users()) + ), // optional + documentSecurity = true, // optional +) +``` +```java +import io.appwrite.Client; +import io.appwrite.ID; +import io.appwrite.Permission; +import io.appwrite.Role; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.createCollection( + "<DATABASE_ID>", + ID.unique(), + "documents", + 4, + List.of( + Permission.read(Role.any()), + Permission.create(Role.users()), + Permission.update(Role.users()), + Permission.delete(Role.users()) + ), + true, + true, + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let collection = try await vectorsDB.createCollection( + databaseId: "<DATABASE_ID>", + collectionId: ID.unique(), + name: "documents", + dimension: 4, + permissions: [ + Permission.read(Role.any()), + Permission.create(Role.users()), + Permission.update(Role.users()), + Permission.delete(Role.users()) + ], + documentSecurity: true // optional +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::id::ID; +use appwrite::permission::Permission; +use appwrite::role::Role; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create_collection( + "<DATABASE_ID>", + ID::unique(), + "documents", + 4, + Some(vec![ + Permission::read(Role::any()).to_string(), + Permission::create(Role::users(None)).to_string(), + Permission::update(Role::users(None)).to_string(), + Permission::delete(Role::users(None)).to_string(), + ]), + Some(true), // documentSecurity (optional) + None, // enabled (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db create-collection \ + --database-id "<DATABASE_ID>" \ + --collection-id "<COLLECTION_ID>" \ + --name "documents" \ + --dimension 4 \ + --permissions 'read("any")' 'create("users")' 'update("users")' 'delete("users")' \ + --document-security true +``` +{% /multicode %} + +To change a collection's permissions later, pass a new `permissions` array to `updateCollection`. The `name` is required when updating. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.updateCollection({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + name: 'documents', + permissions: [ + sdk.Permission.read(sdk.Role.any()), + sdk.Permission.create(sdk.Role.users()) + ], + documentSecurity: false // optional +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.updateCollection({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + name: 'documents', + permissions: [ + sdk.Permission.read(sdk.Role.any()), + sdk.Permission.create(sdk.Role.users()) + ], + documentSecurity: false // optional +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Permission; +use Appwrite\Role; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->updateCollection( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + name: 'documents', + permissions: [ + Permission::read(Role::any()), + Permission::create(Role::users()) + ], + documentSecurity: false // optional +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.permission import Permission +from appwrite.role import Role + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.update_collection( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + name = 'documents', + permissions = [ + Permission.read(Role.any()), + Permission.create(Role.users()) + ], + document_security = False # optional +) +``` +```ruby +require 'appwrite' + +include Appwrite +include Appwrite::Permission +include Appwrite::Role + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.update_collection( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + name: 'documents', + permissions: [ + Permission.read(Role.any()), + Permission.create(Role.users()) + ], + document_security: false # optional +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Collection result = await vectorsDB.UpdateCollection( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + name: "documents", + permissions: new List<string> { + Permission.Read(Role.Any()), + Permission.Create(Role.Users()) + }, + documentSecurity: false // optional +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; +import 'package:dart_appwrite/permission.dart'; +import 'package:dart_appwrite/role.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Collection result = await vectorsDB.updateCollection( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + name: 'documents', + permissions: [ + Permission.read(Role.any()), + Permission.create(Role.users()) + ], // (optional) + documentSecurity: false, // (optional) +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Permission +import io.appwrite.Role +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.updateCollection( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + name = "documents", + permissions = listOf( + Permission.read(Role.any()), + Permission.create(Role.users()) + ), // optional + documentSecurity = false, // optional +) +``` +```java +import io.appwrite.Client; +import io.appwrite.Permission; +import io.appwrite.Role; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.updateCollection( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "documents", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let collection = try await vectorsDB.updateCollection( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + name: "documents", + permissions: [ + Permission.read(Role.any()), + Permission.create(Role.users()) + ], + documentSecurity: false // optional +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::permission::Permission; +use appwrite::role::Role; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.update_collection( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "documents", + None, // dimension (optional) + Some(vec![ + Permission::read(Role::any()).to_string(), + Permission::create(Role::users(None)).to_string(), + ]), + Some(false), // documentSecurity (optional) + None, // enabled (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db update-collection \ + --database-id "<DATABASE_ID>" \ + --collection-id "<COLLECTION_ID>" \ + --name "documents" \ + --permissions 'read("any")' 'create("users")' \ + --document-security false +``` +{% /multicode %} + +[Learn more about permissions and roles](/docs/advanced/platform/permissions) + +# Document level {% #document-level %} +Document level permissions grant access to individual documents. +If a user has read, update, or delete permissions at the document level, the user can access the **individual document**. + +Document level permissions are only applied if `documentSecurity` is enabled on the collection. Enable it in the Console by navigating to **Your collection** > **Security** > **Document security**, or by setting `documentSecurity` to `true` when you create or update the collection, as shown above. + +Set permissions on an individual document by passing a `permissions` array to `createDocument`. Use `Role.user('<USER_ID>')` to scope access to a specific user. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: sdk.ID.unique(), + data: { + embeddings: [0.12, 0.84, 0.33, 0.57], + metadata: { title: 'Hamlet' } + }, + permissions: [ + sdk.Permission.read(sdk.Role.any()), + sdk.Permission.update(sdk.Role.user('<USER_ID>')), + sdk.Permission.delete(sdk.Role.user('<USER_ID>')) + ] +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: sdk.ID.unique(), + data: { + embeddings: [0.12, 0.84, 0.33, 0.57], + metadata: { title: 'Hamlet' } + }, + permissions: [ + sdk.Permission.read(sdk.Role.any()), + sdk.Permission.update(sdk.Role.user('<USER_ID>')), + sdk.Permission.delete(sdk.Role.user('<USER_ID>')) + ] +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\ID; +use Appwrite\Permission; +use Appwrite\Role; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: ID::unique(), + data: [ + 'embeddings' => [0.12, 0.84, 0.33, 0.57], + 'metadata' => ['title' => 'Hamlet'] + ], + permissions: [ + Permission::read(Role::any()), + Permission::update(Role::user('<USER_ID>')), + Permission::delete(Role::user('<USER_ID>')) + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.id import ID +from appwrite.permission import Permission +from appwrite.role import Role + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.create_document( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = ID.unique(), + data = { + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": { "title": "Hamlet" } + }, + permissions = [ + Permission.read(Role.any()), + Permission.update(Role.user('<USER_ID>')), + Permission.delete(Role.user('<USER_ID>')) + ] +) +``` +```ruby +require 'appwrite' + +include Appwrite +include Appwrite::Permission +include Appwrite::Role + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.create_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: ID.unique(), + data: { + "embeddings" => [0.12, 0.84, 0.33, 0.57], + "metadata" => { "title" => "Hamlet" } + }, + permissions: [ + Permission.read(Role.any()), + Permission.update(Role.user('<USER_ID>')), + Permission.delete(Role.user('<USER_ID>')) + ] +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Document result = await vectorsDB.CreateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: ID.Unique(), + data: new Dictionary<string, object> { + { "embeddings", new List<double> { 0.12, 0.84, 0.33, 0.57 } }, + { "metadata", new Dictionary<string, object> { { "title", "Hamlet" } } } + }, + permissions: new List<string> { + Permission.Read(Role.Any()), + Permission.Update(Role.User("<USER_ID>")), + Permission.Delete(Role.User("<USER_ID>")) + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; +import 'package:dart_appwrite/permission.dart'; +import 'package:dart_appwrite/role.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Document result = await vectorsDB.createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: ID.unique(), + data: { + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": { "title": "Hamlet" } + }, + permissions: [ + Permission.read(Role.any()), + Permission.update(Role.user('<USER_ID>')), + Permission.delete(Role.user('<USER_ID>')) + ], // (optional) +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.ID +import io.appwrite.Permission +import io.appwrite.Role +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.createDocument( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = ID.unique(), + data = mapOf( + "embeddings" to listOf(0.12, 0.84, 0.33, 0.57), + "metadata" to mapOf("title" to "Hamlet") + ), + permissions = listOf( + Permission.read(Role.any()), + Permission.update(Role.user("<USER_ID>")), + Permission.delete(Role.user("<USER_ID>")) + ), // optional +) +``` +```java +import io.appwrite.Client; +import io.appwrite.ID; +import io.appwrite.Permission; +import io.appwrite.Role; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; +import java.util.Map; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.createDocument( + "<DATABASE_ID>", + "<COLLECTION_ID>", + ID.unique(), + Map.of( + "embeddings", List.of(0.12, 0.84, 0.33, 0.57), + "metadata", Map.of("title", "Hamlet") + ), + List.of( + Permission.read(Role.any()), + Permission.update(Role.user("<USER_ID>")), + Permission.delete(Role.user("<USER_ID>")) + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let document = try await vectorsDB.createDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: ID.unique(), + data: [ + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": ["title": "Hamlet"] + ], + permissions: [ + Permission.read(Role.any()), + Permission.update(Role.user("<USER_ID>")), + Permission.delete(Role.user("<USER_ID>")) + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::id::ID; +use appwrite::permission::Permission; +use appwrite::role::Role; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + ID::unique(), + json!({ + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": { "title": "Hamlet" } + }), + Some(vec![ + Permission::read(Role::any()).to_string(), + Permission::update(Role::user("<USER_ID>", None)).to_string(), + Permission::delete(Role::user("<USER_ID>", None)).to_string(), + ]), + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db create-document \ + --database-id "<DATABASE_ID>" \ + --collection-id "<COLLECTION_ID>" \ + --document-id 'unique()' \ + --data '{ "embeddings": [0.12, 0.84, 0.33, 0.57], "metadata": { "title": "Hamlet" } }' \ + --permissions 'read("any")' 'update("user:<USER_ID>")' 'delete("user:<USER_ID>")' +``` +{% /multicode %} + +To change a document's permissions later, pass a new `permissions` array to `updateDocument`. Only the fields you pass are changed, so you can update permissions without touching the vector or metadata. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.updateDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + permissions: [ + sdk.Permission.read(sdk.Role.users()) + ] +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.updateDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + permissions: [ + sdk.Permission.read(sdk.Role.users()) + ] +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Permission; +use Appwrite\Role; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->updateDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + permissions: [ + Permission::read(Role::users()) + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.permission import Permission +from appwrite.role import Role + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.update_document( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = '<DOCUMENT_ID>', + permissions = [ + Permission.read(Role.users()) + ] +) +``` +```ruby +require 'appwrite' + +include Appwrite +include Appwrite::Permission +include Appwrite::Role + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.update_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: '<DOCUMENT_ID>', + permissions: [ + Permission.read(Role.users()) + ] +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Document result = await vectorsDB.UpdateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + permissions: new List<string> { + Permission.Read(Role.Users()) + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; +import 'package:dart_appwrite/permission.dart'; +import 'package:dart_appwrite/role.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Document result = await vectorsDB.updateDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + permissions: [ + Permission.read(Role.users()) + ], // (optional) +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Permission +import io.appwrite.Role +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.updateDocument( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = "<DOCUMENT_ID>", + permissions = listOf( + Permission.read(Role.users()) + ), // optional +) +``` +```java +import io.appwrite.Client; +import io.appwrite.Permission; +import io.appwrite.Role; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.updateDocument( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + null, + List.of( + Permission.read(Role.users()) + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let document = try await vectorsDB.updateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + permissions: [ + Permission.read(Role.users()) + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::permission::Permission; +use appwrite::role::Role; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.update_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + None, // data (optional) + Some(vec![ + Permission::read(Role::users(None)).to_string(), + ]), + None, // transactionId (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db update-document \ + --database-id "<DATABASE_ID>" \ + --collection-id "<COLLECTION_ID>" \ + --document-id "<DOCUMENT_ID>" \ + --permissions 'read("users")' +``` +{% /multicode %} + +[Learn more about permissions and roles](/docs/advanced/platform/permissions) + +# Common use cases {% #common-use-cases %} + +For examples of how to implement common permission patterns, including creating private documents that are only accessible to their creators, see the [permissions examples](/docs/advanced/platform/permissions#examples) in our platform documentation. diff --git a/src/routes/docs/products/databases/vectorsdb/queries/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/queries/+page.markdoc new file mode 100644 index 00000000000..5e36bec2324 --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/queries/+page.markdoc @@ -0,0 +1,1879 @@ +--- +layout: article +title: Queries +description: Filter VectorsDB documents by their metadata using the Query class. Discover comparison, string, logical, ordering, and pagination operators. +--- + +Many list endpoints in Appwrite allow you to filter, sort, and paginate results using queries. Appwrite provides a common set of syntax to build queries. + +In VectorsDB, every document stores an `embeddings` vector and an optional `metadata` object. The queries on this page filter documents by the fields inside that `metadata` object. To rank documents by vector similarity instead, see [vector search](/docs/products/databases/vectorsdb/vector-search). + +# Query class {% #query-class %} + +Appwrite SDKs provide a `Query` class to help you build queries. The `Query` class has methods for each type of supported query operation. + +# Building queries {% #building-queries %} + +Queries are passed to an endpoint through the `queries` parameter as an array of query strings, which can be generated using the `Query` class. + +Each query method is logically separated via `AND` operations. For `OR` operation, pass multiple values into the query method separated by commas. +For example `Query.equal('metadata.genre', ['sci-fi', 'drama'])` will fetch documents whose genre is `sci-fi` or `drama`. + +To filter on a field inside the `metadata` object, reference it with dot notation, such as `metadata.genre` or `metadata.year`. + +{% info title="Filter metadata with string values" %} +VectorsDB stores `metadata` as a JSON object, so metadata fields are compared as strings. Pass filter values as strings, even when the stored value is a number. For example, use `Query.greaterThan('metadata.year', '2010')`, not `Query.greaterThan('metadata.year', 2010)`. Passing a numeric value returns a `400` error. +{% /info %} + +{% info title="Default pagination behavior" %} +By default, results are limited to the **first 25 items**. +You can change this through [pagination](/docs/products/databases/vectorsdb/pagination). +{% /info %} + +{% multicode %} + +```client-web +import { Client, Query, VectorsDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>'); + +const vectorsDB = new VectorsDB(client); + +vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.equal('metadata.genre', ['sci-fi', 'drama']), + Query.greaterThan('metadata.year', '2010') + ] +}); +``` +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client(); + +const vectorsDB = new sdk.VectorsDB(client); + +client + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>') +; + +const promise = vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.equal('metadata.genre', ['sci-fi', 'drama']), + sdk.Query.greaterThan('metadata.year', '2010') + ] +}); + +promise.then(function (response) { + console.log(response); +}, function (error) { + console.log(error); +}); +``` +```client-flutter +import 'package:appwrite/appwrite.dart'; + +void main() async { + final client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>'); + + final vectorsDB = VectorsDB(client); + + try { + final documents = await vectorsDB.listDocuments( + '<DATABASE_ID>', + '<COLLECTION_ID>', + [ + Query.equal('metadata.genre', ['sci-fi', 'drama']), + Query.greaterThan('metadata.year', '2010') + ] + ); + } on AppwriteException catch(e) { + print(e); + } +} +``` +```client-apple +import Appwrite +import AppwriteModels + +func main() async throws { + let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + + let vectorsDB = VectorsDB(client) + + do { + let documents = try await vectorsDB.listDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: [ + Query.equal("metadata.genre", value: ["sci-fi", "drama"]), + Query.greaterThan("metadata.year", value: "2010") + ] + ) + } catch { + print(error.localizedDescription) + } +} +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.VectorsDB + +suspend fun main() { + val client = Client(applicationContext) + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>'); + + val vectorsDB = VectorsDB(client) + + try { + val documents = vectorsDB.listDocuments( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + queries = listOf( + Query.equal("metadata.genre", listOf("sci-fi", "drama")), + Query.greaterThan("metadata.year", "2010") + ) + ) + } catch (e: AppwriteException) { + Log.e("Appwrite", e.message) + } +} +``` +```server-go +package main + +import ( + "fmt" + "log" + + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/query" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<API_KEY>"), + ) + + vectorsDB := appwrite.NewVectorsDB(client) + + documents, err := vectorsDB.ListDocuments( + "<DATABASE_ID>", + "<COLLECTION_ID>", + vectorsDB.WithListDocumentsQueries([]string{ + query.Equal("metadata.genre", []string{"sci-fi", "drama"}), + query.GreaterThan("metadata.year", "2010"), + }), + ) + if err != nil { + log.Fatal(err) + } + + fmt.Printf("Documents: %+v\n", documents) +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::query::Query; +use serde_json::Value; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let vectors_db = VectorsDB::new(&client); + + let documents = vectors_db.list_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![ + Query::equal("metadata.genre", Value::Array(vec![ + Value::String("sci-fi".to_string()), + Value::String("drama".to_string()), + ])).to_string(), + Query::greater_than("metadata.year", "2010").to_string(), + ]), + None, + None, + None, + ).await?; + + println!("{:?}", documents); + Ok(()) +} +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Query; +use Appwrite\Services\VectorsDB; + +$client = new Client(); + +$client + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>') +; + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->listDocuments( + '<DATABASE_ID>', + '<COLLECTION_ID>', + [ + Query::equal('metadata.genre', ['sci-fi', 'drama']), + Query::greaterThan('metadata.year', '2010') + ] +); +``` +```server-python +from appwrite.client import Client +from appwrite.query import Query +from appwrite.services.vectors_db import VectorsDB + +client = Client() + +(client + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') +) + +vectorsDB = VectorsDB(client) + +result = vectorsDB.list_documents( + '<DATABASE_ID>', + '<COLLECTION_ID>', + [ + Query.equal('metadata.genre', ['sci-fi', 'drama']), + Query.greater_than('metadata.year', '2010') + ] +) +``` +```graphql +query { + vectorsDBListDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>" + queries: [ + "{\"method\":\"equal\",\"attribute\":\"metadata.genre\",\"values\":[\"sci-fi\",\"drama\"]}", + "{\"method\":\"greaterThan\",\"attribute\":\"metadata.year\",\"values\":[\"2010\"]}" + ] + ) { + total + documents { + _id + data + } + } +} +``` +```http +GET /v1/vectorsdb/<DATABASE_ID>/collections/<COLLECTION_ID>/documents?queries[]=%7B%22method%22%3A%22equal%22%2C%22attribute%22%3A%22metadata.genre%22%2C%22values%22%3A%5B%22sci-fi%22%2C%22drama%22%5D%7D&queries[]=%7B%22method%22%3A%22greaterThan%22%2C%22attribute%22%3A%22metadata.year%22%2C%22values%22%3A%5B%222010%22%5D%7D HTTP/1.1 +Content-Type: application/json +X-Appwrite-Project: <PROJECT_ID> +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"equal","attribute":"metadata.genre","values":["sci-fi","drama"]}' '{"method":"greaterThan","attribute":"metadata.year","values":["2010"]}' +``` +{% /multicode %} + +# Query operators {% #query-operators %} + +## Select {% #select %} + +The `select` operator allows you to specify which fields should be returned from a document. This optimizes response size and retrieves only the data you need. Each VectorsDB document has two data fields, `embeddings` and `metadata`. + +{% multicode %} +```client-web +Query.select(["metadata"]) +``` +```client-flutter +Query.select(["metadata"]) +``` +```server-python +Query.select(["metadata"]) +``` +```server-ruby +Query.select(["metadata"]) +``` +```server-deno +Query.select(["metadata"]) +``` +```server-php +Query::select(["metadata"]) +``` +```client-apple +Query.select(["metadata"]) +``` +```server-go +query.Select([]string{"metadata"}) +``` +```server-nodejs +Query.select(["metadata"]) +``` +```server-rust +Query::select(vec!["metadata"]).to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"select","values":["metadata"]}' +``` +```http +{"method":"select","values":["metadata"]} +``` +{% /multicode %} + +### Use selection patterns {% #select-patterns %} + +| Pattern | Description | Use case | +|---------|-------------|----------| +| `["metadata"]` | Metadata object only | Return metadata without the embedding vector | +| `["embeddings"]` | Embedding vector only | Return the stored vector | +| `["metadata", "embeddings"]` | Both data fields | Get the complete document data | + +### Optimize performance {% #select-performance %} + +**Optimize response size** - Only select the fields you actually need. Embedding vectors are large, so omitting `embeddings` keeps responses small when you only need metadata. + +**Reduce database load** - Selecting fewer fields reduces database processing time. + +## Comparison operators {% #comparison %} + +Pass metadata filter values as strings. See [Filter metadata with string values](#building-queries). + +### Equal {% #equal %} + +Returns document if a metadata field is equal to any value in the provided array. + +{% multicode %} +```client-web +Query.equal("metadata.genre", ["sci-fi"]) +``` +```client-flutter +Query.equal("metadata.genre", ["sci-fi"]) +``` +```server-python +Query.equal("metadata.genre", ["sci-fi"]) +``` +```server-ruby +Query.equal("metadata.genre", ["sci-fi"]) +``` +```server-deno +Query.equal("metadata.genre", ["sci-fi"]) +``` +```server-php +Query::equal("metadata.genre", ["sci-fi"]) +``` +```client-apple +Query.equal("metadata.genre", value: ["sci-fi"]) +``` +```server-go +query.Equal("metadata.genre", []string{"sci-fi"}) +``` +```server-nodejs +Query.equal("metadata.genre", ["sci-fi"]) +``` +```server-rust +Query::equal("metadata.genre", Value::Array(vec![Value::String("sci-fi".to_string())])).to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"equal","attribute":"metadata.genre","values":["sci-fi"]}' +``` +```http +{"method":"equal","attribute":"metadata.genre","values":["sci-fi"]} +``` +{% /multicode %} + +### Not equal {% #not-equal %} + +Returns document if a metadata field is not equal to the provided value. + +{% multicode %} +```client-web +Query.notEqual("metadata.genre", "sci-fi") +``` +```client-flutter +Query.notEqual("metadata.genre", "sci-fi") +``` +```server-python +Query.not_equal("metadata.genre", "sci-fi") +``` +```server-ruby +Query.not_equal("metadata.genre", "sci-fi") +``` +```server-deno +Query.notEqual("metadata.genre", "sci-fi") +``` +```server-php +Query::notEqual("metadata.genre", "sci-fi") +``` +```client-apple +Query.notEqual("metadata.genre", value: "sci-fi") +``` +```server-go +query.NotEqual("metadata.genre", "sci-fi") +``` +```server-nodejs +Query.notEqual("metadata.genre", "sci-fi") +``` +```server-rust +Query::not_equal("metadata.genre", "sci-fi").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"notEqual","attribute":"metadata.genre","values":["sci-fi"]}' +``` +```http +{"method":"notEqual","attribute":"metadata.genre","values":"sci-fi"} +``` +{% /multicode %} + +### Less than {% #less-than %} + +Returns document if a metadata field is less than the provided value. + +{% multicode %} +```client-web +Query.lessThan("metadata.year", "2009") +``` +```client-flutter +Query.lessThan("metadata.year", "2009") +``` +```server-python +Query.less_than("metadata.year", "2009") +``` +```server-ruby +Query.less_than("metadata.year", "2009") +``` +```server-deno +Query.lessThan("metadata.year", "2009") +``` +```server-php +Query::lessThan("metadata.year", "2009") +``` +```client-apple +Query.lessThan("metadata.year", value: "2009") +``` +```server-go +query.LessThan("metadata.year", "2009") +``` +```server-nodejs +Query.lessThan("metadata.year", "2009") +``` +```server-rust +Query::less_than("metadata.year", "2009").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"lessThan","attribute":"metadata.year","values":["2009"]}' +``` +```http +{"method":"lessThan","attribute":"metadata.year","values":["2009"]} +``` +{% /multicode %} + +### Less than or equal {% #less-than-equal %} + +Returns document if a metadata field is less than or equal to the provided value. + +{% multicode %} +```client-web +Query.lessThanEqual("metadata.year", "2009") +``` +```client-flutter +Query.lessThanEqual("metadata.year", "2009") +``` +```server-python +Query.less_than_equal("metadata.year", "2009") +``` +```server-ruby +Query.less_than_equal("metadata.year", "2009") +``` +```server-deno +Query.lessThanEqual("metadata.year", "2009") +``` +```server-php +Query::lessThanEqual("metadata.year", "2009") +``` +```client-apple +Query.lessThanEqual("metadata.year", value: "2009") +``` +```server-go +query.LessThanEqual("metadata.year", "2009") +``` +```server-nodejs +Query.lessThanEqual("metadata.year", "2009") +``` +```server-rust +Query::less_than_equal("metadata.year", "2009").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"lessThanEqual","attribute":"metadata.year","values":["2009"]}' +``` +```http +{"method":"lessThanEqual","attribute":"metadata.year","values":["2009"]} +``` +{% /multicode %} + +### Greater than {% #greater-than %} + +Returns document if a metadata field is greater than the provided value. + +{% multicode %} +```client-web +Query.greaterThan("metadata.year", "2009") +``` +```client-flutter +Query.greaterThan("metadata.year", "2009") +``` +```server-python +Query.greater_than("metadata.year", "2009") +``` +```server-ruby +Query.greater_than("metadata.year", "2009") +``` +```server-deno +Query.greaterThan("metadata.year", "2009") +``` +```server-php +Query::greaterThan("metadata.year", "2009") +``` +```client-apple +Query.greaterThan("metadata.year", value: "2009") +``` +```server-go +query.GreaterThan("metadata.year", "2009") +``` +```server-nodejs +Query.greaterThan("metadata.year", "2009") +``` +```server-rust +Query::greater_than("metadata.year", "2009").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"greaterThan","attribute":"metadata.year","values":["2009"]}' +``` +```http +{"method":"greaterThan","attribute":"metadata.year","values":["2009"]} +``` +{% /multicode %} + +### Greater than or equal {% #greater-than-equal %} + +Returns document if a metadata field is greater than or equal to the provided value. + +{% multicode %} +```client-web +Query.greaterThanEqual("metadata.year", "2009") +``` +```client-flutter +Query.greaterThanEqual("metadata.year", "2009") +``` +```server-python +Query.greater_than_equal("metadata.year", "2009") +``` +```server-ruby +Query.greater_than_equal("metadata.year", "2009") +``` +```server-deno +Query.greaterThanEqual("metadata.year", "2009") +``` +```server-php +Query::greaterThanEqual("metadata.year", "2009") +``` +```client-apple +Query.greaterThanEqual("metadata.year", value: "2009") +``` +```server-go +query.GreaterThanEqual("metadata.year", "2009") +``` +```server-nodejs +Query.greaterThanEqual("metadata.year", "2009") +``` +```server-rust +Query::greater_than_equal("metadata.year", "2009").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"greaterThanEqual","attribute":"metadata.year","values":["2009"]}' +``` +```http +{"method":"greaterThanEqual","attribute":"metadata.year","values":["2009"]} +``` +{% /multicode %} + +### Between {% #between %} + +Returns document if a metadata field value falls between the two values. The boundary values are inclusive. + +{% multicode %} +```client-web +Query.between("metadata.year", "2005", "2010") +``` +```client-flutter +Query.between("metadata.year", "2005", "2010") +``` +```server-python +Query.between("metadata.year", "2005", "2010") +``` +```server-ruby +Query.between("metadata.year", "2005", "2010") +``` +```server-deno +Query.between("metadata.year", "2005", "2010") +``` +```server-php +Query::between("metadata.year", "2005", "2010") +``` +```client-apple +Query.between("metadata.year", start: "2005", end: "2010") +``` +```server-go +query.Between("metadata.year", "2005", "2010") +``` +```server-nodejs +Query.between("metadata.year", "2005", "2010") +``` +```server-rust +Query::between("metadata.year", "2005", "2010").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"between","attribute":"metadata.year","values":["2005","2010"]}' +``` +```http +{"method":"between","attribute":"metadata.year","values":["2005","2010"]} +``` +{% /multicode %} + +### Not between {% #not-between %} + +Returns documents if the metadata field value is outside the range defined by the two values. Boundary values are excluded. + +{% multicode %} +```client-web +Query.notBetween("metadata.year", "2005", "2010") +``` +```client-flutter +Query.notBetween("metadata.year", "2005", "2010") +``` +```client-apple +Query.notBetween("metadata.year", start: "2005", end: "2010") +``` +```client-android-kotlin +Query.notBetween("metadata.year", "2005", "2010") +``` +```client-android-java +Query.notBetween("metadata.year", "2005", "2010") +``` +```server-python +Query.not_between("metadata.year", "2005", "2010") +``` +```server-ruby +Query.not_between("metadata.year", "2005", "2010") +``` +```server-deno +Query.notBetween("metadata.year", "2005", "2010") +``` +```server-nodejs +Query.notBetween("metadata.year", "2005", "2010") +``` +```server-php +Query::notBetween("metadata.year", "2005", "2010") +``` +```server-swift +Query.notBetween("metadata.year", start: "2005", end: "2010") +``` +```server-rust +Query::not_between("metadata.year", "2005", "2010").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"notBetween","attribute":"metadata.year","values":["2005","2010"]}' +``` +```http +{"method":"notBetween","attribute":"metadata.year","values":["2005","2010"]} +``` +{% /multicode %} + +## Null checks {% #null-checks %} + +### Is null {% #is-null %} + +Returns documents where the metadata field value is null or absent. + +{% multicode %} +```client-web +Query.isNull("metadata.archived") +``` +```client-flutter +Query.isNull("metadata.archived") +``` +```server-python +Query.is_null("metadata.archived") +``` +```server-ruby +Query.is_null("metadata.archived") +``` +```server-deno +Query.isNull("metadata.archived") +``` +```server-php +Query::isNull("metadata.archived") +``` +```client-apple +Query.isNull("metadata.archived") +``` +```server-go +query.IsNull("metadata.archived") +``` +```server-nodejs +Query.isNull("metadata.archived") +``` +```server-rust +Query::is_null("metadata.archived").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"isNull","attribute":"metadata.archived"}' +``` +```http +{"method":"isNull","attribute":"metadata.archived"} +``` +{% /multicode %} + +### Is not null {% #is-not-null %} + +Returns documents where the metadata field value is **not** null. + +{% multicode %} +```client-web +Query.isNotNull("metadata.archived") +``` +```client-flutter +Query.isNotNull("metadata.archived") +``` +```server-python +Query.is_not_null("metadata.archived") +``` +```server-ruby +Query.is_not_null("metadata.archived") +``` +```server-deno +Query.isNotNull("metadata.archived") +``` +```server-php +Query::isNotNull("metadata.archived") +``` +```client-apple +Query.isNotNull("metadata.archived") +``` +```server-go +query.IsNotNull("metadata.archived") +``` +```server-nodejs +Query.isNotNull("metadata.archived") +``` +```server-rust +Query::is_not_null("metadata.archived").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"isNotNull","attribute":"metadata.archived"}' +``` +```http +{"method":"isNotNull","attribute":"metadata.archived"} +``` +{% /multicode %} + +## String operations {% #string-operations %} + +### Starts with {% #starts-with %} + +Returns documents if a metadata string field starts with a substring. + +{% multicode %} +```client-web +Query.startsWith("metadata.title", "Iron") +``` +```client-flutter +Query.startsWith("metadata.title", "Iron") +``` +```server-python +Query.starts_with("metadata.title", "Iron") +``` +```server-ruby +Query.starts_with("metadata.title", "Iron") +``` +```server-deno +Query.startsWith("metadata.title", "Iron") +``` +```server-php +Query::startsWith("metadata.title", "Iron") +``` +```client-apple +Query.startsWith("metadata.title", value: "Iron") +``` +```server-go +query.StartsWith("metadata.title", "Iron") +``` +```server-nodejs +Query.startsWith("metadata.title", "Iron") +``` +```server-rust +Query::starts_with("metadata.title", "Iron").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"startsWith","attribute":"metadata.title","values":["Iron"]}' +``` +```http +{"method":"startsWith","attribute":"metadata.title","values":["Iron"]} +``` +{% /multicode %} + +### Not starts with {% #not-starts-with %} + +Returns documents if a metadata string field does not start with a substring. + +{% multicode %} +```client-web +Query.notStartsWith("metadata.title", "Iron") +``` +```client-flutter +Query.notStartsWith("metadata.title", "Iron") +``` +```client-apple +Query.notStartsWith("metadata.title", value: "Iron") +``` +```client-android-kotlin +Query.notStartsWith("metadata.title", "Iron") +``` +```client-android-java +Query.notStartsWith("metadata.title", "Iron") +``` +```server-python +Query.not_starts_with("metadata.title", "Iron") +``` +```server-ruby +Query.not_starts_with("metadata.title", "Iron") +``` +```server-deno +Query.notStartsWith("metadata.title", "Iron") +``` +```server-nodejs +Query.notStartsWith("metadata.title", "Iron") +``` +```server-php +Query::notStartsWith("metadata.title", "Iron") +``` +```server-swift +Query.notStartsWith("metadata.title", value: "Iron") +``` +```server-rust +Query::not_starts_with("metadata.title", "Iron").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"notStartsWith","attribute":"metadata.title","values":["Iron"]}' +``` +```http +{"method":"notStartsWith","attribute":"metadata.title","values":["Iron"]} +``` +{% /multicode %} + +### Ends with {% #ends-with %} + +Returns documents if a metadata string field ends with a substring. + +{% multicode %} +```client-web +Query.endsWith("metadata.title", "Man") +``` +```client-flutter +Query.endsWith("metadata.title", "Man") +``` +```server-python +Query.ends_with("metadata.title", "Man") +``` +```server-ruby +Query.ends_with("metadata.title", "Man") +``` +```server-deno +Query.endsWith("metadata.title", "Man") +``` +```server-php +Query::endsWith("metadata.title", "Man") +``` +```client-apple +Query.endsWith("metadata.title", value: "Man") +``` +```server-go +query.EndsWith("metadata.title", "Man") +``` +```server-nodejs +Query.endsWith("metadata.title", "Man") +``` +```server-rust +Query::ends_with("metadata.title", "Man").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"endsWith","attribute":"metadata.title","values":["Man"]}' +``` +```http +{"method":"endsWith","attribute":"metadata.title","values":["Man"]} +``` +{% /multicode %} + +### Not ends with {% #not-ends-with %} + +Returns documents if a metadata string field does not end with a substring. + +{% multicode %} +```client-web +Query.notEndsWith("metadata.title", "Man") +``` +```client-flutter +Query.notEndsWith("metadata.title", "Man") +``` +```client-apple +Query.notEndsWith("metadata.title", value: "Man") +``` +```client-android-kotlin +Query.notEndsWith("metadata.title", "Man") +``` +```client-android-java +Query.notEndsWith("metadata.title", "Man") +``` +```server-python +Query.not_ends_with("metadata.title", "Man") +``` +```server-ruby +Query.not_ends_with("metadata.title", "Man") +``` +```server-deno +Query.notEndsWith("metadata.title", "Man") +``` +```server-nodejs +Query.notEndsWith("metadata.title", "Man") +``` +```server-php +Query::notEndsWith("metadata.title", "Man") +``` +```server-swift +Query.notEndsWith("metadata.title", value: "Man") +``` +```server-rust +Query::not_ends_with("metadata.title", "Man").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"notEndsWith","attribute":"metadata.title","values":["Man"]}' +``` +```http +{"method":"notEndsWith","attribute":"metadata.title","values":["Man"]} +``` +{% /multicode %} + +### Contains {% #contains %} + +Returns documents if a metadata array field contains the specified elements or if a metadata string field contains the specified substring. + +{% multicode %} +```client-web +// For arrays +Query.contains("metadata.tags", ["space"]) + +// For strings +Query.contains("metadata.title", "Iron") +``` +```client-flutter +// For arrays +Query.contains("metadata.tags", ["space"]) + +// For strings +Query.contains("metadata.title", "Iron") +``` +```server-python +# For arrays +Query.contains("metadata.tags", ["space"]) + +# For strings +Query.contains("metadata.title", "Iron") +``` +```server-ruby +# For arrays +Query.contains("metadata.tags", ["space"]) + +# For strings +Query.contains("metadata.title", "Iron") +``` +```server-deno +// For arrays +Query.contains("metadata.tags", ["space"]) + +// For strings +Query.contains("metadata.title", "Iron") +``` +```server-php +// For arrays +Query::contains("metadata.tags", ["space"]) + +// For strings +Query::contains("metadata.title", "Iron") +``` +```client-apple +// For arrays +Query.contains("metadata.tags", value: ["space"]) + +// For strings +Query.contains("metadata.title", value: "Iron") +``` +```server-go +// For arrays +query.Contains("metadata.tags", []string{"space"}) + +// For strings +query.Contains("metadata.title", "Iron") +``` +```server-nodejs +// For arrays +Query.contains("metadata.tags", ["space"]) + +// For strings +Query.contains("metadata.title", "Iron") +``` +```server-rust +// For arrays +Query::contains("metadata.tags", Value::Array(vec![ + Value::String("space".to_string()), +])).to_string() + +// For strings +Query::contains("metadata.title", "Iron").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"contains","attribute":"metadata.tags","values":["space"]}' +``` +```http +# For arrays +{"method":"contains","attribute":"metadata.tags","values":["space"]} + +# For strings +{"method":"contains","attribute":"metadata.title","values":["Iron"]} +``` +{% /multicode %} + +### Not contains {% #not-contains %} + +Returns documents if a metadata array field does not contain the specified elements, or if a metadata string field does not contain the specified substring. + +{% multicode %} +```client-web +// For arrays +Query.notContains("metadata.tags", ["space"]) + +// For strings +Query.notContains("metadata.title", "Iron") +``` +```client-flutter +// For arrays +Query.notContains("metadata.tags", ["space"]) + +// For strings +Query.notContains("metadata.title", "Iron") +``` +```client-react-native +// For arrays +Query.notContains("metadata.tags", ["space"]) + +// For strings +Query.notContains("metadata.title", "Iron") +``` +```client-apple +// For arrays +Query.notContains("metadata.tags", value: ["space"]) + +// For strings +Query.notContains("metadata.title", value: "Iron") +``` +```client-android-kotlin +// For arrays +Query.notContains("metadata.tags", listOf("space")) + +// For strings +Query.notContains("metadata.title", "Iron") +``` +```client-android-java +// For arrays +Query.notContains("metadata.tags", Arrays.asList("space")) + +// For strings +Query.notContains("metadata.title", "Iron") +``` +```server-python +# For arrays +Query.not_contains("metadata.tags", ["space"]) + +# For strings +Query.not_contains("metadata.title", "Iron") +``` +```server-ruby +# For arrays +Query.not_contains("metadata.tags", ["space"]) + +# For strings +Query.not_contains("metadata.title", "Iron") +``` +```server-deno +// For arrays +Query.notContains("metadata.tags", ["space"]) + +// For strings +Query.notContains("metadata.title", "Iron") +``` +```server-nodejs +// For arrays +Query.notContains("metadata.tags", ["space"]) + +// For strings +Query.notContains("metadata.title", "Iron") +``` +```server-php +// For arrays +Query::notContains("metadata.tags", ["space"]) + +// For strings +Query::notContains("metadata.title", "Iron") +``` +```server-dotnet +// For arrays +Query.NotContains("metadata.tags", new List<string> { "space" }) + +// For strings +Query.NotContains("metadata.title", "Iron") +``` +```server-go +// For arrays +query.NotContains("metadata.tags", []string{"space"}) + +// For strings +query.NotContains("metadata.title", "Iron") +``` +```server-dart +// For arrays +Query.notContains("metadata.tags", ["space"]) + +// For strings +Query.notContains("metadata.title", "Iron") +``` +```server-swift +// For arrays +Query.notContains("metadata.tags", value: ["space"]) + +// For strings +Query.notContains("metadata.title", value: "Iron") +``` +```server-kotlin +// For arrays +Query.notContains("metadata.tags", listOf("space")) + +// For strings +Query.notContains("metadata.title", "Iron") +``` +```server-rust +// For arrays +Query::not_contains("metadata.tags", Value::Array(vec![ + Value::String("space".to_string()), +])).to_string() + +// For strings +Query::not_contains("metadata.title", "Iron").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"notContains","attribute":"metadata.tags","values":["space"]}' +``` +```http +# For arrays +{"method":"notContains","attribute":"metadata.tags","values":["space"]} + +# For strings +{"method":"notContains","attribute":"metadata.title","values":["Iron"]} +``` +{% /multicode %} + +## Logical operators {% #logical-operators %} + +### AND {% #and %} + +Returns document if it matches all of the nested sub-queries in the array passed in. + +{% multicode %} +```client-web +Query.and([ + Query.equal("metadata.genre", "sci-fi"), + Query.startsWith("metadata.title", "Inter") +]) +``` +```client-flutter +Query.and([ + Query.equal("metadata.genre", "sci-fi"), + Query.startsWith("metadata.title", "Inter") +]) +``` +```server-python +Query.and_queries([ + Query.equal("metadata.genre", "sci-fi"), + Query.starts_with("metadata.title", "Inter") +]) +``` +```server-ruby +Query.and([ + Query.equal("metadata.genre", "sci-fi"), + Query.starts_with("metadata.title", "Inter") +]) +``` +```server-deno +Query.and([ + Query.equal("metadata.genre", "sci-fi"), + Query.startsWith("metadata.title", "Inter") +]) +``` +```server-php +Query::and([ + Query::equal("metadata.genre", "sci-fi"), + Query::startsWith("metadata.title", "Inter") +]) +``` +```client-apple +Query.and([ + Query.equal("metadata.genre", value: "sci-fi"), + Query.startsWith("metadata.title", value: "Inter") +]) +``` +```server-go +query.And([]string{ + query.Equal("metadata.genre", []string{"sci-fi"}), + query.StartsWith("metadata.title", "Inter"), +}) +``` +```server-nodejs +Query.and([ + Query.equal("metadata.genre", "sci-fi"), + Query.startsWith("metadata.title", "Inter") +]) +``` +```server-rust +Query::and(vec![ + Query::equal("metadata.genre", Value::String("sci-fi".to_string())).to_string(), + Query::starts_with("metadata.title", "Inter").to_string(), +]).to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"and","values":[{"method":"equal","attribute":"metadata.genre","values":["sci-fi"]},{"method":"startsWith","attribute":"metadata.title","values":["Inter"]}]}' +``` +```http +{"method":"and","values":[{"method":"equal","attribute":"metadata.genre","values":["sci-fi"]},{"method":"startsWith","attribute":"metadata.title","values":["Inter"]}]} +``` +{% /multicode %} + +### OR {% #or %} + +Returns document if it matches any of the nested sub-queries in the array passed in. + +{% multicode %} +```client-web +Query.or([ + Query.equal("metadata.genre", "romance"), + Query.equal("metadata.genre", "sci-fi") +]) +``` +```client-flutter +Query.or([ + Query.equal("metadata.genre", "romance"), + Query.equal("metadata.genre", "sci-fi") +]) +``` +```server-python +Query.or_queries([ + Query.equal("metadata.genre", "romance"), + Query.equal("metadata.genre", "sci-fi") +]) +``` +```server-ruby +Query.or([ + Query.equal("metadata.genre", "romance"), + Query.equal("metadata.genre", "sci-fi") +]) +``` +```server-deno +Query.or([ + Query.equal("metadata.genre", "romance"), + Query.equal("metadata.genre", "sci-fi") +]) +``` +```server-php +Query::or([ + Query::equal("metadata.genre", "romance"), + Query::equal("metadata.genre", "sci-fi") +]) +``` +```client-apple +Query.or([ + Query.equal("metadata.genre", value: "romance"), + Query.equal("metadata.genre", value: "sci-fi") +]) +``` +```server-go +query.Or([]string{ + query.Equal("metadata.genre", []string{"romance"}), + query.Equal("metadata.genre", []string{"sci-fi"}), +}) +``` +```server-nodejs +Query.or([ + Query.equal("metadata.genre", "romance"), + Query.equal("metadata.genre", "sci-fi") +]) +``` +```server-rust +Query::or(vec![ + Query::equal("metadata.genre", Value::String("romance".to_string())).to_string(), + Query::equal("metadata.genre", Value::String("sci-fi".to_string())).to_string(), +]).to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"or","values":[{"method":"equal","attribute":"metadata.genre","values":["romance"]},{"method":"equal","attribute":"metadata.genre","values":["sci-fi"]}]}' +``` +```http +{"method":"or","values":[{"method":"equal","attribute":"metadata.genre","values":["romance"]},{"method":"equal","attribute":"metadata.genre","values":["sci-fi"]}]} +``` +{% /multicode %} + +## Ordering {% #ordering %} + +Order results by a top-level document attribute such as `$createdAt`, `$updatedAt`, or `$id`. Ordering by a nested `metadata` field is not supported. + +### Order descending {% #order-desc %} + +Orders results in descending order by attribute. + +{% multicode %} +```client-web +Query.orderDesc("$createdAt") +``` +```client-flutter +Query.orderDesc("$createdAt") +``` +```server-python +Query.order_desc("$createdAt") +``` +```server-ruby +Query.order_desc("$createdAt") +``` +```server-nodejs +Query.orderDesc("$createdAt") +``` +```server-php +Query::orderDesc("$createdAt") +``` +```client-apple +Query.orderDesc("$createdAt") +``` +```server-go +query.OrderDesc("$createdAt") +``` +```server-rust +Query::order_desc("$createdAt").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"orderDesc","attribute":"$createdAt"}' +``` +```http +{"method":"orderDesc","attribute":"$createdAt"} +``` +{% /multicode %} + +### Order ascending {% #order-asc %} + +Orders results in ascending order by attribute. + +{% multicode %} +```client-web +Query.orderAsc("$createdAt") +``` +```client-flutter +Query.orderAsc("$createdAt") +``` +```server-python +Query.order_asc("$createdAt") +``` +```server-ruby +Query.order_asc("$createdAt") +``` +```server-nodejs +Query.orderAsc("$createdAt") +``` +```server-php +Query::orderAsc("$createdAt") +``` +```client-apple +Query.orderAsc("$createdAt") +``` +```server-go +query.OrderAsc("$createdAt") +``` +```server-rust +Query::order_asc("$createdAt").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"orderAsc","attribute":"$createdAt"}' +``` +```http +{"method":"orderAsc","attribute":"$createdAt"} +``` +{% /multicode %} + +### Order random {% #order-random %} + +Orders results in random order. + +{% multicode %} +```client-web +Query.orderRandom() +``` +```client-flutter +Query.orderRandom() +``` +```server-python +Query.order_random() +``` +```server-ruby +Query.order_random() +``` +```server-nodejs +Query.orderRandom() +``` +```server-php +Query::orderRandom() +``` +```client-apple +Query.orderRandom() +``` +```server-go +query.OrderRandom() +``` +```server-rust +Query::order_random().to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"orderRandom"}' +``` +```http +{"method":"orderRandom"} +``` +{% /multicode %} + +## Pagination {% #pagination %} + +### Limit {% #limit %} + +Limits the number of results returned by the query. Used for [pagination](/docs/products/databases/vectorsdb/pagination). + +{% multicode %} +```client-web +Query.limit(25) +``` +```client-flutter +Query.limit(25) +``` +```server-python +Query.limit(25) +``` +```server-ruby +Query.limit(25) +``` +```server-deno +Query.limit(25) +``` +```server-php +Query::limit(25) +``` +```client-apple +Query.limit(25) +``` +```server-go +query.Limit(25) +``` +```server-nodejs +Query.limit(25) +``` +```server-rust +Query::limit(25).to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"limit","values":[25]}' +``` +```http +{"method":"limit","values":[25]} +``` +{% /multicode %} + +### Offset {% #offset %} + +Offset the results returned by skipping some of the results. Used for [pagination](/docs/products/databases/vectorsdb/pagination). + +{% multicode %} +```client-web +Query.offset(0) +``` +```client-flutter +Query.offset(0) +``` +```server-python +Query.offset(0) +``` +```server-ruby +Query.offset(0) +``` +```server-deno +Query.offset(0) +``` +```server-php +Query::offset(0) +``` +```client-apple +Query.offset(0) +``` +```server-go +query.Offset(0) +``` +```server-nodejs +Query.offset(0) +``` +```server-rust +Query::offset(0).to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"offset","values":[0]}' +``` +```http +{"method":"offset","values":[0]} +``` +{% /multicode %} + +### Cursor after {% #cursor-after %} + +Places the cursor after the specified document ID. Used for [pagination](/docs/products/databases/vectorsdb/pagination). + +{% multicode %} +```client-web +Query.cursorAfter("62a7...f620") +``` +```client-flutter +Query.cursorAfter("62a7...f620") +``` +```server-python +Query.cursor_after("62a7...f620") +``` +```server-ruby +Query.cursor_after("62a7...f620") +``` +```server-deno +Query.cursorAfter("62a7...f620") +``` +```server-php +Query::cursorAfter("62a7...f620") +``` +```client-apple +Query.cursorAfter("62a7...f620") +``` +```server-go +query.CursorAfter("62a7...f620") +``` +```server-nodejs +Query.cursorAfter("62a7...f620") +``` +```server-rust +Query::cursor_after("62a7...f620").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"cursorAfter","values":["62a7...f620"]}' +``` +```http +{"method":"cursorAfter","values":["62a7...f620"]} +``` +{% /multicode %} + +### Cursor before {% #cursor-before %} + +Places the cursor before the specified document ID. Used for [pagination](/docs/products/databases/vectorsdb/pagination). + +{% multicode %} +```client-web +Query.cursorBefore("62a7...a600") +``` +```client-flutter +Query.cursorBefore("62a7...a600") +``` +```server-python +Query.cursor_before("62a7...a600") +``` +```server-ruby +Query.cursor_before("62a7...a600") +``` +```server-deno +Query.cursorBefore("62a7...a600") +``` +```server-php +Query::cursorBefore("62a7...a600") +``` +```client-apple +Query.cursorBefore("62a7...a600") +``` +```server-go +query.CursorBefore("62a7...a600") +``` +```server-nodejs +Query.cursorBefore("62a7...a600") +``` +```server-rust +Query::cursor_before("62a7...a600").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"cursorBefore","values":["62a7...a600"]}' +``` +```http +{"method":"cursorBefore","values":["62a7...a600"]} +``` +{% /multicode %} + +# Complex queries {% #complex-queries %} + +You can create complex queries by combining AND and OR operations. For example, to find documents that are either sci-fi released after 2010 or romance released before 2005: + +{% multicode %} +```client-web +const results = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.or([ + Query.and([ + Query.equal('metadata.genre', ['sci-fi']), + Query.greaterThan('metadata.year', '2010') + ]), + Query.and([ + Query.equal('metadata.genre', ['romance']), + Query.lessThan('metadata.year', '2005') + ]) + ]) + ] +}); +``` +```client-flutter +final results = await vectorsDB.listDocuments( + '<DATABASE_ID>', + '<COLLECTION_ID>', + [ + Query.or([ + Query.and([ + Query.equal('metadata.genre', ['sci-fi']), + Query.greaterThan('metadata.year', '2010') + ]), + Query.and([ + Query.equal('metadata.genre', ['romance']), + Query.lessThan('metadata.year', '2005') + ]) + ]) + ] +); +``` +```server-nodejs +const results = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.or([ + Query.and([ + Query.equal('metadata.genre', ['sci-fi']), + Query.greaterThan('metadata.year', '2010') + ]), + Query.and([ + Query.equal('metadata.genre', ['romance']), + Query.lessThan('metadata.year', '2005') + ]) + ]) + ] +}); +``` +```server-python +results = vectorsDB.list_documents( + database_id='<DATABASE_ID>', + collection_id='<COLLECTION_ID>', + queries=[ + Query.or_queries([ + Query.and_queries([ + Query.equal('metadata.genre', ['sci-fi']), + Query.greater_than('metadata.year', '2010') + ]), + Query.and_queries([ + Query.equal('metadata.genre', ['romance']), + Query.less_than('metadata.year', '2005') + ]) + ]) + ] +) +``` +```server-go +documents, err := vectorsDB.ListDocuments( + "<DATABASE_ID>", + "<COLLECTION_ID>", + vectorsDB.WithListDocumentsQueries([]string{ + query.Or([]string{ + query.And([]string{ + query.Equal("metadata.genre", []string{"sci-fi"}), + query.GreaterThan("metadata.year", "2010"), + }), + query.And([]string{ + query.Equal("metadata.genre", []string{"romance"}), + query.LessThan("metadata.year", "2005"), + }), + }), + }), +) +if err != nil { + log.Fatal(err) +} +``` +```server-rust +let documents = vectors_db.list_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![ + Query::or(vec![ + Query::and(vec![ + Query::equal("metadata.genre", Value::Array(vec![Value::String("sci-fi".to_string())])).to_string(), + Query::greater_than("metadata.year", "2010").to_string(), + ]).to_string(), + Query::and(vec![ + Query::equal("metadata.genre", Value::Array(vec![Value::String("romance".to_string())])).to_string(), + Query::less_than("metadata.year", "2005").to_string(), + ]).to_string(), + ]).to_string(), + ]), + None, + None, + None, +).await?; +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"or","values":[{"method":"and","values":[{"method":"equal","attribute":"metadata.genre","values":["sci-fi"]},{"method":"greaterThan","attribute":"metadata.year","values":["2010"]}]},{"method":"and","values":[{"method":"equal","attribute":"metadata.genre","values":["romance"]},{"method":"lessThan","attribute":"metadata.year","values":["2005"]}]}]}' +``` +```http +{"method":"or","values":[{"method":"and","values":[{"method":"equal","attribute":"metadata.genre","values":["sci-fi"]},{"method":"greaterThan","attribute":"metadata.year","values":["2010"]}]},{"method":"and","values":[{"method":"equal","attribute":"metadata.genre","values":["romance"]},{"method":"lessThan","attribute":"metadata.year","values":["2005"]}]}]} +``` +{% /multicode %} + +This example demonstrates how to combine `OR` and `AND` operations. The query uses `Query.or()` to match either condition: sci-fi released after 2010 OR romance released before 2005. +Each condition within the OR is composed of two AND conditions, one for the genre and one for the year. The database returns documents that match either of these combined conditions. + +{% arrow_link href="/docs/products/databases/vectorsdb/vector-search" %} +Learn about vector search +{% /arrow_link %} diff --git a/src/routes/docs/products/databases/vectorsdb/quick-start/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/quick-start/+page.markdoc new file mode 100644 index 00000000000..3586d5bc965 --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/quick-start/+page.markdoc @@ -0,0 +1,1034 @@ +--- +layout: article +title: Start with VectorsDB +description: Get started with Appwrite VectorsDB. Follow a step-by-step guide to create your first database, add a collection with a fixed dimension, store embeddings with metadata, and read them back. +--- + + +VectorsDB stores embedding vectors so you can build features like semantic search, recommendations, and retrieval for AI applications. +This guide walks through creating a database, adding a collection with a fixed `dimension`, storing a document with its `embeddings` and `metadata`, and reading it back. + +These steps use a [Server SDK](/docs/sdks#server), which requires an [API key](/docs/advanced/platform/api-keys). + +{% section #create-database step=1 title="Create database" %} +Head to your [Appwrite Console](https://cloud.appwrite.io/console/) and click **Create database**. Name it `Knowledge base` and choose **VectorsDB** as the database type. +Optionally, add a custom database ID. Select your preferred tier, then click **Create database**. + +You can also create a database programmatically with the `create` method. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.create({ + databaseId: sdk.ID.unique(), + name: 'Knowledge base' +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.create({ + databaseId: sdk.ID.unique(), + name: 'Knowledge base' +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\ID; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->create( + databaseId: ID::unique(), + name: 'Knowledge base' +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.id import ID + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.create( + database_id = ID.unique(), + name = 'Knowledge base' +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.create( + database_id: ID.unique(), + name: 'Knowledge base' +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Database result = await vectorsDB.Create( + databaseId: ID.Unique(), + name: "Knowledge base" +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Database result = await vectorsDB.create( + databaseId: ID.unique(), + name: 'Knowledge base', +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.ID +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.create( + databaseId = ID.unique(), + name = "Knowledge base", +) +``` +```java +import io.appwrite.Client; +import io.appwrite.ID; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.create( + ID.unique(), + "Knowledge base", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let database = try await vectorsDB.create( + databaseId: ID.unique(), + name: "Knowledge base" +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::id::ID; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create( + ID::unique(), + "Knowledge base", + None, // enabled (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db create \ + --database-id <DATABASE_ID> \ + --name "Knowledge base" +``` +{% /multicode %} +{% /section %} + +{% section #create-collection step=2 title="Create collection" %} +In the `Knowledge base` database, click **Create collection** and name it `Articles`. Optionally, add a custom collection ID. + +A VectorsDB collection has a fixed `dimension`, the length of the embedding vectors it holds. All documents in the collection must use vectors of that exact length, so set the `dimension` to match the embedding model you plan to use. This guide uses `dimension: 4` to keep the vectors short and readable. The default `nomic-embed-text` [embedding model](/docs/products/databases/vectorsdb/embeddings) produces 768-dimensional vectors, so a production collection would use `dimension: 768`. + +The schema is provisioned for you: an `embeddings` vector and an optional `metadata` object. There are no columns to define. + +Set the **dimension**, then add a **Read** permission for the **Any** role so anyone can read documents. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createCollection({ + databaseId: '<DATABASE_ID>', + collectionId: sdk.ID.unique(), + name: 'Articles', + dimension: 4, + permissions: [sdk.Permission.read(sdk.Role.any())] // optional +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createCollection({ + databaseId: '<DATABASE_ID>', + collectionId: sdk.ID.unique(), + name: 'Articles', + dimension: 4, + permissions: [sdk.Permission.read(sdk.Role.any())] // optional +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\ID; +use Appwrite\Permission; +use Appwrite\Role; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->createCollection( + databaseId: '<DATABASE_ID>', + collectionId: ID::unique(), + name: 'Articles', + dimension: 4, + permissions: [Permission::read(Role::any())] // optional +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.id import ID +from appwrite.permission import Permission +from appwrite.role import Role + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.create_collection( + database_id = '<DATABASE_ID>', + collection_id = ID.unique(), + name = 'Articles', + dimension = 4, + permissions = [Permission.read(Role.any())] # optional +) +``` +```ruby +require 'appwrite' + +include Appwrite +include Appwrite::Permission +include Appwrite::Role + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.create_collection( + database_id: '<DATABASE_ID>', + collection_id: ID.unique(), + name: 'Articles', + dimension: 4, + permissions: [Permission.read(Role.any())] # optional +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Collection result = await vectorsDB.CreateCollection( + databaseId: "<DATABASE_ID>", + collectionId: ID.Unique(), + name: "Articles", + dimension: 4, + permissions: new List<string> { Permission.Read(Role.Any()) } // optional +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Collection result = await vectorsDB.createCollection( + databaseId: '<DATABASE_ID>', + collectionId: ID.unique(), + name: 'Articles', + dimension: 4, + permissions: [Permission.read(Role.any())], // optional +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.ID +import io.appwrite.Permission +import io.appwrite.Role +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.createCollection( + databaseId = "<DATABASE_ID>", + collectionId = ID.unique(), + name = "Articles", + dimension = 4, + permissions = listOf(Permission.read(Role.any())), // optional +) +``` +```java +import io.appwrite.Client; +import io.appwrite.ID; +import io.appwrite.Permission; +import io.appwrite.Role; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.createCollection( + "<DATABASE_ID>", + ID.unique(), + "Articles", + 4, + List.of(Permission.read(Role.any())), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let collection = try await vectorsDB.createCollection( + databaseId: "<DATABASE_ID>", + collectionId: ID.unique(), + name: "Articles", + dimension: 4, + permissions: [Permission.read(Role.any())] // optional +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::id::ID; +use appwrite::permission::Permission; +use appwrite::role::Role; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create_collection( + "<DATABASE_ID>", + ID::unique(), + "Articles", + 4, + Some(vec![Permission::read(Role::any()).to_string()]), // permissions (optional) + None, // documentSecurity (optional) + None, // enabled (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db create-collection \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --name "Articles" \ + --dimension 4 \ + --permissions 'read("any")' +``` +{% /multicode %} +{% /section %} + +{% section #store-documents step=3 title="Store documents" %} +A document's data is an `embeddings` vector and an optional `metadata` object. +The `embeddings` array length must equal the collection's `dimension`, and `metadata` is free-form JSON you can use to store any data you want to keep alongside the vector. + +The vectors here are hardcoded so the example runs on its own. In an application, you generate them from your text with [embeddings](/docs/products/databases/vectorsdb/embeddings). + +Use the `createDocument` method to store the embedding and its metadata. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: sdk.ID.unique(), + data: { + embeddings: [0.12, 0.84, 0.33, 0.57], + metadata: { title: 'Getting started with Appwrite', tags: ['guide'] } + } +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: sdk.ID.unique(), + data: { + embeddings: [0.12, 0.84, 0.33, 0.57], + metadata: { title: 'Getting started with Appwrite', tags: ['guide'] } + } +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\ID; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: ID::unique(), + data: [ + 'embeddings' => [0.12, 0.84, 0.33, 0.57], + 'metadata' => ['title' => 'Getting started with Appwrite', 'tags' => ['guide']] + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.id import ID + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.create_document( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = ID.unique(), + data = { + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": { "title": "Getting started with Appwrite", "tags": ["guide"] } + } +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.create_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: ID.unique(), + data: { + "embeddings" => [0.12, 0.84, 0.33, 0.57], + "metadata" => { "title" => "Getting started with Appwrite", "tags" => ["guide"] } + } +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Document result = await vectorsDB.CreateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: ID.Unique(), + data: new Dictionary<string, object> { + { "embeddings", new List<double> { 0.12, 0.84, 0.33, 0.57 } }, + { "metadata", new Dictionary<string, object> { { "title", "Getting started with Appwrite" }, { "tags", new List<string> { "guide" } } } } + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Document result = await vectorsDB.createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: ID.unique(), + data: { + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": { "title": "Getting started with Appwrite", "tags": ["guide"] } + }, +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.ID +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.createDocument( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = ID.unique(), + data = mapOf( + "embeddings" to listOf(0.12, 0.84, 0.33, 0.57), + "metadata" to mapOf("title" to "Getting started with Appwrite", "tags" to listOf("guide")) + ), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.ID; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; +import java.util.Map; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.createDocument( + "<DATABASE_ID>", + "<COLLECTION_ID>", + ID.unique(), + Map.of( + "embeddings", List.of(0.12, 0.84, 0.33, 0.57), + "metadata", Map.of("title", "Getting started with Appwrite", "tags", List.of("guide")) + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let document = try await vectorsDB.createDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: ID.unique(), + data: [ + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": ["title": "Getting started with Appwrite", "tags": ["guide"]] + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::id::ID; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + ID::unique(), + json!({ + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": { "title": "Getting started with Appwrite", "tags": ["guide"] } + }), + None, // permissions (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db create-document \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --document-id 'unique()' \ + --data '{ "embeddings": [0.12, 0.84, 0.33, 0.57], "metadata": { "title": "Getting started with Appwrite", "tags": ["guide"] } }' +``` +{% /multicode %} + +The response includes the stored `embeddings` and `metadata` alongside the document's system fields. + +```json +{ + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": { "title": "Getting started with Appwrite", "tags": ["guide"] }, + "$id": "6a43c07100195469a012", + "$sequence": "1", + "$createdAt": "2026-06-30T13:11:13.441+00:00", + "$updatedAt": "2026-06-30T13:11:13.441+00:00", + "$permissions": [], + "$databaseId": "650125c64b3c25ce4bc4", + "$collectionId": "650125cff227cf9f95ad" +} +``` +{% /section %} + +{% section #read-documents step=4 title="Read documents" %} +To read documents back from your collection, use the `listDocuments` method. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.limit(10) + ] +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.limit(10) + ] +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Query; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query::limit(10) + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.list_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + queries = [ + Query.limit(10) + ] +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.list_documents( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + queries: [ + Query.limit(10) + ] +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; +using Appwrite.Queries; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +DocumentList result = await vectorsDB.ListDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: new List<string> { + Query.Limit(10) + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +DocumentList result = await vectorsDB.listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.limit(10) + ], +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.listDocuments( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + queries = listOf( + Query.limit(10) + ), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.Query; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.listDocuments( + "<DATABASE_ID>", + "<COLLECTION_ID>", + List.of( + Query.limit(10) + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let documents = try await vectorsDB.listDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: [ + Query.limit(10) + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::query::Query; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.list_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![Query::limit(10).to_string()]), + None, // transactionId (optional) + None, // total (optional) + None, // ttl (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"limit","values":[10]}' +``` +{% /multicode %} +{% /section %} + +{% section #next-steps step=5 title="Next steps" %} +You now have a database, a collection, and a document holding an embedding with its metadata. From here: + +- Generate vectors from your text instead of hardcoding them with [embeddings](/docs/products/databases/vectorsdb/embeddings). +- Find the documents closest to a query vector with [vector search](/docs/products/databases/vectorsdb/vector-search). + +{% arrow_link href="/docs/products/databases/vectorsdb/vector-search" %} +Run vector search +{% /arrow_link %} +{% /section %} diff --git a/src/routes/docs/products/databases/vectorsdb/timestamp-overrides/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/timestamp-overrides/+page.markdoc new file mode 100644 index 00000000000..2956294d759 --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/timestamp-overrides/+page.markdoc @@ -0,0 +1,1302 @@ +--- +layout: article +title: Timestamp overrides +description: Set custom $createdAt and $updatedAt timestamps for your documents when using server SDKs. +--- + +When creating or updating documents, Appwrite automatically sets `$createdAt` and `$updatedAt` timestamps. However, there are scenarios where you might need to set these timestamps manually, such as when migrating data from another system or backfilling historical records. + +{% info title="Server SDKs required" %} +To manually set `$createdAt` and `$updatedAt`, you must use a **server SDK** with an **API key**. These attributes can be passed inside the `data` parameter on any of the create, update, or upsert routes (single or bulk). +{% /info %} + +# Setting custom timestamps {% #setting-custom-timestamps %} + +You can override a document's timestamps by providing ISO 8601 strings (for example, `2025-08-10T12:34:56.000Z`) in the `data` payload. If these attributes are not provided, Appwrite will set them automatically. + +Custom timestamps work with all document operations: create, update, upsert, and their bulk variants. + +## Single document operations {% #single-document-operations %} + +When working with individual documents, you can set custom timestamps during create, update, and upsert operations. + +### Create with custom timestamps {% #create-custom %} + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<API_KEY>'); + +const vectorsDB = new sdk.VectorsDB(client); + +await vectorsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: sdk.ID.unique(), + data: { + '$createdAt': new Date('2025-08-10T12:34:56.000Z').toISOString(), + '$updatedAt': new Date('2025-08-10T12:34:56.000Z').toISOString(), + embeddings: [0.12, 0.84, 0.33, 0.57], + metadata: { title: 'Hamlet' } + } +}); +``` +```server-php +use Appwrite\Client; +use Appwrite\ID; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<YOUR_PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$vectorsDB = new VectorsDB($client); + +$vectorsDB->createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: ID::unique(), + data: [ + '$createdAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM), + '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM), + 'embeddings' => [0.12, 0.84, 0.33, 0.57], + 'metadata' => ['title' => 'Hamlet'] + ] +); +``` +```server-swift +import Appwrite +import Foundation + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<YOUR_PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let vectorsDB = VectorsDB(client) + +let isoFormatter = ISO8601DateFormatter() +isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] +let customDate = isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date() +let createdAt = isoFormatter.string(from: customDate) +let updatedAt = isoFormatter.string(from: customDate) + +do { + let created = try await vectorsDB.createDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + data: [ + "$createdAt": createdAt, + "$updatedAt": updatedAt, + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": ["title": "Hamlet"] + ] + ) + print("Created:", created) +} catch { + print("Create error:", error) +} +``` +```server-python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.id import ID +from datetime import datetime, timezone + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<API_KEY>') + +vectors_db = VectorsDB(client) + +iso = datetime(2025, 8, 10, 12, 34, 56, tzinfo=timezone.utc).isoformat() + +vectors_db.create_document( + database_id='<DATABASE_ID>', + collection_id='<COLLECTION_ID>', + document_id=ID.unique(), + data={ + '$createdAt': iso, + '$updatedAt': iso, + 'embeddings': [0.12, 0.84, 0.33, 0.57], + 'metadata': { 'title': 'Hamlet' } + } +) +``` +```server-ruby +require 'appwrite' +require 'time' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<YOUR_PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +vectors_db = VectorsDB.new(client) + +custom_date = Time.parse('2025-08-10T12:34:56.000Z').iso8601 + +vectors_db.create_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: ID.unique(), + data: { + '$createdAt' => custom_date, + '$updatedAt' => custom_date, + 'embeddings' => [0.12, 0.84, 0.33, 0.57], + 'metadata' => { 'title' => 'Hamlet' } + } +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<YOUR_PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +VectorsDB vectorsDB = new VectorsDB(client); + +string customDate = DateTimeOffset.Parse("2025-08-10T12:34:56.000Z").ToString("O"); + +await vectorsDB.CreateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: ID.Unique(), + data: new Dictionary<string, object> + { + ["$createdAt"] = customDate, + ["$updatedAt"] = customDate, + ["embeddings"] = new List<double> { 0.12, 0.84, 0.33, 0.57 }, + ["metadata"] = new Dictionary<string, object> { ["title"] = "Hamlet" } + } +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<YOUR_PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +VectorsDB vectorsDB = VectorsDB(client); + +String customDate = DateTime.parse('2025-08-10T12:34:56.000Z').toIso8601String(); + +await vectorsDB.createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: ID.unique(), + data: { + '\$createdAt': customDate, + '\$updatedAt': customDate, + 'embeddings': [0.12, 0.84, 0.33, 0.57], + 'metadata': { 'title': 'Hamlet' } + }, +); +``` +```rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::id::ID; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); + client.set_project("<PROJECT_ID>"); + client.set_key("<API_KEY>"); + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + ID::unique(), + json!({ + "$createdAt": "2025-08-10T12:34:56.000Z", + "$updatedAt": "2025-08-10T12:34:56.000Z", + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": { "title": "Hamlet" } + }), + None, // permissions (optional) + ).await?; + + println!("Created: {:?}", result); + Ok(()) +} +``` +{% /multicode %} + +### Update with custom timestamps {% #update-custom %} + +When updating documents, you can also set a custom `$updatedAt` timestamp. The existing `$createdAt` is preserved unless you provide a new one: + +{% multicode %} +```server-nodejs +await vectorsDB.updateDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { + '$updatedAt': new Date('2025-08-10T12:34:56.000Z').toISOString(), + metadata: { title: 'Hamlet, revised' } + } +}); +``` +```server-php +$vectorsDB->updateDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: [ + '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM), + 'metadata' => ['title' => 'Hamlet, revised'] + ] +); +``` +```server-python +from datetime import datetime, timezone + +vectors_db.update_document( + database_id='<DATABASE_ID>', + collection_id='<COLLECTION_ID>', + document_id='<DOCUMENT_ID>', + data={ + '$updatedAt': datetime(2025, 8, 10, 12, 34, 56, tzinfo=timezone.utc).isoformat(), + 'metadata': { 'title': 'Hamlet, revised' } + } +) +``` +```server-swift +import Appwrite +import Foundation + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<YOUR_PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let vectorsDB = VectorsDB(client) + +let isoFormatter = ISO8601DateFormatter() +isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] +let updatedAt = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date()) + +do { + let updated = try await vectorsDB.updateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + data: [ + "$updatedAt": updatedAt, + "metadata": ["title": "Hamlet, revised"] + ] + ) + print("Updated:", updated) +} catch { + print("Update error:", error) +} +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<YOUR_PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +vectors_db = VectorsDB.new(client) + +custom_date = Time.parse('<CUSTOM_DATE>').iso8601 + +vectors_db.update_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: '<DOCUMENT_ID>', + data: { + '$updatedAt' => custom_date, + 'metadata' => { 'title' => 'Hamlet, revised' } + } +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<YOUR_PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +VectorsDB vectorsDB = new VectorsDB(client); + +string customDate = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O"); + +await vectorsDB.UpdateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + data: new Dictionary<string, object> + { + ["$updatedAt"] = customDate, + ["metadata"] = new Dictionary<string, object> { ["title"] = "Hamlet, revised" } + } +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<YOUR_PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +VectorsDB vectorsDB = VectorsDB(client); + +String customDate = DateTime.parse('<CUSTOM_DATE>').toIso8601String(); + +await vectorsDB.updateDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { + '\$updatedAt': customDate, + 'metadata': { 'title': 'Hamlet, revised' } + }, +); +``` +```rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); + client.set_project("<PROJECT_ID>"); + client.set_key("<API_KEY>"); + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.update_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + Some(json!({ + "$updatedAt": "2025-08-10T12:34:56.000Z", + "metadata": { "title": "Hamlet, revised" } + })), + None, // permissions (optional) + None, // transactionId (optional) + ).await?; + + println!("Updated: {:?}", result); + Ok(()) +} +``` +{% /multicode %} + +## Bulk operations {% #bulk-operations %} + +Custom timestamps also work with bulk operations, allowing you to set different timestamps for each document in the batch: + +### Bulk create {% #bulk-create %} + +{% multicode %} +```server-nodejs +await vectorsDB.createDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documents: [ + { + '$id': sdk.ID.unique(), + '$createdAt': new Date('2024-01-01T00:00:00.000Z').toISOString(), + '$updatedAt': new Date('2024-01-01T00:00:00.000Z').toISOString(), + embeddings: [0.1, 0.1, 0.1, 0.1], + metadata: { batch: 1 } + }, + { + '$id': sdk.ID.unique(), + '$createdAt': new Date('2024-02-01T00:00:00.000Z').toISOString(), + '$updatedAt': new Date('2024-02-01T00:00:00.000Z').toISOString(), + embeddings: [0.2, 0.2, 0.2, 0.2], + metadata: { batch: 2 } + } + ] +}); +``` +```server-python +vectors_db.create_documents( + database_id='<DATABASE_ID>', + collection_id='<COLLECTION_ID>', + documents=[ + { + '$id': ID.unique(), + '$createdAt': datetime(2024, 1, 1, tzinfo=timezone.utc).isoformat(), + '$updatedAt': datetime(2024, 1, 1, tzinfo=timezone.utc).isoformat(), + 'embeddings': [0.1, 0.1, 0.1, 0.1], + 'metadata': { 'batch': 1 } + }, + { + '$id': ID.unique(), + '$createdAt': datetime(2024, 2, 1, tzinfo=timezone.utc).isoformat(), + '$updatedAt': datetime(2024, 2, 1, tzinfo=timezone.utc).isoformat(), + 'embeddings': [0.2, 0.2, 0.2, 0.2], + 'metadata': { 'batch': 2 } + } + ] +) +``` +```server-php +use Appwrite\Client; +use Appwrite\ID; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<YOUR_PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$vectorsDB = new VectorsDB($client); + +$vectorsDB->createDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documents: [ + [ + '$id' => ID::unique(), + '$createdAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM), + '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM), + 'embeddings' => [0.1, 0.1, 0.1, 0.1], + 'metadata' => ['batch' => 1] + ], + [ + '$id' => ID::unique(), + '$createdAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM), + '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM), + 'embeddings' => [0.2, 0.2, 0.2, 0.2], + 'metadata' => ['batch' => 2] + ], + ] +); +``` +```server-swift +import Appwrite +import Foundation + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<YOUR_PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let vectorsDB = VectorsDB(client) + +let isoFormatter = ISO8601DateFormatter() +isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] + +let first = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date()) +let second = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date()) + +do { + let bulkCreated = try await vectorsDB.createDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documents: [ + [ + "$id": ID.unique(), + "$createdAt": first, + "$updatedAt": first, + "embeddings": [0.1, 0.1, 0.1, 0.1], + "metadata": ["batch": 1] + ], + [ + "$id": ID.unique(), + "$createdAt": second, + "$updatedAt": second, + "embeddings": [0.2, 0.2, 0.2, 0.2], + "metadata": ["batch": 2] + ] + ] + ) + print("Bulk create:", bulkCreated) +} catch { + print("Bulk create error:", error) +} +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<YOUR_PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +vectors_db = VectorsDB.new(client) + +first = Time.parse('<CUSTOM_DATE>').iso8601 +second = Time.parse('<CUSTOM_DATE>').iso8601 + +vectors_db.create_documents( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + documents: [ + { + '$id' => ID.unique(), + '$createdAt' => first, + '$updatedAt' => first, + 'embeddings' => [0.1, 0.1, 0.1, 0.1], + 'metadata' => { 'batch' => 1 } + }, + { + '$id' => ID.unique(), + '$createdAt' => second, + '$updatedAt' => second, + 'embeddings' => [0.2, 0.2, 0.2, 0.2], + 'metadata' => { 'batch' => 2 } + } + ] +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<YOUR_PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +VectorsDB vectorsDB = new VectorsDB(client); + +string first = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O"); +string second = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O"); + +await vectorsDB.CreateDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documents: new List<object> + { + new Dictionary<string, object> + { + ["$id"] = ID.Unique(), + ["$createdAt"] = first, + ["$updatedAt"] = first, + ["embeddings"] = new List<double> { 0.1, 0.1, 0.1, 0.1 }, + ["metadata"] = new Dictionary<string, object> { ["batch"] = 1 } + }, + new Dictionary<string, object> + { + ["$id"] = ID.Unique(), + ["$createdAt"] = second, + ["$updatedAt"] = second, + ["embeddings"] = new List<double> { 0.2, 0.2, 0.2, 0.2 }, + ["metadata"] = new Dictionary<string, object> { ["batch"] = 2 } + } + } +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<YOUR_PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +VectorsDB vectorsDB = VectorsDB(client); + +String first = DateTime.parse('<CUSTOM_DATE>').toIso8601String(); +String second = DateTime.parse('<CUSTOM_DATE>').toIso8601String(); + +await vectorsDB.createDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documents: [ + { + '\$id': ID.unique(), + '\$createdAt': first, + '\$updatedAt': first, + 'embeddings': [0.1, 0.1, 0.1, 0.1], + 'metadata': { 'batch': 1 } + }, + { + '\$id': ID.unique(), + '\$createdAt': second, + '\$updatedAt': second, + 'embeddings': [0.2, 0.2, 0.2, 0.2], + 'metadata': { 'batch': 2 } + } + ], +); +``` +```rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::id::ID; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); + client.set_project("<PROJECT_ID>"); + client.set_key("<API_KEY>"); + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + vec![ + json!({ + "$id": ID::unique(), + "$createdAt": "2024-01-01T00:00:00.000Z", + "$updatedAt": "2024-01-01T00:00:00.000Z", + "embeddings": [0.1, 0.1, 0.1, 0.1], + "metadata": { "batch": 1 } + }), + json!({ + "$id": ID::unique(), + "$createdAt": "2024-02-01T00:00:00.000Z", + "$updatedAt": "2024-02-01T00:00:00.000Z", + "embeddings": [0.2, 0.2, 0.2, 0.2], + "metadata": { "batch": 2 } + }), + ], + ).await?; + + println!("Bulk create: {:?}", result); + Ok(()) +} +``` +{% /multicode %} + +### Bulk upsert {% #bulk-upsert %} + +{% multicode %} +```server-nodejs +await vectorsDB.upsertDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documents: [ + { + '$id': '<DOCUMENT_ID_OR_NEW_ID>', + '$createdAt': new Date('2024-01-01T00:00:00.000Z').toISOString(), + '$updatedAt': new Date('2025-01-01T00:00:00.000Z').toISOString(), + embeddings: [0.3, 0.3, 0.3, 0.3], + metadata: { source: 'sync' } + } + ] +}); +``` +```server-python +vectors_db.upsert_documents( + database_id='<DATABASE_ID>', + collection_id='<COLLECTION_ID>', + documents=[ + { + '$id': '<DOCUMENT_ID_OR_NEW_ID>', + '$createdAt': datetime(2024, 1, 1, tzinfo=timezone.utc).isoformat(), + '$updatedAt': datetime(2025, 1, 1, tzinfo=timezone.utc).isoformat(), + 'embeddings': [0.3, 0.3, 0.3, 0.3], + 'metadata': { 'source': 'sync' } + } + ] +) +``` +```server-php +use Appwrite\Client; +use Appwrite\ID; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<YOUR_PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$vectorsDB = new VectorsDB($client); + +$vectorsDB->upsertDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documents: [ + [ + '$id' => '<DOCUMENT_ID_OR_NEW_ID>', + '$createdAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM), + '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM), + 'embeddings' => [0.3, 0.3, 0.3, 0.3], + 'metadata' => ['source' => 'sync'] + ], + ] +); +``` +```server-swift +import Appwrite +import Foundation + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<YOUR_PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let vectorsDB = VectorsDB(client) + +let isoFormatter = ISO8601DateFormatter() +isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] +let createdAt = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date()) +let updatedAt = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date()) + +do { + let bulkUpserted = try await vectorsDB.upsertDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documents: [ + [ + "$id": "<DOCUMENT_ID_OR_NEW_ID>", + "$createdAt": createdAt, + "$updatedAt": updatedAt, + "embeddings": [0.3, 0.3, 0.3, 0.3], + "metadata": ["source": "sync"] + ] + ] + ) + print("Bulk upsert:", bulkUpserted) +} catch { + print("Bulk upsert error:", error) +} +``` +```server-ruby +require 'appwrite' +require 'time' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<YOUR_PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +vectors_db = VectorsDB.new(client) + +custom_date = Time.parse('<CUSTOM_DATE>').iso8601 + +vectors_db.upsert_documents( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + documents: [ + { + '$id' => '<DOCUMENT_ID_OR_NEW_ID>', + '$createdAt' => custom_date, + '$updatedAt' => custom_date, + 'embeddings' => [0.3, 0.3, 0.3, 0.3], + 'metadata' => { 'source' => 'sync' } + } + ] +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<YOUR_PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +VectorsDB vectorsDB = new VectorsDB(client); + +string createdAt = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O"); +string updatedAt = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O"); + +await vectorsDB.UpsertDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documents: new List<object> + { + new Dictionary<string, object> + { + ["$id"] = "<DOCUMENT_ID_OR_NEW_ID>", + ["$createdAt"] = createdAt, + ["$updatedAt"] = updatedAt, + ["embeddings"] = new List<double> { 0.3, 0.3, 0.3, 0.3 }, + ["metadata"] = new Dictionary<string, object> { ["source"] = "sync" } + } + } +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<YOUR_PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +VectorsDB vectorsDB = VectorsDB(client); + +String createdAt = DateTime.parse('<CUSTOM_DATE>').toIso8601String(); +String updatedAt = DateTime.parse('<CUSTOM_DATE>').toIso8601String(); + +await vectorsDB.upsertDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documents: [ + { + '\$id': '<DOCUMENT_ID_OR_NEW_ID>', + '\$createdAt': createdAt, + '\$updatedAt': updatedAt, + 'embeddings': [0.3, 0.3, 0.3, 0.3], + 'metadata': { 'source': 'sync' } + } + ], +); +``` +```rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); + client.set_project("<PROJECT_ID>"); + client.set_key("<API_KEY>"); + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.upsert_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + vec![ + json!({ + "$id": "<DOCUMENT_ID_OR_NEW_ID>", + "$createdAt": "2024-01-01T00:00:00.000Z", + "$updatedAt": "2025-01-01T00:00:00.000Z", + "embeddings": [0.3, 0.3, 0.3, 0.3], + "metadata": { "source": "sync" } + }), + ], + None, // transactionId (optional) + ).await?; + + println!("Bulk upsert: {:?}", result); + Ok(()) +} +``` +{% /multicode %} + +# Common use cases {% #use-cases %} + +Custom timestamps are particularly useful in several scenarios: + +## Data migration {% #data-migration %} +When migrating existing vectors from another system, you can preserve the original +creation and modification times: + +{% multicode %} +```server-nodejs +await vectorsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: sdk.ID.unique(), + data: { + '$createdAt': '<ORIGINAL_CREATED_AT_ISO>', + '$updatedAt': '<LAST_MODIFIED_ISO>', + embeddings: [0.12, 0.84, 0.33, 0.57], + metadata: { title: 'Imported post' } + } +}); +``` +```server-php +$vectorsDB->createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: ID::unique(), + data: [ + '$createdAt' => '<ORIGINAL_CREATED_AT_ISO>', + '$updatedAt' => '<LAST_MODIFIED_ISO>', + 'embeddings' => [0.12, 0.84, 0.33, 0.57], + 'metadata' => ['title' => 'Imported post'] + ] +); +``` +```server-swift +let _ = try await vectorsDB.createDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: ID.unique(), + data: [ + "$createdAt": "<ORIGINAL_CREATED_AT_ISO>", + "$updatedAt": "<LAST_MODIFIED_ISO>", + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": ["title": "Imported post"] + ] +) +``` +```server-python +vectors_db.create_document( + database_id='<DATABASE_ID>', + collection_id='<COLLECTION_ID>', + document_id=ID.unique(), + data={ + '$createdAt': '<ORIGINAL_CREATED_AT_ISO>', + '$updatedAt': '<LAST_MODIFIED_ISO>', + 'embeddings': [0.12, 0.84, 0.33, 0.57], + 'metadata': { 'title': 'Imported post' } + } +) +``` +```server-ruby +vectors_db.create_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: ID.unique(), + data: { + '$createdAt' => '<ORIGINAL_CREATED_AT_ISO>', + '$updatedAt' => '<LAST_MODIFIED_ISO>', + 'embeddings' => [0.12, 0.84, 0.33, 0.57], + 'metadata' => { 'title' => 'Imported post' } + } +) +``` +```server-dotnet +await vectorsDB.CreateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: ID.Unique(), + data: new Dictionary<string, object> + { + ["$createdAt"] = "<ORIGINAL_CREATED_AT_ISO>", + ["$updatedAt"] = "<LAST_MODIFIED_ISO>", + ["embeddings"] = new List<double> { 0.12, 0.84, 0.33, 0.57 }, + ["metadata"] = new Dictionary<string, object> { ["title"] = "Imported post" } + } +); +``` +```server-dart +await vectorsDB.createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: ID.unique(), + data: { + '\$createdAt': '<ORIGINAL_CREATED_AT_ISO>', + '\$updatedAt': '<LAST_MODIFIED_ISO>', + 'embeddings': [0.12, 0.84, 0.33, 0.57], + 'metadata': { 'title': 'Imported post' } + }, +); +``` +```rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::id::ID; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); + client.set_project("<PROJECT_ID>"); + client.set_key("<API_KEY>"); + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + ID::unique(), + json!({ + "$createdAt": "<ORIGINAL_CREATED_AT_ISO>", + "$updatedAt": "<LAST_MODIFIED_ISO>", + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": { "title": "Imported post" } + }), + None, // permissions (optional) + ).await?; + + println!("Created: {:?}", result); + Ok(()) +} +``` +{% /multicode %} + +## Backdating records {% #backdating %} +For historical data entry or when creating records that represent past events: + +{% multicode %} +```server-nodejs +await vectorsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: sdk.ID.unique(), + data: { + '$createdAt': '2023-12-31T23:59:59.000Z', + '$updatedAt': '2023-12-31T23:59:59.000Z', + embeddings: [0.5, 0.5, 0.5, 0.5], + metadata: { type: 'year-end-bonus', amount: 1000 } + } +}); +``` +```server-php +$vectorsDB->createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: ID::unique(), + data: [ + '$createdAt' => '2023-12-31T23:59:59.000Z', + '$updatedAt' => '2023-12-31T23:59:59.000Z', + 'embeddings' => [0.5, 0.5, 0.5, 0.5], + 'metadata' => ['type' => 'year-end-bonus', 'amount' => 1000] + ] +); +``` +```server-swift +let _ = try await vectorsDB.createDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: ID.unique(), + data: [ + "$createdAt": "2023-12-31T23:59:59.000Z", + "$updatedAt": "2023-12-31T23:59:59.000Z", + "embeddings": [0.5, 0.5, 0.5, 0.5], + "metadata": ["type": "year-end-bonus", "amount": 1000] + ] +) +``` +```server-python +vectors_db.create_document( + database_id='<DATABASE_ID>', + collection_id='<COLLECTION_ID>', + document_id=ID.unique(), + data={ + '$createdAt': '2023-12-31T23:59:59.000Z', + '$updatedAt': '2023-12-31T23:59:59.000Z', + 'embeddings': [0.5, 0.5, 0.5, 0.5], + 'metadata': { 'type': 'year-end-bonus', 'amount': 1000 } + } +) +``` +```server-ruby +vectors_db.create_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: ID.unique(), + data: { + '$createdAt' => '2023-12-31T23:59:59.000Z', + '$updatedAt' => '2023-12-31T23:59:59.000Z', + 'embeddings' => [0.5, 0.5, 0.5, 0.5], + 'metadata' => { 'type' => 'year-end-bonus', 'amount' => 1000 } + } +) +``` +```server-dotnet +await vectorsDB.CreateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: ID.Unique(), + data: new Dictionary<string, object> + { + ["$createdAt"] = "2023-12-31T23:59:59.000Z", + ["$updatedAt"] = "2023-12-31T23:59:59.000Z", + ["embeddings"] = new List<double> { 0.5, 0.5, 0.5, 0.5 }, + ["metadata"] = new Dictionary<string, object> { ["type"] = "year-end-bonus", ["amount"] = 1000 } + } +); +``` +```server-dart +await vectorsDB.createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: ID.unique(), + data: { + '\$createdAt': '2023-12-31T23:59:59.000Z', + '\$updatedAt': '2023-12-31T23:59:59.000Z', + 'embeddings': [0.5, 0.5, 0.5, 0.5], + 'metadata': { 'type': 'year-end-bonus', 'amount': 1000 } + }, +); +``` +```rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::id::ID; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); + client.set_project("<PROJECT_ID>"); + client.set_key("<API_KEY>"); + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + ID::unique(), + json!({ + "$createdAt": "2023-12-31T23:59:59.000Z", + "$updatedAt": "2023-12-31T23:59:59.000Z", + "embeddings": [0.5, 0.5, 0.5, 0.5], + "metadata": { "type": "year-end-bonus", "amount": 1000 } + }), + None, // permissions (optional) + ).await?; + + println!("Created: {:?}", result); + Ok(()) +} +``` +{% /multicode %} + +## Synchronization {% #synchronization %} +When synchronizing data between systems while maintaining timestamp consistency: + +{% multicode %} +```server-nodejs +await vectorsDB.upsertDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID_OR_NEW_ID>', + data: { + '$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>', + embeddings: [0.6, 0.6, 0.6, 0.6], + metadata: { profile: 'external' } + } +}); +``` +```server-php +$vectorsDB->upsertDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID_OR_NEW_ID>', + data: [ + '$updatedAt' => '<EXTERNAL_LAST_MODIFIED_ISO>', + 'embeddings' => [0.6, 0.6, 0.6, 0.6], + 'metadata' => ['profile' => 'external'] + ] +); +``` +```server-swift +let _ = try await vectorsDB.upsertDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID_OR_NEW_ID>", + data: [ + "$updatedAt": "<EXTERNAL_LAST_MODIFIED_ISO>", + "embeddings": [0.6, 0.6, 0.6, 0.6], + "metadata": ["profile": "external"] + ] +) +``` +```server-python +vectors_db.upsert_document( + database_id='<DATABASE_ID>', + collection_id='<COLLECTION_ID>', + document_id='<DOCUMENT_ID_OR_NEW_ID>', + data={ + '$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>', + 'embeddings': [0.6, 0.6, 0.6, 0.6], + 'metadata': { 'profile': 'external' } + } +) +``` +```server-ruby +vectors_db.upsert_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: '<DOCUMENT_ID_OR_NEW_ID>', + data: { + '$updatedAt' => '<EXTERNAL_LAST_MODIFIED_ISO>', + 'embeddings' => [0.6, 0.6, 0.6, 0.6], + 'metadata' => { 'profile' => 'external' } + } +) +``` +```server-dotnet +await vectorsDB.UpsertDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID_OR_NEW_ID>", + data: new Dictionary<string, object> + { + ["$updatedAt"] = "<EXTERNAL_LAST_MODIFIED_ISO>", + ["embeddings"] = new List<double> { 0.6, 0.6, 0.6, 0.6 }, + ["metadata"] = new Dictionary<string, object> { ["profile"] = "external" } + } +); +``` +```server-dart +await vectorsDB.upsertDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID_OR_NEW_ID>', + data: { + '\$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>', + 'embeddings': [0.6, 0.6, 0.6, 0.6], + 'metadata': { 'profile': 'external' } + }, +); +``` +```rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); + client.set_project("<PROJECT_ID>"); + client.set_key("<API_KEY>"); + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.upsert_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID_OR_NEW_ID>", + Some(json!({ + "$updatedAt": "<EXTERNAL_LAST_MODIFIED_ISO>", + "embeddings": [0.6, 0.6, 0.6, 0.6], + "metadata": { "profile": "external" } + })), + None, // permissions (optional) + None, // transactionId (optional) + ).await?; + + println!("Upserted: {:?}", result); + Ok(()) +} +``` +{% /multicode %} + +{% info title="Timestamp format and usage" %} +- Values must be valid ISO 8601 date-time strings (UTC recommended). Using `toISOString()` (JavaScript) or `datetime.isoformat()` (Python) is a good default. +- You can set either or both attributes as needed. If omitted, Appwrite sets them automatically. +{% /info %} diff --git a/src/routes/docs/products/databases/vectorsdb/transactions/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/transactions/+page.markdoc new file mode 100644 index 00000000000..00dfdfb011e --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/transactions/+page.markdoc @@ -0,0 +1,1240 @@ +--- +layout: article +title: Transactions +description: Stage multiple VectorsDB operations and commit them atomically. Group changes across databases and collections with ordering, isolation, and conflict detection. +--- + +Transactions let you stage multiple database operations and apply them together, atomically. Use transactions to keep related changes consistent, even when they span multiple databases and collections. + +# How transactions work {% #how-transactions-work %} + +1. Call the [createTransaction](#create-a-transaction) method to create a transaction. This will return a transaction model, including its ID. +2. Stage operations by passing the `transactionId` parameter to supported document, bulk, and atomic numeric methods. You can stage many operations at once with the [createOperations](#create-operations) method. +3. Call the [updateTransaction](#commit-or-rollback) method to commit or roll back. + +On commit, Appwrite replays all staged logs in order inside a real database transaction. Staged operations see earlier staged changes (read your own writes). If any affected document changed outside your transaction, the commit fails with a conflict. + +{% info title="Scope and limitations" %} +You can stage operations across any database and collection within the same transaction. Schema operations (for example, creating or deleting indexes) are not included in transactions. +{% /info %} + +# Limits {% #limits %} + +The maximum number of operations you can stage per transaction depends on your plan: + +| Plan | Max operations per transaction | +|------|-------------------------------| +| Free | 100 | +| Pro | 1,000 | + +# Create a transaction {% #create-a-transaction %} + +Call the `createTransaction` method to begin. It returns a transaction model that includes `$id`. Pass this ID as `transactionId` to subsequent operations. Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/platform/api-keys). + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const tx = await vectorsDB.createTransaction(); +// tx.$id is your transactionId +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const tx = await vectorsDB.createTransaction(); +// tx.$id is your transactionId +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$tx = $vectorsDB->createTransaction(); +// $tx['$id'] is your transactionId +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +tx = vectors_db.create_transaction() +# tx['$id'] is your transactionId +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +tx = vectors_db.create_transaction +# tx['$id'] is your transactionId +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +var tx = await vectorsDB.CreateTransaction(); +// tx.Id is your transactionId +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +final tx = await vectorsDB.createTransaction(); +// tx.$id is your transactionId +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val tx = vectorsDB.createTransaction() +// tx.id is your transactionId +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.createTransaction(new CoroutineCallback<>((tx, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(tx); +})); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let tx = try await vectorsDB.createTransaction() +// tx.id is your transactionId +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let tx = vectors_db.create_transaction(None).await?; + // tx.id is your transaction_id + + Ok(()) +} +``` +```bash +appwrite vectors-db create-transaction +``` +{% /multicode %} + +# Stage operations {% #stage-operations %} + +Add the `transactionId` parameter to supported methods to stage them instead of immediately persisting. + +When you pass `transactionId`, Appwrite writes the operation to an internal staging area. The target collection is not modified until you commit the transaction. + +## Stage single operations {% #stage-single-operations %} + +Update, upsert, and delete operations accept `transactionId`, as well as their bulk versions (`updateDocuments`, `upsertDocuments`, `deleteDocuments`). To stage the first write to a brand new document, use `upsertDocument` with `transactionId`. + +{% multicode %} +```server-nodejs +// Update inside a transaction +await vectorsDB.updateDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { metadata: { title: 'Hamlet', year: 1602 } }, + transactionId: tx.$id +}); + +// Delete inside a transaction +await vectorsDB.deleteDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + transactionId: tx.$id +}); +``` +```deno +// Update inside a transaction +await vectorsDB.updateDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { metadata: { title: 'Hamlet', year: 1602 } }, + transactionId: tx.$id +}); + +// Delete inside a transaction +await vectorsDB.deleteDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + transactionId: tx.$id +}); +``` +```php +// Update inside a transaction +$vectorsDB->updateDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: ['metadata' => ['title' => 'Hamlet', 'year' => 1602]], + transactionId: $tx['$id'] +); + +// Delete inside a transaction +$vectorsDB->deleteDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + transactionId: $tx['$id'] +); +``` +```python +# Update inside a transaction +vectors_db.update_document( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = '<DOCUMENT_ID>', + data = { "metadata": { "title": "Hamlet", "year": 1602 } }, + transaction_id = tx['$id'] +) + +# Delete inside a transaction +vectors_db.delete_document( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = '<DOCUMENT_ID>', + transaction_id = tx['$id'] +) +``` +```ruby +# Update inside a transaction +vectors_db.update_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: '<DOCUMENT_ID>', + data: { "metadata" => { "title" => "Hamlet", "year" => 1602 } }, + transaction_id: tx['$id'] +) + +# Delete inside a transaction +vectors_db.delete_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: '<DOCUMENT_ID>', + transaction_id: tx['$id'] +) +``` +```csharp +// Update inside a transaction +await vectorsDB.UpdateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + data: new Dictionary<string, object> { + { "metadata", new Dictionary<string, object> { { "title", "Hamlet" }, { "year", 1602 } } } + }, + transactionId: tx.Id +); + +// Delete inside a transaction +await vectorsDB.DeleteDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + transactionId: tx.Id +); +``` +```dart +// Update inside a transaction +await vectorsDB.updateDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { "metadata": { "title": "Hamlet", "year": 1602 } }, + transactionId: tx.$id +); + +// Delete inside a transaction +await vectorsDB.deleteDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + transactionId: tx.$id +); +``` +```kotlin +// Update inside a transaction +vectorsDB.updateDocument( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = "<DOCUMENT_ID>", + data = mapOf("metadata" to mapOf("title" to "Hamlet", "year" to 1602)), + transactionId = tx.id +) + +// Delete inside a transaction +vectorsDB.deleteDocument( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = "<DOCUMENT_ID>", + transactionId = tx.id +) +``` +```java +// Update inside a transaction (asynchronous) +vectorsDB.updateDocument( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + Map.of("metadata", Map.of("title", "Hamlet", "year", 1602)), + listOf(), + "<TRANSACTION_ID>", + new CoroutineCallback<>((document, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(document); + }) +); +``` +```swift +// Update inside a transaction +try await vectorsDB.updateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + data: ["metadata": ["title": "Hamlet", "year": 1602]], + transactionId: tx.id +) + +// Delete inside a transaction +try await vectorsDB.deleteDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + transactionId: tx.id +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let tx = vectors_db.create_transaction(None).await?; + + // Update inside a transaction + vectors_db.update_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + Some(json!({ "metadata": { "title": "Hamlet", "year": 1602 } })), + None, + Some(&tx.id), + ).await?; + + // Delete inside a transaction + vectors_db.delete_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + Some(&tx.id), + ).await?; + + Ok(()) +} +``` +```bash +appwrite vectors-db update-document \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --document-id <DOCUMENT_ID> \ + --data '{ "metadata": { "title": "Hamlet", "year": 1602 } }' \ + --transaction-id <TRANSACTION_ID> +``` +{% /multicode %} + +You can read uncommitted changes back inside the same transaction by passing `transactionId` to `getDocument`. A plain read without `transactionId` still returns the committed document. + +{% multicode %} +```server-nodejs +// Read your own staged write +const staged = await vectorsDB.getDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + transactionId: tx.$id +}); +``` +```deno +// Read your own staged write +const staged = await vectorsDB.getDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + transactionId: tx.$id +}); +``` +```php +// Read your own staged write +$staged = $vectorsDB->getDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + transactionId: $tx['$id'] +); +``` +```python +# Read your own staged write +staged = vectors_db.get_document( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = '<DOCUMENT_ID>', + transaction_id = tx['$id'] +) +``` +```ruby +# Read your own staged write +staged = vectors_db.get_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: '<DOCUMENT_ID>', + transaction_id: tx['$id'] +) +``` +```csharp +// Read your own staged write +var staged = await vectorsDB.GetDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + transactionId: tx.Id +); +``` +```dart +// Read your own staged write +final staged = await vectorsDB.getDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + transactionId: tx.$id +); +``` +```kotlin +// Read your own staged write +val staged = vectorsDB.getDocument( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = "<DOCUMENT_ID>", + transactionId = tx.id +) +``` +```java +// Read your own staged write (asynchronous) +vectorsDB.getDocument( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + listOf(), + "<TRANSACTION_ID>", + new CoroutineCallback<>((document, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(document); + }) +); +``` +```swift +// Read your own staged write +let staged = try await vectorsDB.getDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + transactionId: tx.id +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let tx = vectors_db.create_transaction(None).await?; + + // Read your own staged write + let staged = vectors_db.get_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + None, + Some(&tx.id), + ).await?; + + Ok(()) +} +``` +```bash +appwrite vectors-db get-document \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --document-id <DOCUMENT_ID> \ + --transaction-id <TRANSACTION_ID> +``` +{% /multicode %} + +## Stage many with createOperations {% #create-operations %} + +Use the `createOperations` method to stage multiple operations across databases and collections in a single request. Provide an array of operation objects: + +```json +[ + { + "action": "create|update|upsert|increment|decrement|delete|bulkCreate|bulkUpdate|bulkUpsert|bulkDelete", + "databaseId": "<DATABASE_ID>", + "collectionId": "<COLLECTION_ID>", + "documentId": "<DOCUMENT_ID>", + "data": {} + } +] +``` + +For `create`, `update`, and `upsert` actions, pass the document `data` (an `embeddings` vector and optional `metadata`). For `delete`, the `documentId` is enough. + +{% multicode %} +```server-nodejs +// Stage multiple operations at once +await vectorsDB.createOperations({ + transactionId: tx.$id, + operations: [ + { + action: 'update', + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID_1>', + data: { metadata: { title: 'Macbeth' } } + }, + { + action: 'delete', + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID_2>' + } + ] +}); +``` +```deno +// Stage multiple operations at once +await vectorsDB.createOperations({ + transactionId: tx.$id, + operations: [ + { + action: 'update', + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID_1>', + data: { metadata: { title: 'Macbeth' } } + }, + { + action: 'delete', + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID_2>' + } + ] +}); +``` +```php +$vectorsDB->createOperations( + transactionId: $tx['$id'], + operations: [ + [ + 'action' => 'update', + 'databaseId' => '<DATABASE_ID>', + 'collectionId' => '<COLLECTION_ID>', + 'documentId' => '<DOCUMENT_ID_1>', + 'data' => [ 'metadata' => [ 'title' => 'Macbeth' ] ] + ], + [ + 'action' => 'delete', + 'databaseId' => '<DATABASE_ID>', + 'collectionId' => '<COLLECTION_ID>', + 'documentId' => '<DOCUMENT_ID_2>' + ] + ] +); +``` +```python +vectors_db.create_operations( + transaction_id = tx['$id'], + operations = [ + { + 'action': 'update', + 'databaseId': '<DATABASE_ID>', + 'collectionId': '<COLLECTION_ID>', + 'documentId': '<DOCUMENT_ID_1>', + 'data': { 'metadata': { 'title': 'Macbeth' } } + }, + { + 'action': 'delete', + 'databaseId': '<DATABASE_ID>', + 'collectionId': '<COLLECTION_ID>', + 'documentId': '<DOCUMENT_ID_2>' + } + ] +) +``` +```ruby +vectors_db.create_operations( + transaction_id: tx['$id'], + operations: [ + { + 'action' => 'update', + 'databaseId' => '<DATABASE_ID>', + 'collectionId' => '<COLLECTION_ID>', + 'documentId' => '<DOCUMENT_ID_1>', + 'data' => { 'metadata' => { 'title' => 'Macbeth' } } + }, + { + 'action' => 'delete', + 'databaseId' => '<DATABASE_ID>', + 'collectionId' => '<COLLECTION_ID>', + 'documentId' => '<DOCUMENT_ID_2>' + } + ] +) +``` +```csharp +await vectorsDB.CreateOperations( + transactionId: tx.Id, + operations: new List<object> + { + new Dictionary<string, object> + { + ["action"] = "update", + ["databaseId"] = "<DATABASE_ID>", + ["collectionId"] = "<COLLECTION_ID>", + ["documentId"] = "<DOCUMENT_ID_1>", + ["data"] = new Dictionary<string, object> { + ["metadata"] = new Dictionary<string, object> { ["title"] = "Macbeth" } + } + }, + new Dictionary<string, object> + { + ["action"] = "delete", + ["databaseId"] = "<DATABASE_ID>", + ["collectionId"] = "<COLLECTION_ID>", + ["documentId"] = "<DOCUMENT_ID_2>" + } + } +); +``` +```dart +await vectorsDB.createOperations( + transactionId: tx.$id, + operations: [ + { + 'action': 'update', + 'databaseId': '<DATABASE_ID>', + 'collectionId': '<COLLECTION_ID>', + 'documentId': '<DOCUMENT_ID_1>', + 'data': { 'metadata': { 'title': 'Macbeth' } } + }, + { + 'action': 'delete', + 'databaseId': '<DATABASE_ID>', + 'collectionId': '<COLLECTION_ID>', + 'documentId': '<DOCUMENT_ID_2>' + } + ] +); +``` +```kotlin +vectorsDB.createOperations( + transactionId = tx.id, + operations = listOf( + mapOf( + "action" to "update", + "databaseId" to "<DATABASE_ID>", + "collectionId" to "<COLLECTION_ID>", + "documentId" to "<DOCUMENT_ID_1>", + "data" to mapOf("metadata" to mapOf("title" to "Macbeth")) + ), + mapOf( + "action" to "delete", + "databaseId" to "<DATABASE_ID>", + "collectionId" to "<COLLECTION_ID>", + "documentId" to "<DOCUMENT_ID_2>" + ) + ) +) +``` +```java +// Stage multiple operations at once (asynchronous) +List<Map<String, Object>> operations = Arrays.asList( + Map.of( + "action", "update", + "databaseId", "<DATABASE_ID>", + "collectionId", "<COLLECTION_ID>", + "documentId", "<DOCUMENT_ID_1>", + "data", Map.of("metadata", Map.of("title", "Macbeth")) + ), + Map.of( + "action", "delete", + "databaseId", "<DATABASE_ID>", + "collectionId", "<COLLECTION_ID>", + "documentId", "<DOCUMENT_ID_2>" + ) +); + +vectorsDB.createOperations( + "<TRANSACTION_ID>", + operations, + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +try await vectorsDB.createOperations( + transactionId: tx.id, + operations: [ + [ + "action": "update", + "databaseId": "<DATABASE_ID>", + "collectionId": "<COLLECTION_ID>", + "documentId": "<DOCUMENT_ID_1>", + "data": ["metadata": ["title": "Macbeth"]] + ], + [ + "action": "delete", + "databaseId": "<DATABASE_ID>", + "collectionId": "<COLLECTION_ID>", + "documentId": "<DOCUMENT_ID_2>" + ] + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let tx = vectors_db.create_transaction(None).await?; + + // Stage multiple operations at once + vectors_db.create_operations( + &tx.id, + Some(vec![ + json!({ + "action": "update", + "databaseId": "<DATABASE_ID>", + "collectionId": "<COLLECTION_ID>", + "documentId": "<DOCUMENT_ID_1>", + "data": { "metadata": { "title": "Macbeth" } } + }), + json!({ + "action": "delete", + "databaseId": "<DATABASE_ID>", + "collectionId": "<COLLECTION_ID>", + "documentId": "<DOCUMENT_ID_2>" + }), + ]), + ).await?; + + Ok(()) +} +``` +{% /multicode %} + +# Commit or roll back {% #commit-or-rollback %} + +When you are done staging operations, call the `updateTransaction` method to finalize the transaction. Set `commit` to `true` to apply the staged operations, or `rollback` to `true` to discard them. + +{% multicode %} +```server-nodejs +// Commit +await vectorsDB.updateTransaction({ + transactionId: tx.$id, + commit: true +}); + +// Or roll back +await vectorsDB.updateTransaction({ + transactionId: tx.$id, + rollback: true +}); +``` +```deno +// Commit +await vectorsDB.updateTransaction({ + transactionId: tx.$id, + commit: true +}); + +// Or roll back +await vectorsDB.updateTransaction({ + transactionId: tx.$id, + rollback: true +}); +``` +```php +// Commit +$vectorsDB->updateTransaction( + transactionId: $tx['$id'], + commit: true +); + +// Roll back +$vectorsDB->updateTransaction( + transactionId: $tx['$id'], + rollback: true +); +``` +```python +# Commit +vectors_db.update_transaction( + transaction_id = tx['$id'], + commit = True +) + +# Roll back +vectors_db.update_transaction( + transaction_id = tx['$id'], + rollback = True +) +``` +```ruby +# Commit +vectors_db.update_transaction( + transaction_id: tx['$id'], + commit: true +) + +# Roll back +vectors_db.update_transaction( + transaction_id: tx['$id'], + rollback: true +) +``` +```csharp +// Commit +await vectorsDB.UpdateTransaction( + transactionId: tx.Id, + commit: true +); + +// Roll back +await vectorsDB.UpdateTransaction( + transactionId: tx.Id, + rollback: true +); +``` +```dart +// Commit +await vectorsDB.updateTransaction( + transactionId: tx.$id, + commit: true +); + +// Roll back +await vectorsDB.updateTransaction( + transactionId: tx.$id, + rollback: true +); +``` +```kotlin +// Commit +vectorsDB.updateTransaction( + transactionId = tx.id, + commit = true +) + +// Roll back +vectorsDB.updateTransaction( + transactionId = tx.id, + rollback = true +) +``` +```java +// Commit (asynchronous) +vectorsDB.updateTransaction( + "<TRANSACTION_ID>", + true, + false, + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); + +// Roll back (asynchronous) +vectorsDB.updateTransaction( + "<TRANSACTION_ID>", + false, + true, + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +// Commit +try await vectorsDB.updateTransaction( + transactionId: tx.id, + commit: true +) + +// Roll back +try await vectorsDB.updateTransaction( + transactionId: tx.id, + rollback: true +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + // Commit + vectors_db.update_transaction( + "<TRANSACTION_ID>", + Some(true), + None, + ).await?; + + // Roll back + vectors_db.update_transaction( + "<TRANSACTION_ID>", + None, + Some(true), + ).await?; + + Ok(()) +} +``` +```bash +# Commit +appwrite vectors-db update-transaction \ + --transaction-id <TRANSACTION_ID> \ + --commit true + +# Roll back +appwrite vectors-db update-transaction \ + --transaction-id <TRANSACTION_ID> \ + --rollback true +``` +{% /multicode %} + +# Inspect and delete transactions {% #inspect-and-delete %} + +Use `getTransaction` to check a transaction's status and operation count, `listTransactions` to list transactions across all databases, and `deleteTransaction` to discard a transaction without committing it. + +{% multicode %} +```server-nodejs +// Get one transaction +const tx = await vectorsDB.getTransaction({ transactionId: '<TRANSACTION_ID>' }); + +// List transactions +const list = await vectorsDB.listTransactions(); + +// Delete a transaction +await vectorsDB.deleteTransaction({ transactionId: '<TRANSACTION_ID>' }); +``` +```deno +// Get one transaction +const tx = await vectorsDB.getTransaction({ transactionId: '<TRANSACTION_ID>' }); + +// List transactions +const list = await vectorsDB.listTransactions(); + +// Delete a transaction +await vectorsDB.deleteTransaction({ transactionId: '<TRANSACTION_ID>' }); +``` +```php +// Get one transaction +$tx = $vectorsDB->getTransaction(transactionId: '<TRANSACTION_ID>'); + +// List transactions +$list = $vectorsDB->listTransactions(); + +// Delete a transaction +$vectorsDB->deleteTransaction(transactionId: '<TRANSACTION_ID>'); +``` +```python +# Get one transaction +tx = vectors_db.get_transaction(transaction_id = '<TRANSACTION_ID>') + +# List transactions +list = vectors_db.list_transactions() + +# Delete a transaction +vectors_db.delete_transaction(transaction_id = '<TRANSACTION_ID>') +``` +```ruby +# Get one transaction +tx = vectors_db.get_transaction(transaction_id: '<TRANSACTION_ID>') + +# List transactions +list = vectors_db.list_transactions + +# Delete a transaction +vectors_db.delete_transaction(transaction_id: '<TRANSACTION_ID>') +``` +```csharp +// Get one transaction +var tx = await vectorsDB.GetTransaction(transactionId: "<TRANSACTION_ID>"); + +// List transactions +var list = await vectorsDB.ListTransactions(); + +// Delete a transaction +await vectorsDB.DeleteTransaction(transactionId: "<TRANSACTION_ID>"); +``` +```dart +// Get one transaction +final tx = await vectorsDB.getTransaction(transactionId: '<TRANSACTION_ID>'); + +// List transactions +final list = await vectorsDB.listTransactions(); + +// Delete a transaction +await vectorsDB.deleteTransaction(transactionId: '<TRANSACTION_ID>'); +``` +```kotlin +// Get one transaction +val tx = vectorsDB.getTransaction(transactionId = "<TRANSACTION_ID>") + +// List transactions +val list = vectorsDB.listTransactions() + +// Delete a transaction +vectorsDB.deleteTransaction(transactionId = "<TRANSACTION_ID>") +``` +```java +// Get one transaction (asynchronous) +vectorsDB.getTransaction( + "<TRANSACTION_ID>", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); + +// List transactions (asynchronous) +vectorsDB.listTransactions(new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); +})); + +// Delete a transaction (asynchronous) +vectorsDB.deleteTransaction( + "<TRANSACTION_ID>", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +// Get one transaction +let tx = try await vectorsDB.getTransaction(transactionId: "<TRANSACTION_ID>") + +// List transactions +let list = try await vectorsDB.listTransactions() + +// Delete a transaction +try await vectorsDB.deleteTransaction(transactionId: "<TRANSACTION_ID>") +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + // Get one transaction + let tx = vectors_db.get_transaction("<TRANSACTION_ID>").await?; + + // List transactions + let list = vectors_db.list_transactions(None).await?; + + // Delete a transaction + vectors_db.delete_transaction("<TRANSACTION_ID>").await?; + + Ok(()) +} +``` +```bash +# Get one transaction +appwrite vectors-db get-transaction \ + --transaction-id <TRANSACTION_ID> + +# List transactions +appwrite vectors-db list-transactions + +# Delete a transaction +appwrite vectors-db delete-transaction \ + --transaction-id <TRANSACTION_ID> +``` +{% /multicode %} + +# Handle conflicts {% #handle-conflicts %} + +On commit, Appwrite verifies that documents affected by your transaction haven't changed externally since they were staged. If a conflicting change is detected, the commit fails with a conflict error. Resolve the conflict (for example, refetch and re-stage) and try again. + +{% info title="Best practices" %} +Keep transactions short-lived to reduce the likelihood of conflicts. Stage related updates in the order they must be applied. Prefer `createOperations` when you need to stage many changes across multiple collections. +{% /info %} + +{% arrow_link href="/docs/references" %} +Explore the API references +{% /arrow_link %} diff --git a/src/routes/docs/products/databases/vectorsdb/vector-search/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/vector-search/+page.markdoc new file mode 100644 index 00000000000..a663dcb0458 --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/vector-search/+page.markdoc @@ -0,0 +1,556 @@ +--- +layout: article +title: Vector search +description: Run similarity search over your documents with Appwrite VectorsDB. Create an HNSW index on the embeddings field and rank documents by cosine, dot product, or Euclidean distance. +--- +Vector search finds the documents whose `embeddings` are closest to a query vector. +Instead of matching exact values, it ranks documents by similarity, so you can build features like semantic search, recommendations, and retrieval for AI applications. + +There are two steps: create an [index](/docs/products/databases/vectorsdb/collections) on the `embeddings` field so searches are fast, then pass a vector query to `listDocuments` to get documents ranked by similarity. + +# Create an index {% #create-an-index %} +Before you search, create an HNSW index on the `embeddings` field with `createIndex`. HNSW (Hierarchical Navigable Small World) is an approximate nearest neighbor index that keeps similarity search fast as your collection grows. + +The index `type` decides how similarity is measured. Use the `VectorsDBIndexType` enum to pick one: + +| Index type | Enum | Use when | +| --- | --- | --- | +| `hnsw_cosine` | `VectorsDBIndexType.HnswCosine` | You care about the direction of the vectors, not their magnitude. A common default for text embeddings. | +| `hnsw_dot` | `VectorsDBIndexType.HnswDot` | You want the dot product, which factors in both direction and magnitude. | +| `hnsw_euclidean` | `VectorsDBIndexType.HnswEuclidean` | You want the straight-line distance between vectors. | + +Match the index type to the search query you plan to run. A `hnsw_cosine` index serves `Query.vectorCosine` searches, `hnsw_dot` serves `Query.vectorDot`, and `hnsw_euclidean` serves `Query.vectorEuclidean`. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createIndex({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + key: 'embeddings_index', + type: sdk.VectorsDBIndexType.HnswCosine, + attributes: ['embeddings'] +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createIndex({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + key: 'embeddings_index', + type: sdk.VectorsDBIndexType.HnswCosine, + attributes: ['embeddings'] +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Enums\VectorsDBIndexType; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->createIndex( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + key: 'embeddings_index', + type: VectorsDBIndexType::HNSWCOSINE(), + attributes: ['embeddings'] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.enums import VectorsDBIndexType + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.create_index( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + key = 'embeddings_index', + type = VectorsDBIndexType.HNSW_COSINE, + attributes = ['embeddings'] +) +``` +```ruby +require 'appwrite' + +include Appwrite +include Appwrite::Enums + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.create_index( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + key: 'embeddings_index', + type: VectorsDBIndexType::HNSW_COSINE, + attributes: ['embeddings'] +) +``` +```csharp +using Appwrite; +using Appwrite.Enums; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Index result = await vectorsDB.CreateIndex( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + key: "embeddings_index", + type: VectorsDBIndexType.HnswCosine, + attributes: new List<string> { "embeddings" } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; +import 'package:dart_appwrite/enums.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Index result = await vectorsDB.createIndex( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + key: 'embeddings_index', + type: VectorsDBIndexType.hnswCosine, + attributes: ['embeddings'], +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.enums.VectorsDBIndexType +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val result = vectorsDB.createIndex( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + key = "embeddings_index", + type = VectorsDBIndexType.HNSW_COSINE, + attributes = listOf("embeddings"), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.enums.VectorsDBIndexType; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.createIndex( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "embeddings_index", + VectorsDBIndexType.HNSW_COSINE, + List.of("embeddings"), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite +import AppwriteEnums + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let index = try await vectorsDB.createIndex( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + key: "embeddings_index", + type: .hnswCosine, + attributes: ["embeddings"] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::enums::VectorsDBIndexType; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create_index( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "embeddings_index", + VectorsDBIndexType::HnswCosine, + vec!["embeddings"], + None, // orders (optional) + None, // lengths (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db create-index \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --key 'embeddings_index' \ + --type 'hnsw_cosine' \ + --attributes 'embeddings' +``` +{% /multicode %} + +The index is built in the background. New documents are added to the index as you create them, so you can keep writing while it builds. + +# Run a similarity search {% #run-a-similarity-search %} +To search, pass a vector query to `listDocuments`. Build the query with one of the `Query` vector methods, passing the field name `embeddings` and the query vector. The response returns documents ranked from most to least similar. + +| Query method | Use with index type | +| --- | --- | +| `Query.vectorCosine('embeddings', vector)` | `hnsw_cosine` | +| `Query.vectorDot('embeddings', vector)` | `hnsw_dot` | +| `Query.vectorEuclidean('embeddings', vector)` | `hnsw_euclidean` | + +The query vector must have the same `dimension` as the collection. You can combine the vector query with other queries, such as `Query.limit()` to cap how many results you get back. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.vectorCosine('embeddings', [0.11, 0.21, 0.30, 0.40]), + sdk.Query.limit(3) + ] +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.vectorCosine('embeddings', [0.11, 0.21, 0.30, 0.40]), + sdk.Query.limit(3) + ] +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Query; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query::vectorCosine('embeddings', [0.11, 0.21, 0.30, 0.40]), + Query::limit(3) + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.list_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + queries = [ + Query.vector_cosine('embeddings', [0.11, 0.21, 0.30, 0.40]), + Query.limit(3) + ] +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.list_documents( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + queries: [ + Query.vector_cosine('embeddings', [0.11, 0.21, 0.30, 0.40]), + Query.limit(3) + ] +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; +using Appwrite.Queries; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +DocumentList result = await vectorsDB.ListDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: new List<string> { + Query.VectorCosine("embeddings", new List<object> { 0.11, 0.21, 0.30, 0.40 }), + Query.Limit(3) + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +DocumentList result = await vectorsDB.listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.vectorCosine('embeddings', [0.11, 0.21, 0.30, 0.40]), + Query.limit(3) + ], +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val result = vectorsDB.listDocuments( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + queries = listOf( + Query.vectorCosine("embeddings", listOf(0.11, 0.21, 0.30, 0.40)), + Query.limit(3) + ), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.Query; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.listDocuments( + "<DATABASE_ID>", + "<COLLECTION_ID>", + List.of( + Query.vectorCosine("embeddings", List.of(0.11, 0.21, 0.30, 0.40)), + Query.limit(3) + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let result = try await vectorsDB.listDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: [ + Query.vectorCosine("embeddings", [0.11, 0.21, 0.30, 0.40]), + Query.limit(3) + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::query::Query; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.list_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![ + Query::vector_cosine("embeddings", json!([0.11, 0.21, 0.30, 0.40])).to_string(), + Query::limit(3).to_string(), + ]), + None, // transaction_id + None, // total + None, // ttl + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"vectorCosine","attribute":"embeddings","values":[[0.11,0.21,0.30,0.40]]}' '{"method":"limit","values":[3]}' +``` +{% /multicode %} + +To rank by dot product or Euclidean distance instead, swap `vectorCosine` for `vectorDot` or `vectorEuclidean`, and make sure your index uses the matching type. + +{% info title="One vector query per request" %} +A single `listDocuments` call accepts at most one vector query. You can still add non-vector queries such as `Query.limit()` to the same request, but combining two vector queries in one call is not supported. +{% /info %} + +{% info title="Vector search currently works with low dimensions only" %} +Vector queries are sent on a `GET` request, and each query string is capped at 4096 characters. A serialized query vector grows with its dimension, so once a vector reaches a few hundred dimensions it pushes the query past that limit and the request is rejected with a `400 general_argument_invalid` error: *"Value must be a valid string and at least 1 chars and no longer than 4096 chars"*. The exact cutoff depends on the precision of the numbers in your vector, but in practice it falls somewhere around 200 to 270 dimensions. + +This means vector search through the SDK currently works for low-dimensional vectors only. The built-in [embedding models](/docs/products/databases/vectorsdb/embeddings) produce 384- and 768-dimensional vectors, which exceed this limit, so you cannot yet search over those embeddings directly. Keep your search vectors small enough to stay under the 4096-character limit. +{% /info %} + +# Next steps {% #next-steps %} +Vector search ranks documents by similarity. To filter those results by the data stored alongside each vector, combine search with metadata queries. + +{% arrow_link href="/docs/products/databases/vectorsdb/queries" %} +Learn about querying metadata +{% /arrow_link %} diff --git a/static/images/docs/databases/dark/documentsdb-import-export.avif b/static/images/docs/databases/dark/documentsdb-import-export.avif new file mode 100644 index 00000000000..de937ff4c6a Binary files /dev/null and b/static/images/docs/databases/dark/documentsdb-import-export.avif differ diff --git a/static/images/docs/databases/documentsdb-import-export.avif b/static/images/docs/databases/documentsdb-import-export.avif new file mode 100644 index 00000000000..9bdce2ba3bb Binary files /dev/null and b/static/images/docs/databases/documentsdb-import-export.avif differ diff --git a/static/images/docs/databases/documentsdb/backup-policy.avif b/static/images/docs/databases/documentsdb/backup-policy.avif new file mode 100644 index 00000000000..b44ed9afa6b Binary files /dev/null and b/static/images/docs/databases/documentsdb/backup-policy.avif differ diff --git a/static/images/docs/databases/documentsdb/backups-tab.avif b/static/images/docs/databases/documentsdb/backups-tab.avif new file mode 100644 index 00000000000..29aec532bc7 Binary files /dev/null and b/static/images/docs/databases/documentsdb/backups-tab.avif differ diff --git a/static/images/docs/databases/documentsdb/create-database.avif b/static/images/docs/databases/documentsdb/create-database.avif new file mode 100644 index 00000000000..4a201bf9554 Binary files /dev/null and b/static/images/docs/databases/documentsdb/create-database.avif differ diff --git a/static/images/docs/databases/documentsdb/dark/backup-policy.avif b/static/images/docs/databases/documentsdb/dark/backup-policy.avif new file mode 100644 index 00000000000..3e152f6732d Binary files /dev/null and b/static/images/docs/databases/documentsdb/dark/backup-policy.avif differ diff --git a/static/images/docs/databases/documentsdb/dark/backups-tab.avif b/static/images/docs/databases/documentsdb/dark/backups-tab.avif new file mode 100644 index 00000000000..b975ab60a2a Binary files /dev/null and b/static/images/docs/databases/documentsdb/dark/backups-tab.avif differ diff --git a/static/images/docs/databases/documentsdb/dark/create-database.avif b/static/images/docs/databases/documentsdb/dark/create-database.avif new file mode 100644 index 00000000000..8cfe52aa5ed Binary files /dev/null and b/static/images/docs/databases/documentsdb/dark/create-database.avif differ diff --git a/static/images/docs/databases/documentsdb/dark/manual-backup.avif b/static/images/docs/databases/documentsdb/dark/manual-backup.avif new file mode 100644 index 00000000000..091f73911ed Binary files /dev/null and b/static/images/docs/databases/documentsdb/dark/manual-backup.avif differ diff --git a/static/images/docs/databases/documentsdb/manual-backup.avif b/static/images/docs/databases/documentsdb/manual-backup.avif new file mode 100644 index 00000000000..0dc1a93e6db Binary files /dev/null and b/static/images/docs/databases/documentsdb/manual-backup.avif differ diff --git a/static/images/docs/products/databases/mysql/create-database-specs.avif b/static/images/docs/products/databases/mysql/create-database-specs.avif new file mode 100644 index 00000000000..fac1583d4dc Binary files /dev/null and b/static/images/docs/products/databases/mysql/create-database-specs.avif differ diff --git a/static/images/docs/products/databases/mysql/create-database-type.avif b/static/images/docs/products/databases/mysql/create-database-type.avif new file mode 100644 index 00000000000..0f4763f6ad6 Binary files /dev/null and b/static/images/docs/products/databases/mysql/create-database-type.avif differ diff --git a/static/images/docs/products/databases/mysql/dark/create-database-specs.avif b/static/images/docs/products/databases/mysql/dark/create-database-specs.avif new file mode 100644 index 00000000000..2349dd1482f Binary files /dev/null and b/static/images/docs/products/databases/mysql/dark/create-database-specs.avif differ diff --git a/static/images/docs/products/databases/mysql/dark/create-database-type.avif b/static/images/docs/products/databases/mysql/dark/create-database-type.avif new file mode 100644 index 00000000000..4c1257ed562 Binary files /dev/null and b/static/images/docs/products/databases/mysql/dark/create-database-type.avif differ diff --git a/static/images/docs/products/databases/postgresql/connections.avif b/static/images/docs/products/databases/postgresql/connections.avif new file mode 100644 index 00000000000..548756f3684 Binary files /dev/null and b/static/images/docs/products/databases/postgresql/connections.avif differ diff --git a/static/images/docs/products/databases/postgresql/create-database-specs.avif b/static/images/docs/products/databases/postgresql/create-database-specs.avif new file mode 100644 index 00000000000..e01987bf2ac Binary files /dev/null and b/static/images/docs/products/databases/postgresql/create-database-specs.avif differ diff --git a/static/images/docs/products/databases/postgresql/create-database-type.avif b/static/images/docs/products/databases/postgresql/create-database-type.avif new file mode 100644 index 00000000000..6b2619635b0 Binary files /dev/null and b/static/images/docs/products/databases/postgresql/create-database-type.avif differ diff --git a/static/images/docs/products/databases/postgresql/credentials.avif b/static/images/docs/products/databases/postgresql/credentials.avif new file mode 100644 index 00000000000..0f77e9949ac Binary files /dev/null and b/static/images/docs/products/databases/postgresql/credentials.avif differ diff --git a/static/images/docs/products/databases/postgresql/dark/connections.avif b/static/images/docs/products/databases/postgresql/dark/connections.avif new file mode 100644 index 00000000000..1713ab1916a Binary files /dev/null and b/static/images/docs/products/databases/postgresql/dark/connections.avif differ diff --git a/static/images/docs/products/databases/postgresql/dark/create-database-specs.avif b/static/images/docs/products/databases/postgresql/dark/create-database-specs.avif new file mode 100644 index 00000000000..c3702ef2ea3 Binary files /dev/null and b/static/images/docs/products/databases/postgresql/dark/create-database-specs.avif differ diff --git a/static/images/docs/products/databases/postgresql/dark/create-database-type.avif b/static/images/docs/products/databases/postgresql/dark/create-database-type.avif new file mode 100644 index 00000000000..ddee81190ac Binary files /dev/null and b/static/images/docs/products/databases/postgresql/dark/create-database-type.avif differ diff --git a/static/images/docs/products/databases/postgresql/dark/credentials.avif b/static/images/docs/products/databases/postgresql/dark/credentials.avif new file mode 100644 index 00000000000..4c47e9f18a5 Binary files /dev/null and b/static/images/docs/products/databases/postgresql/dark/credentials.avif differ diff --git a/static/images/docs/products/databases/postgresql/dark/monitor.avif b/static/images/docs/products/databases/postgresql/dark/monitor.avif new file mode 100644 index 00000000000..01261934f13 Binary files /dev/null and b/static/images/docs/products/databases/postgresql/dark/monitor.avif differ diff --git a/static/images/docs/products/databases/postgresql/dark/roles-tab.avif b/static/images/docs/products/databases/postgresql/dark/roles-tab.avif new file mode 100644 index 00000000000..0fe6284c6e0 Binary files /dev/null and b/static/images/docs/products/databases/postgresql/dark/roles-tab.avif differ diff --git a/static/images/docs/products/databases/postgresql/dark/settings-compute-tier.avif b/static/images/docs/products/databases/postgresql/dark/settings-compute-tier.avif new file mode 100644 index 00000000000..105cb23e159 Binary files /dev/null and b/static/images/docs/products/databases/postgresql/dark/settings-compute-tier.avif differ diff --git a/static/images/docs/products/databases/postgresql/dark/settings-high-availability.avif b/static/images/docs/products/databases/postgresql/dark/settings-high-availability.avif new file mode 100644 index 00000000000..4fd9cd601cb Binary files /dev/null and b/static/images/docs/products/databases/postgresql/dark/settings-high-availability.avif differ diff --git a/static/images/docs/products/databases/postgresql/dark/settings-network.avif b/static/images/docs/products/databases/postgresql/dark/settings-network.avif new file mode 100644 index 00000000000..4035fd641d5 Binary files /dev/null and b/static/images/docs/products/databases/postgresql/dark/settings-network.avif differ diff --git a/static/images/docs/products/databases/postgresql/dark/settings-pitr.avif b/static/images/docs/products/databases/postgresql/dark/settings-pitr.avif new file mode 100644 index 00000000000..f8dec97f72a Binary files /dev/null and b/static/images/docs/products/databases/postgresql/dark/settings-pitr.avif differ diff --git a/static/images/docs/products/databases/postgresql/dark/settings.avif b/static/images/docs/products/databases/postgresql/dark/settings.avif new file mode 100644 index 00000000000..3f1cb84f669 Binary files /dev/null and b/static/images/docs/products/databases/postgresql/dark/settings.avif differ diff --git a/static/images/docs/products/databases/postgresql/dark/sql-editor.avif b/static/images/docs/products/databases/postgresql/dark/sql-editor.avif new file mode 100644 index 00000000000..95831392f81 Binary files /dev/null and b/static/images/docs/products/databases/postgresql/dark/sql-editor.avif differ diff --git a/static/images/docs/products/databases/postgresql/monitor.avif b/static/images/docs/products/databases/postgresql/monitor.avif new file mode 100644 index 00000000000..93e13b873e2 Binary files /dev/null and b/static/images/docs/products/databases/postgresql/monitor.avif differ diff --git a/static/images/docs/products/databases/postgresql/roles-tab.avif b/static/images/docs/products/databases/postgresql/roles-tab.avif new file mode 100644 index 00000000000..01b8f19e157 Binary files /dev/null and b/static/images/docs/products/databases/postgresql/roles-tab.avif differ diff --git a/static/images/docs/products/databases/postgresql/settings-compute-tier.avif b/static/images/docs/products/databases/postgresql/settings-compute-tier.avif new file mode 100644 index 00000000000..cc1f570c7e7 Binary files /dev/null and b/static/images/docs/products/databases/postgresql/settings-compute-tier.avif differ diff --git a/static/images/docs/products/databases/postgresql/settings-high-availability.avif b/static/images/docs/products/databases/postgresql/settings-high-availability.avif new file mode 100644 index 00000000000..7dd74d77420 Binary files /dev/null and b/static/images/docs/products/databases/postgresql/settings-high-availability.avif differ diff --git a/static/images/docs/products/databases/postgresql/settings-network.avif b/static/images/docs/products/databases/postgresql/settings-network.avif new file mode 100644 index 00000000000..23fcff820de Binary files /dev/null and b/static/images/docs/products/databases/postgresql/settings-network.avif differ diff --git a/static/images/docs/products/databases/postgresql/settings-pitr.avif b/static/images/docs/products/databases/postgresql/settings-pitr.avif new file mode 100644 index 00000000000..3e73b5f22c7 Binary files /dev/null and b/static/images/docs/products/databases/postgresql/settings-pitr.avif differ diff --git a/static/images/docs/products/databases/postgresql/settings.avif b/static/images/docs/products/databases/postgresql/settings.avif new file mode 100644 index 00000000000..aa6de37d524 Binary files /dev/null and b/static/images/docs/products/databases/postgresql/settings.avif differ diff --git a/static/images/docs/products/databases/postgresql/sql-editor.avif b/static/images/docs/products/databases/postgresql/sql-editor.avif new file mode 100644 index 00000000000..24e982281f9 Binary files /dev/null and b/static/images/docs/products/databases/postgresql/sql-editor.avif differ