1 - How to: Store and query vectors with the Dapr JavaScript SDK
Learn how to manage collections, store embeddings, and query similar vectors using the Dapr JavaScript SDK
Use DaprClient.vector to store precomputed embeddings and find similar records through a Dapr vector store component. This guide covers collection configuration, upserts, retrieval, similarity queries, batch queries, and deletes.
Prerequisites
- Dapr CLI
- An initialized Dapr environment with a Vector-capable runtime from the 1.19 release line or later
- Node.js 18 or later
- The
@dapr/dapr package, version 3.19.0 or later - A configured vector store component, such as the in-memory component below
Runtime support
Use a Dapr runtime that includes the Vector API. Installing JavaScript SDK 3.19.0 alone does not enable runtime support for Vector.In your application directory, initialize an npm project if you do not already have one, then install the SDK:
npm init -y
npm install @dapr/dapr@3.19.0
The examples use JavaScript ES modules and run as a .mjs file without a TypeScript compiler. The same APIs are available in TypeScript.
Create a components directory in your application directory and save the following as vectorstore.yaml inside it:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: vectorstore
spec:
type: vector.in-memory
version: v1
This component stores collections and records in the sidecar’s memory. Data is lost when the sidecar stops and is not shared across sidecars. Use it for local development and testing, not durable storage.
The component’s metadata.name, vectorstore, is the store name passed to every client operation. It is not the component’s type or the YAML file name. A collection is a separate, named group of records within that store.
Create a collection and query vectors
Save the following as app.mjs:
import { CommunicationProtocolEnum, DaprClient } from "@dapr/dapr";
const client = new DaprClient({
daprHost: "127.0.0.1",
daprPort: "3500",
communicationProtocol: CommunicationProtocolEnum.HTTP,
});
const storeName = "vectorstore";
const collection = "documents";
const writeOptions = {
mode: "waitForCompletion",
waitTimeout: "10s",
onWaitTimeout: "failRequest",
};
try {
await client.vector.createCollection(storeName, collection, {
dimensions: 3,
metric: "cosine",
});
const write = await client.vector.upsertVectors(
storeName,
collection,
[
{
id: "doc-1",
values: [1, 0, 0],
payload: Buffer.from("Introduction to Dapr"),
metadata: { category: "guide", details: { language: "en" } },
},
{
id: "doc-2",
values: [0.8, 0.6, 0],
payload: Buffer.from("Building applications with Dapr"),
metadata: { category: "guide", details: { language: "en" } },
},
{
id: "doc-3",
values: [0, 1, 0],
metadata: { category: "reference", details: { language: "en" } },
},
],
writeOptions,
);
if (write.failedItems.length > 0) {
console.error("Vector upsert failures:", write.failedItems);
throw new Error("Not all vector records were stored");
}
if (write.ack !== "completed") {
throw new Error("Vector indexing has not completed");
}
const result = await client.vector.queryVectors(storeName, collection, {
vector: [1, 0, 0],
topK: 2,
includePayload: true,
filter: { category: { $eq: "guide" } },
});
console.log("Metric:", result.metric);
for (const match of result.matches) {
console.log(
match.record.id,
match.score,
match.record.payload === undefined
? "(no payload)"
: Buffer.from(match.record.payload).toString("utf8"),
);
}
} finally {
await client.stop();
}
Start the application with a Dapr sidecar, loading the component from components:
dapr run --app-id vector-example --dapr-http-port 3500 --dapr-grpc-port 50001 --resources-path ./components -- node app.mjs
dapr run --app-id vector-example --dapr-http-port 3500 --dapr-grpc-port 50001 --resources-path .\components -- node app.mjs
The example creates a three-dimensional cosine collection, waits for indexing, and finds the two closest guide records. With the in-memory component, doc-1 has a score of approximately 1 and doc-2 approximately 0.8. These illustrative vectors are supplied directly; the SDK does not generate embeddings.
To use gRPC instead, change communicationProtocol to CommunicationProtocolEnum.GRPC and daprPort to "50001". All vector methods work with either protocol.
The examples below reuse client, storeName, collection, and writeOptions. Add them inside the try block, after the query and before the client is stopped. Run the delete examples last. Creating a collection that already exists fails; if you rerun against the same sidecar, reuse the existing collection or explicitly delete it first.
Inspect collections and retrieve records
console.log(await client.vector.listCollections(storeName));
const info = await client.vector.getCollection(storeName, collection);
console.log(info.collection, info.dimensions, info.metric, info.recordCount);
const records = await client.vector.getVectors(
storeName,
collection,
["doc-2", "missing", "doc-1"],
{ includeValues: true },
);
console.log(records);
getCollection returns the collection name, dimensions, effective metric, approximate record count, and component-specific properties. The count is 0 when unavailable.
getVectors omits missing IDs and retains request order for records it finds. It always retrieves payloads and record metadata, but returns values: [] unless includeValues is true. Payloads are returned as Uint8Array, not automatically decoded text or JSON.
Instead of supplying a vector, use byId to search using a stored record’s vector:
const similar = await client.vector.queryVectors(storeName, collection, {
byId: "doc-1",
topK: 5,
includeValues: true,
includePayload: true,
scoreThreshold: 0.5,
filter: {
$and: [
{ category: { $eq: "guide" } },
{ "details.language": { $eq: "en" } },
],
},
});
console.log(similar.matches);
Supply exactly one of vector or byId. A missing source record causes a single query to fail. Whether a byId query includes the source record among its matches depends on the component.
Filters apply to record metadata, not payload bytes. The portable filter DSL supports $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $exists, and the boolean operators $and, $or, and $not. Use dotted paths for nested metadata fields.
For cosine and dot product, higher scores are better; for Euclidean distance, lower scores are better. scoreThreshold is inclusive on the metric’s raw scale: a minimum score for cosine or dot product, or a maximum distance for Euclidean. It is not a normalized probability.
Run multiple queries
batchQueryVectors runs queries against the same store and collection and returns one result per query in request order:
const results = await client.vector.batchQueryVectors(storeName, collection, [
{ vector: [1, 0, 0], topK: 1 },
{ byId: "missing", topK: 1 },
]);
for (const [index, result] of results.entries()) {
if (result.error !== undefined) {
console.error("Query failed:", index, result.error.code, result.error.message);
} else {
console.log("Query matches:", index, result.response.matches);
}
}
An individual runtime or provider failure stays in that query’s result slot. Request-wide failures reject the whole call. Do not treat a resolved batch promise as proof that every query succeeded.
Delete records and collections
const ack = await client.vector.deleteVectors(
storeName,
collection,
["doc-1", "missing"],
writeOptions,
);
console.log("Delete acknowledgement:", ack);
await client.vector.deleteCollection(storeName, collection);
Deleting missing record IDs is not an error. Deleting a collection removes the collection and its records; operations against a missing collection fail.
Next steps
Read the client usage guidance for indexing acknowledgements, record replacement, query defaults, cancellation, and error handling.
2 - Vector client usage
Essential guidance for configuring and using the JavaScript Vector client
Vector is available through DaprClient.vector in JavaScript SDK 3.19.0 or later. Both HTTP and gRPC use the alpha Vector API and require a Vector-capable Dapr runtime from the 1.19 release line or later. Installing the JavaScript SDK alone does not enable the Vector API.
Client lifetime and configuration
Reuse a long-lived DaprClient rather than creating a client for each operation. Await outstanding operations before calling await client.stop() during application shutdown.
The default protocol is HTTP. To explicitly configure a gRPC connection:
import { CommunicationProtocolEnum, DaprClient } from "@dapr/dapr";
const client = new DaprClient({
daprHost: "127.0.0.1",
daprPort: "50001",
communicationProtocol: CommunicationProtocolEnum.GRPC,
daprApiToken: process.env.DAPR_API_TOKEN,
});
Use CommunicationProtocolEnum.HTTP and the sidecar’s HTTP port (by default "3500") for HTTP. Supply port values as strings.
When you omit explicit host and port options, the SDK can use DAPR_HTTP_ENDPOINT or DAPR_GRPC_ENDPOINT for the selected protocol. Otherwise, it uses the configured host and port, falling back to 127.0.0.1 and DAPR_HTTP_PORT or DAPR_GRPC_PORT (defaults: 3500 and 50001). The API token defaults to DAPR_API_TOKEN if daprApiToken is not supplied. Vector requests use the same authentication settings as other client operations.
Choose an operation
Every method takes a component name. Collection-bound operations also take a collection name. The options object is the last argument.
| Method | Result |
|---|
createCollection(storeName, collection, options) | Promise<void> |
getCollection(storeName, collection, options?) | Promise<VectorCollection> |
listCollections(storeName, options?) | Promise<string[]> |
deleteCollection(storeName, collection, options?) | Promise<void> |
upsertVectors(storeName, collection, records, options?) | Promise<VectorUpsertResult> |
getVectors(storeName, collection, ids, options?) | Promise<VectorRecord[]> |
deleteVectors(storeName, collection, ids, options?) | Promise<VectorIndexAck> |
queryVectors(storeName, collection, query, options?) | Promise<VectorQueryResult> |
batchQueryVectors(storeName, collection, queries, options?) | Promise<VectorBatchQueryResult[]> |
TypeScript applications can import these result types, IClientVector, VectorRecord, VectorQuery, VectorDistanceMetric, VectorOptions, VectorCreateCollectionOptions, VectorIndexingOptions, VectorError, and VectorErrorDetail as types from @dapr/dapr.
VectorOptions provides metadata?: Readonly<Record<string, string>> and signal?: AbortSignal. Request metadata is component-specific and string-valued; it is separate from the structured, filterable metadata attached to each record.
Collections and records
Creating a collection requires dimensions, a positive integer no greater than 4294967295. Supported metric names are "cosine", "dotProduct", and "euclidean". Omitting metric selects the component’s documented default; a metric the component does not support is rejected by the runtime as an invalid argument. The metadata passed to createCollection carries component-specific collection settings, such as index parameters. getCollection returns the collection’s dimensions, its effective metric, an approximate recordCount (which some components report as 0), and component-specific properties. Component capabilities and limits can be narrower than the SDK’s accepted inputs.
The alpha API covers dense vectors only. Sparse or named vectors, hybrid fusion, and result pagination are not available.
A VectorRecord contains a nonempty string id, a values array of numbers, optional payload bytes, and optional structured JSON metadata. IDs are scoped to a collection. Upserting an existing ID replaces its record rather than merging individual fields; supply all values, payload, and metadata you want to retain.
Provide dense embeddings as ordinary number arrays, not Float32Array. Coordinates must be finite and within the finite float32 range; float32 serialization can round them. Vector lengths must match the collection’s dimensions, and components typically reject zero-magnitude vectors for cosine similarity. Dimension mismatches and other runtime record validation failures can appear in failedItems rather than rejecting the upsert.
Payloads accept Uint8Array or Node.js Buffer and are returned as Uint8Array. The SDK does not apply a custom serializer or generate embeddings from payloads. Encode and decode application content explicitly. Record metadata and query filters must be JSON objects.
getVectors always retrieves payloads and metadata. Set { includeValues: true } to retrieve embeddings; otherwise values is an empty array. Missing IDs are omitted, and found records retain request order. Deleting missing IDs succeeds.
Indexing acknowledgements and partial failures
upsertVectors and deleteVectors accept VectorIndexingOptions. The default mode, "returnOnAcceptance", allows a write to return before indexing completes. An acknowledgement of "queued" means work was accepted, not that it succeeded or is queryable. Queued work can still fail later; this API does not expose its eventual result.
For a read-after-write flow, explicitly wait for completion:
const write = await client.vector.upsertVectors(
"vectorstore",
"documents",
[{ id: "doc-1", values: [1, 0, 0], metadata: { category: "guide" } }],
{
mode: "waitForCompletion",
waitTimeout: "10s",
onWaitTimeout: "failRequest",
},
);
if (write.failedItems.length > 0) {
console.error("Failed records:", write.failedItems);
throw new Error("Vector upsert had partial failures");
}
if (write.ack !== "completed") {
throw new Error("Vector indexing has not completed");
}
This snippet assumes an existing client and collection. Run it inside an async function or an ES module.
Waiting requires a positive protobuf duration string, such as "10s" or "0.250s", and an explicit timeout action. waitTimeout must be shorter than the remaining request deadline. When the store has a resiliency timeout policy, it must be shorter than that timeout by at least a tenth of the timeout and by no less than 100 milliseconds.
onWaitTimeout | Behavior |
|---|
"continueAsync" | Return a "queued" acknowledgement if the wait expires; work can continue asynchronously. Invalid when the component has no queued acknowledgement. |
"failRequest" | Fail the request with a deadline-exceeded error (HTTP 504, gRPC DeadlineExceeded) if the wait expires. Provider-side work is not guaranteed to be cancelled. |
Do not supply waitTimeout or onWaitTimeout with "returnOnAcceptance" or without explicitly selecting "waitForCompletion"; the SDK rejects these combinations before sending the request.
upsertVectors returns { ack, failedItems }. Inspect failedItems even when the promise resolves and ack is "completed": completion does not mean every record succeeded. Each failed item contains its id and a structured error. Failures reported are those known at the acknowledgement boundary; a queued acknowledgement is not a final per-record report.
deleteVectors returns only "queued" or "completed". Empty upsert and delete arrays are supported. Duplicate IDs in an upsert request are rejected by the runtime; do not rely on last-write-wins ordering within a batch.
Queries, scores, and inclusion flags
A VectorQuery requires exactly one source: vector: number[] or byId: string. A vector array must be nonempty; the runtime rejects an empty array as an invalid argument. The remaining fields are optional:
| Field | Behavior |
|---|
topK | Nonnegative uint32. Omitted or 0 uses the component’s default match count. |
filter | Portable filter DSL applied to record metadata, including dotted paths. Payloads are not filterable. |
includeValues | Defaults to false; omitted embeddings are returned as values: []. |
includePayload | Defaults to false; omitted payloads are returned as undefined. |
metric | Omission uses the collection’s metric. Unsupported overrides fail explicitly. |
scoreThreshold | Finite, inclusive cutoff on the metric’s unnormalized scale. |
Record metadata is returned with query matches independently of the values and payload flags. The result is { matches, metric }; each match contains { record, score }, and metric identifies the effective metric even when the query omitted it.
Cosine and dot product scores are higher-is-better; Euclidean distances are lower-is-better. Thresholds are minimum scores for cosine and dot product, and maximum distances for Euclidean. Do not treat scores as normalized probabilities or compare scores across different metrics as though they shared a scale. Whether a byId query returns the source record itself is component-dependent.
All queries in batchQueryVectors target the same store and collection, with their own query fields. Results preserve request order and contain exactly one of { response: VectorQueryResult } or { error: VectorError }. An empty query array returns an empty result array. Inspect every slot; individual runtime/provider failures do not reject the entire batch, but request-wide failures do.
Cancellation
Every vector operation accepts signal in its options object. Pass an AbortSignal to cancel a request when an application deadline expires or a caller disconnects:
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 30_000);
try {
const result = await client.vector.queryVectors(
"vectorstore",
"documents",
{ vector: [1, 0, 0], topK: 5 },
{ signal: controller.signal },
);
console.log(result.matches);
} finally {
clearTimeout(timeout);
}
This snippet assumes an existing client and collection. Run it inside an async function or an ES module. Cancellation rejects the request but does not guarantee cancellation or rollback of provider-side indexing work. Do not assume both protocols report cancellation with the same error class.
Errors
Await each operation and handle request failures at your application’s error boundary. Check both upsertVectors().failedItems and each batchQueryVectors() result for failures that do not reject the call.
- HTTP request failures: Runtime failures are JavaScript
Error instances whose message is a JSON string containing status (HTTP status code), error (status text), and error_msg (runtime response body). Transport and cancellation errors can have different shapes; do not assume every error message is JSON. - gRPC request failures: Runtime failures preserve
ConnectError codes from @connectrpc/connect. Do not handle them as HTTP status codes. - Structured item/query failures:
VectorError contains a canonical non-OK google.rpc.Code number (1 through 16), a message, and details. These codes are not HTTP status codes. Each detail contains typeUrl and value: HTTP preserves protobuf JSON fields without @type, while gRPC preserves the original protobuf bytes as Uint8Array. - Invalid arguments: Empty store/collection names or record IDs, store/collection names equal to
. or .., invalid payload types, unsupported metric names, nonfinite numbers, invalid JSON, and ambiguous query sources are rejected before the request is sent. Invalid dimensions, topK, or coordinates outside the finite float32 range are also rejected. Type and range failures use TypeError or RangeError; malformed protobuf duration strings can produce parser errors.
Creating an existing collection (HTTP 409, gRPC AlreadyExists), accessing a missing collection or querying by a missing ID (HTTP 404, gRPC NotFound), unsupported component capabilities, and invalid filters (HTTP 400, gRPC InvalidArgument) cause runtime failures. In a batch, a missing byId source fails only that slot with VectorError code 5. A valid query with no matches instead returns an empty matches array. Collection counts exceeding JavaScript’s safe integer range and malformed responses are rejected rather than silently losing precision or substituting defaults.
See the Vector how-to guide for complete setup and runnable examples.