@azure/cosmos (TypeScript/JavaScript)
SDK du plan de données pour les opérations Azure Cosmos DB NoSQL API — CRUD sur documents, requêtes, opérations en masse.
⚠️ Plan de données vs Plan de gestion
- Ce SDK (@azure/cosmos) : opérations CRUD sur documents, requêtes, procédures stockées
- SDK de gestion (@azure/arm-cosmosdb) : créer des comptes, bases de données, conteneurs via ARM
Installation
npm install @azure/cosmos @azure/identity
Version actuelle : 4.9.0
Node.js : >= 20.0.0
Variables d'environnement
COSMOS_ENDPOINT=https://<account>.documents.azure.com:443/
COSMOS_DATABASE=<database-name>
COSMOS_CONTAINER=<container-name>
# Pour l'authentification par clé uniquement (préférer AAD)
COSMOS_KEY=<account-key>
AZURE_TOKEN_CREDENTIALS=prod # Obligatoire uniquement si DefaultAzureCredential est utilisé en production
Authentification
Credential de jeton Microsoft Entra (Recommandé)
import { CosmosClient } from "@azure/cosmos";
import { DefaultAzureCredential, ManagedIdentityCredential } from "@azure/identity";
// Dev local : DefaultAzureCredential. Production : définir AZURE_TOKEN_CREDENTIALS=prod ou AZURE_TOKEN_CREDENTIALS=<specific_credential>
const credential = new DefaultAzureCredential({requiredEnvVars: ["AZURE_TOKEN_CREDENTIALS"]});
// Ou utiliser un credential spécifique directement en production :
// Voir https://learn.microsoft.com/javascript/api/overview/azure/identity-readme?view=azure-node-latest#credential-classes
// const credential = new ManagedIdentityCredential();
const client = new CosmosClient({
endpoint: process.env.COSMOS_ENDPOINT!,
aadCredentials: credential,
});
Authentification par clé
import { CosmosClient } from "@azure/cosmos";
// Option 1 : Endpoint + Clé
const client = new CosmosClient({
endpoint: process.env.COSMOS_ENDPOINT!,
key: process.env.COSMOS_KEY!,
});
// Option 2 : Chaîne de connexion
const client = new CosmosClient(process.env.COSMOS_CONNECTION_STRING!);
Hiérarchie des ressources
CosmosClient
└── Database
└── Container
├── Items (documents)
├── Scripts (procédures stockées, triggers, UDFs)
└── Conflicts
Opérations essentielles
Configuration de base de données et conteneur
const { database } = await client.databases.createIfNotExists({
id: "my-database",
});
const { container } = await database.containers.createIfNotExists({
id: "my-container",
partitionKey: { paths: ["/partitionKey"] },
});
Créer un document
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);
Lire un document
const { resource } = await container
.item("product-1", "electronics") // id, partitionKey
.read<Product>();
if (resource) {
console.log(resource.name);
}
Mettre à jour un document (Remplacer)
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);
}
Upsert de document
const item: Product = {
id: "product-1",
partitionKey: "electronics",
name: "Laptop Pro",
price: 1299.99,
};
const { resource } = await container.items.upsert<Product>(item);
Supprimer un document
await container.item("product-1", "electronics").delete();
Patch de document (Mise à jour partielle)
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);
Requêtes
Requête simple
const { resources } = await container.items
.query<Product>("SELECT * FROM c WHERE c.price < 1000")
.fetchAll();
Requête paramétrée (Recommandé)
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();
Requête avec pagination
const queryIterator = container.items.query<Product>(querySpec, {
maxItemCount: 10, // Éléments par page
});
while (queryIterator.hasMoreResults()) {
const { resources, continuationToken } = await queryIterator.fetchNext();
console.log(`Page avec ${resources?.length} éléments`);
// Utiliser continuationToken pour la page suivante si nécessaire
}
Requête multi-partition
const { resources } = await container.items
.query<Product>(
"SELECT * FROM c WHERE c.price > 500",
{ enableCrossPartitionQuery: true }
)
.fetchAll();
Opérations en masse
Exécuter des opérations en masse
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(`Opération ${index} réussie`);
} else {
console.error(`Opération ${index} échouée : ${result.statusCode}`);
}
});
Clés de partition
Clé de partition simple
const { container } = await database.containers.createIfNotExists({
id: "products",
partitionKey: { paths: ["/category"] },
});
Clé de partition hiérarchique (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,
},
});
// Les opérations requièrent un tableau de valeurs de clé de partition
const { resource } = await container.items.create({
id: "order-1",
tenantId: "tenant-a",
userId: "user-123",
sessionId: "session-xyz",
total: 99.99,
});
// Lecture avec clé de partition hiérarchique
const { resource: order } = await container
.item("order-1", ["tenant-a", "user-123", "session-xyz"])
.read();
Gestion des erreurs
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 introuvable");
break;
case 409:
console.log("Conflit - document déjà existant");
break;
case 412:
console.log("Précondition échouée (ETag incompatible)");
break;
case 429:
console.log("Limitation de débit - réessayer après :", error.retryAfterInMs);
break;
default:
console.error(`Erreur Cosmos ${error.code} : ${error.message}`);
}
}
throw error;
}
Concurrence optimiste (ETags)
// Lire avec ETag
const { resource, etag } = await container
.item("product-1", "electronics")
.read<Product>();
if (resource && etag) {
resource.price = 899.99;
try {
// Remplacer uniquement si l'ETag correspond
await container.item("product-1", "electronics").replace(resource, {
accessCondition: { type: "IfMatch", condition: etag },
});
} catch (error) {
if (error instanceof ErrorResponse && error.code === 412) {
console.log("Le document a été modifié par un autre processus");
}
}
}
Référence des types TypeScript
import {
// Client & Ressources
CosmosClient,
Database,
Container,
Item,
Items,
// Opérations
OperationInput,
BulkOperationType,
PatchOperation,
// Requêtes
SqlQuerySpec,
SqlParameter,
FeedOptions,
// Clés de partition
PartitionKeyDefinition,
PartitionKeyDefinitionVersion,
PartitionKeyKind,
// Réponses
ItemResponse,
FeedResponse,
ResourceResponse,
// Erreurs
ErrorResponse,
} from "@azure/cosmos";
Bonnes pratiques
- Utiliser Microsoft Entra Token Credential — Utiliser
DefaultAzureCredentialpour le développement local ; utiliserManagedIdentityCredentialouWorkloadIdentityCredentialen production - Toujours utiliser des requêtes paramétrées — Prévient l'injection, améliore la mise en cache du plan
- Spécifier la clé de partition — Éviter les requêtes multi-partition si possible
- Utiliser les opérations en masse — Pour plusieurs écritures, utiliser
executeBulkOperations - Gérer les erreurs 429 — Implémenter une logique de relance avec backoff exponentiel
- Utiliser les ETags pour la concurrence — Prévenir les pertes de mise à jour dans les scénarios concurrents
- Fermer le client à l'arrêt — Appeler
client.dispose()lors du nettoyage
Modèles courants
Modèle de couche service
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 connexes
| SDK | Objectif | Installation |
|---|---|---|
@azure/cosmos |
Plan de données (ce SDK) | npm install @azure/cosmos |
@azure/arm-cosmosdb |
Plan de gestion (ARM) | npm install @azure/arm-cosmosdb |
@azure/identity |
Authentification | npm install @azure/identity |