azure-cosmos-ts について
@azure/cosmos は、Azure Cosmos DB NoSQL API のデータプレーン操作に使う TypeScript/JavaScript SDK です。ドキュメントの読み書きや検索を行うアプリケーション向けに、CRUD、クエリ、一括操作、パーティションキーを提供し、ARM の管理プレーン操作は扱いません。
主な機能
- 01ドキュメントの作成・読み取り・更新・削除
- 02パラメーター化クエリとページネーション
- 03一括操作
- 04単純および階層型パーティションキー
- 05ETag による楽観的並行制御
- 06AAD とキーによる認証
活用シーン
- 01アプリケーションでの Cosmos DB ドキュメント管理
- 02条件付きページネーションクエリの実行
- 03複数レコードの一括書き込み
- 04ETag による同時更新の管理
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ sickn33/ agentic-awesome-skills --skill azure-cosmos-tsインストール後、Claude Code で/azure-cosmos-tsを入力して読み込まれたか確認します。
詳細
@azure/cosmos(TypeScript/JavaScript)
Azure Cosmos DB NoSQL API 操作用のデータプレーン SDKです。ドキュメントの CRUD、クエリ、バルク操作に対応します。
⚠️ データプレーンと管理プレーン
- この SDK(@azure/cosmos):ドキュメント、クエリ、ストアドプロシージャに対する CRUD 操作
- 管理 SDK(@azure/arm-cosmosdb):ARM 経由でアカウント、データベース、コンテナーを作成
インストール
npm install @azure/cosmos @azure/identity
現行バージョン:4.9.0
Node.js:>= 20.0.0
環境変数
COSMOS_ENDPOINT=https://<account>.documents.azure.com:443/
COSMOS_DATABASE=<database-name>
COSMOS_CONTAINER=<container-name>
# For key-based auth only (prefer AAD)
COSMOS_KEY=<account-key>
認証
DefaultAzureCredential を使用する AAD(推奨)
import { CosmosClient } from "@azure/cosmos";
import { DefaultAzureCredential } from "@azure/identity";
const client = new CosmosClient({
endpoint: process.env.COSMOS_ENDPOINT!,
aadCredentials: new DefaultAzureCredential(),
});
キーベースの認証
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!);
リソース階層
CosmosClient
└── Database
└── Container
├── Items (documents)
├── Scripts (stored procedures, triggers, UDFs)
└── Conflicts
主な操作
まず、アカウント/データベース/コンテナー、テナントに紐づく認可、書き込み/削除のスコープを確認します。パーティションキーはルーティングに使うものであり、認可には使いません。クエリのページ数、RU コスト、リトライ、実行時間に上限を設けます。fetchAll は無制限になる可能性があります。バルク操作は全件成功か全件失敗かのトランザクションではありません。各レスポンス/エラーを確認し、冪等性/同時実行性を確認したうえで、リトライ対象となる失敗項目のみを再試行します。SDK のバージョンを固定し、Microsoft SDK のドキュメントに照らしてサンプルを確認してください。
データベースとコンテナーのセットアップ
const { database } = await client.databases.createIfNotExists({
id: "my-database",
});
const { container } = await database.containers.createIfNotExists({
id: "my-container",
partitionKey: { paths: ["/partitionKey"] },
});
ドキュメントの作成
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<Product>(item);
ドキュメントの読み取り
const { resource } = await container
.item("product-1", "electronics") // id, partitionKey
.read<Product>();
if (resource) {
console.log(resource.name);
}
ドキュメントの更新(置換)
const { resource: existing } = await container
.item("product-1", "electronics")
.read<Product>();
if (existing) {
existing.price = 899.99;
const { resource: updated } = await container
.item("product-1", "electronics")
.replace<Product>(existing);
}
ドキュメントのアップサート
const item: Product = {
id: "product-1",
partitionKey: "electronics",
name: "Laptop Pro",
price: 1299.99,
};
const { resource } = await container.items.upsert<Product>(item);
ドキュメントの削除
await container.item("product-1", "electronics").delete();
ドキュメントのパッチ(部分更新)
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<Product>(operations);
クエリ
シンプルなクエリ
const { resources } = await container.items
.query<Product>("SELECT * FROM c WHERE c.price < 1000")
.fetchAll();
パラメーター化クエリ(推奨)
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<Product>(querySpec)
.fetchAll();
ページネーションを使用したクエリ
const queryIterator = container.items.query<Product>(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
}
クロスパーティションクエリ
const { resources } = await container.items
.query<Product>(
"SELECT * FROM c WHERE c.price > 500",
{ enableCrossPartitionQuery: true }
)
.fetchAll();
バルク操作
バルク操作の実行
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) => {
const statusCode = result.response?.statusCode;
if (!result.error && statusCode !== undefined && statusCode >= 200 && statusCode < 300) {
console.log(`Operation ${index} succeeded`);
} else {
console.error(`Operation ${index} failed: ${statusCode ?? result.error?.code ?? "unknown"}`);
}
});
パーティションキー
シンプルなパーティションキー
const { container } = await database.containers.createIfNotExists({
id: "products",
partitionKey: { paths: ["/category"] },
});
階層型パーティションキー(MultiHash)
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();
エラー処理
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;
}
楽観的同時実行制御(ETag)
// Read with ETag
const { resource, etag } = await container
.item("product-1", "electronics")
.read<Product>();
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 型リファレンス
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";
ベストプラクティス
- AAD 認証を使用する — キーよりも
DefaultAzureCredentialを優先します - 常にパラメーター化クエリを使用する — インジェクションを防ぎ、プランのキャッシュを改善します
- パーティションキーを指定する — 可能な場合はクロスパーティションクエリを避けます
- バルク操作を使用する — 複数の書き込みには
executeBulkOperationsを使用します - 429 エラーを処理する — 指数バックオフを使用したリトライロジックを実装します
- 同時実行制御に ETag を使用する — 同時実行時の更新の消失を防ぎます
- シャットダウン時にクライアントを閉じる — クリーンアップ時に
client.dispose()を呼び出します
一般的なパターン
サービスレイヤーパターン
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<Product | null> {
try {
const { resource } = await this.container
.item(id, category)
.read<Product>();
return resource ?? null;
} catch (error) {
if (error instanceof ErrorResponse && error.code === 404) {
return null;
}
throw error;
}
}
async create(product: Omit<Product, "id">): Promise<Product> {
const item = { ...product, id: crypto.randomUUID() };
const { resource } = await this.container.items.create<Product>(item);
return resource!;
}
async findByCategory(category: string): Promise<Product[]> {
const querySpec: SqlQuerySpec = {
query: "SELECT * FROM c WHERE c.partitionKey = @category",
parameters: [{ name: "@category", value: category }],
};
const { resources } = await this.container.items
.query<Product>(querySpec)
.fetchAll();
return resources;
}
}
関連 SDK
| SDK | 用途 | インストール |
|---|---|---|
@azure/cosmos | データプレーン(この SDK) | npm install @azure/cosmos |
@azure/arm-cosmosdb | 管理プレーン(ARM) | npm install @azure/arm-cosmosdb |
@azure/identity | 認証 | npm install @azure/identity |
使用する場面
このスキルは、概要で説明されているワークフローまたはアクションを実行する場合に適用できます。
制限事項
- このスキルは、タスクが上記の説明された範囲に明確に一致する場合にのみ使用してください。
- 出力を、環境固有の検証、テスト、または専門家によるレビューの代わりとして扱わないでください。
- 必要な入力、権限、安全上の境界、または成功基準が不足している場合は、作業を中止して確認を求めてください。
コメント
Markdown 対応