--- name: azure-cosmos-ts description: | Azure Cosmos DB JavaScript/TypeScript SDK (@azure/cosmos) for data plane operations. Use for CRUD operations on documents, queries, bulk operations, and container management. Triggers: "Cosmos DB", "@azure/cosmos", "CosmosClient", "document CRUD", "NoSQL queries", "bulk operations", "partition key", "container.items". package: "@azure/cosmos" --- # @azure/cosmos (TypeScript/JavaScript) Data plane SDK for Azure Cosmos DB NoSQL API operations — CRUD on documents, queries, bulk operations. > **⚠️ Data vs Management Plane** > - **This SDK (@azure/cosmos)**: CRUD operations on documents, queries, stored procedures > - **Management SDK (@azure/arm-cosmosdb)**: Create accounts, databases, containers via ARM ## Installation ```bash npm install @azure/cosmos @azure/identity ``` **Current Version**: 4.9.0 **Node.js**: >= 20.0.0 ## Environment Variables ```bash COSMOS_ENDPOINT=https://.documents.azure.com:443/ COSMOS_DATABASE= COSMOS_CONTAINER= # For key-based auth only (prefer AAD) COSMOS_KEY= ``` ## Authentication ### AAD with DefaultAzureCredential (Recommended) ```typescript import { CosmosClient } from "@azure/cosmos"; import { DefaultAzureCredential } from "@azure/identity"; const client = new CosmosClient({ endpoint: process.env.COSMOS_ENDPOINT!, aadCredentials: new DefaultAzureCredential(), }); ``` ### Key-Based Authentication ```typescript import { CosmosClient } from "@azure/cosmos"; // Option 1: Endpoint + Key const client = new CosmosClient({ endpoint: process.env.COSMOS_ENDPOINT!, key: process.env.COSMOS_KEY!, }); // Option 2: Connection String const client = new CosmosClient(process.env.COSMOS_CONNECTION_STRING!); ``` ## Resource Hierarchy ``` CosmosClient └── Database └── Container ├── Items (documents) ├── Scripts (stored procedures, triggers, UDFs) └── Conflicts ``` ## Core Operations ### Database & Container Setup ```typescript const { database } = await client.databases.createIfNotExists({ id: "my-database", }); const { container } = await database.containers.createIfNotExists({ id: "my-container", partitionKey: { paths: ["/partitionKey"] }, }); ``` ### Create Document ```typescript interface Product { id: string; partitionKey: string; name: string; price: number; } const item: Product = { id: "product-1", partitionKey: "electronics", name: "Laptop", price: 999.99, }; const { resource } = await container.items.create(item); ``` ### Read Document ```typescript const { resource } = await container .item("product-1", "electronics") // id, partitionKey .read(); if (resource) { console.log(resource.name); } ``` ### Update Document (Replace) ```typescript const { resource: existing } = await container .item("product-1", "electronics") .read(); if (existing) { existing.price = 899.99; const { resource: updated } = await container .item("product-1", "electronics") .replace(existing); } ``` ### Upsert Document ```typescript const item: Product = { id: "product-1", partitionKey: "electronics", name: "Laptop Pro", price: 1299.99, }; const { resource } = await container.items.upsert(item); ``` ### Delete Document ```typescript await container.item("product-1", "electronics").delete(); ``` ### Patch Document (Partial Update) ```typescript import { PatchOperation } from "@azure/cosmos"; const operations: PatchOperation[] = [ { op: "replace", path: "/price", value: 799.99 }, { op: "add", path: "/discount", value: true }, { op: "remove", path: "/oldField" }, ]; const { resource } = await container .item("product-1", "electronics") .patch(operations); ``` ## Queries ### Simple Query ```typescript const { resources } = await container.items .query("SELECT * FROM c WHERE c.price < 1000") .fetchAll(); ``` ### Parameterized Query (Recommended) ```typescript import { SqlQuerySpec } from "@azure/cosmos"; const querySpec: SqlQuerySpec = { query: "SELECT * FROM c WHERE c.partitionKey = @category AND c.price < @maxPrice", parameters: [ { name: "@category", value: "electronics" }, { name: "@maxPrice", value: 1000 }, ], }; const { resources } = await container.items .query(querySpec) .fetchAll(); ``` ### Query with Pagination ```typescript const queryIterator = container.items.query(querySpec, { maxItemCount: 10, // Items per page }); while (queryIterator.hasMoreResults()) { const { resources, continuationToken } = await queryIterator.fetchNext(); console.log(`Page with ${resources?.length} items`); // Use continuationToken for next page if needed } ``` ### Cross-Partition Query ```typescript const { resources } = await container.items .query( "SELECT * FROM c WHERE c.price > 500", { enableCrossPartitionQuery: true } ) .fetchAll(); ``` ## Bulk Operations ### Execute Bulk Operations ```typescript import { BulkOperationType, OperationInput } from "@azure/cosmos"; const operations: OperationInput[] = [ { operationType: BulkOperationType.Create, resourceBody: { id: "1", partitionKey: "cat-a", name: "Item 1" }, }, { operationType: BulkOperationType.Upsert, resourceBody: { id: "2", partitionKey: "cat-a", name: "Item 2" }, }, { operationType: BulkOperationType.Read, id: "3", partitionKey: "cat-b", }, { operationType: BulkOperationType.Replace, id: "4", partitionKey: "cat-b", resourceBody: { id: "4", partitionKey: "cat-b", name: "Updated" }, }, { operationType: BulkOperationType.Delete, id: "5", partitionKey: "cat-c", }, { operationType: BulkOperationType.Patch, id: "6", partitionKey: "cat-c", resourceBody: { operations: [{ op: "replace", path: "/name", value: "Patched" }], }, }, ]; const response = await container.items.executeBulkOperations(operations); response.forEach((result, index) => { if (result.statusCode >= 200 && result.statusCode < 300) { console.log(`Operation ${index} succeeded`); } else { console.error(`Operation ${index} failed: ${result.statusCode}`); } }); ``` ## Partition Keys ### Simple Partition Key ```typescript const { container } = await database.containers.createIfNotExists({ id: "products", partitionKey: { paths: ["/category"] }, }); ``` ### Hierarchical Partition Key (MultiHash) ```typescript import { PartitionKeyDefinitionVersion, PartitionKeyKind } from "@azure/cosmos"; const { container } = await database.containers.createIfNotExists({ id: "orders", partitionKey: { paths: ["/tenantId", "/userId", "/sessionId"], version: PartitionKeyDefinitionVersion.V2, kind: PartitionKeyKind.MultiHash, }, }); // Operations require array of partition key values const { resource } = await container.items.create({ id: "order-1", tenantId: "tenant-a", userId: "user-123", sessionId: "session-xyz", total: 99.99, }); // Read with hierarchical partition key const { resource: order } = await container .item("order-1", ["tenant-a", "user-123", "session-xyz"]) .read(); ``` ## Error Handling ```typescript import { ErrorResponse } from "@azure/cosmos"; try { const { resource } = await container.item("missing", "pk").read(); } catch (error) { if (error instanceof ErrorResponse) { switch (error.code) { case 404: console.log("Document not found"); break; case 409: console.log("Conflict - document already exists"); break; case 412: console.log("Precondition failed (ETag mismatch)"); break; case 429: console.log("Rate limited - retry after:", error.retryAfterInMs); break; default: console.error(`Cosmos error ${error.code}: ${error.message}`); } } throw error; } ``` ## Optimistic Concurrency (ETags) ```typescript // Read with ETag const { resource, etag } = await container .item("product-1", "electronics") .read(); if (resource && etag) { resource.price = 899.99; try { // Replace only if ETag matches await container.item("product-1", "electronics").replace(resource, { accessCondition: { type: "IfMatch", condition: etag }, }); } catch (error) { if (error instanceof ErrorResponse && error.code === 412) { console.log("Document was modified by another process"); } } } ``` ## TypeScript Types Reference ```typescript import { // Client & Resources CosmosClient, Database, Container, Item, Items, // Operations OperationInput, BulkOperationType, PatchOperation, // Queries SqlQuerySpec, SqlParameter, FeedOptions, // Partition Keys PartitionKeyDefinition, PartitionKeyDefinitionVersion, PartitionKeyKind, // Responses ItemResponse, FeedResponse, ResourceResponse, // Errors ErrorResponse, } from "@azure/cosmos"; ``` ## Best Practices 1. **Use AAD authentication** — Prefer `DefaultAzureCredential` over keys 2. **Always use parameterized queries** — Prevents injection, improves plan caching 3. **Specify partition key** — Avoid cross-partition queries when possible 4. **Use bulk operations** — For multiple writes, use `executeBulkOperations` 5. **Handle 429 errors** — Implement retry logic with exponential backoff 6. **Use ETags for concurrency** — Prevent lost updates in concurrent scenarios 7. **Close client on shutdown** — Call `client.dispose()` in cleanup ## Common Patterns ### Service Layer Pattern ```typescript export class ProductService { private container: Container; constructor(client: CosmosClient) { this.container = client .database(process.env.COSMOS_DATABASE!) .container(process.env.COSMOS_CONTAINER!); } async getById(id: string, category: string): Promise { try { const { resource } = await this.container .item(id, category) .read(); return resource ?? null; } catch (error) { if (error instanceof ErrorResponse && error.code === 404) { return null; } throw error; } } async create(product: Omit): Promise { const item = { ...product, id: crypto.randomUUID() }; const { resource } = await this.container.items.create(item); return resource!; } async findByCategory(category: string): Promise { const querySpec: SqlQuerySpec = { query: "SELECT * FROM c WHERE c.partitionKey = @category", parameters: [{ name: "@category", value: category }], }; const { resources } = await this.container.items .query(querySpec) .fetchAll(); return resources; } } ``` ## Related SDKs | SDK | Purpose | Install | |-----|---------|---------| | `@azure/cosmos` | Data plane (this SDK) | `npm install @azure/cosmos` | | `@azure/arm-cosmosdb` | Management plane (ARM) | `npm install @azure/arm-cosmosdb` | | `@azure/identity` | Authentication | `npm install @azure/identity` |