azure-search-client
v3.1.5
Published
Client for Azure Search
Downloads
1,978
Readme
Azure Search Client
npm install azure-search-client
Usage
Basic query (async/await)
Use the async/await
pattern:
const { SearchService } = require('azure-search-client');
const client = new SearchService('myservice', 'mykey');
const resp = await client.indexes.use('myIndex').search({
search: 'hello world',
});
console.log(resp.result.value); // array of result docs
Basic query (callback)
Or use the Node callback
pattern:
client.indexes.use('myIndex').search({
search: 'hello world',
}, (err, resp) => {
if (err) { throw err; }
console.log(resp.result.value); // array of result docs
});
Query API
Use the Azure Search POST query representation
If you are using TypeScript see how to use your own document models on index operations.
client.indexes.use('myIndex').search({
count: true,
facets: ['field1', 'field2'],
filter: `field1 eq 123 and field2 ne 'foo'`,
highlight: 'field1, field2',
highlightPreTag: '<b>',
highlightPostTag: '</b>',
minimumCoverage: 90,
orderby: 'field1 desc, field2',
scoringProfile: 'myProfile',
scoringParameters: [
"currentLocation--122.123,44.77233",
"lastLocation--121.499,44.2113"
],
search: 'hello world',
searchFields: 'field1, field2',
searchMode: SearchMode.any,
select: 'field1, field2',
queryType: QueryType.simple,
skip: 0,
top: 10,
});
Query builders
Use QueryBuilder
, QueryFilter
, QueryFacet
, and LambdaQueryFilter
helpers to build a query using method chaining.
Build a simple query:
await client.indexes.use('myIndex')
.buildQuery()
.count(true)
.search('some search query')
.top(20)
.executeQuery();
Build a query with facets:
await client.indexes.use('myIndex')
.buildQuery()
.buildFacet('myFacetField1', (f) => f.count(20).sortByValue('desc'))
.buildFacet('myFacetField2', (f) => f.interval('day'))
.executeQuery();
There are multiple ways to specify facets (combine them as needed):
await client.indexes.use('myIndex')
.buildQuery()
.facet('myFacetField1', 'myFacetField2') // simple facet parameters
.buildFacet('myFacetField3', (f) => f.count(20).sortByValue('desc')) // callback builder
.facet(new QueryFacet('myFacetField4').count(20)) // constructor builder
.facetExpression('myFacetField5,count:5,interval:100', 'myFacetField6,count10') // raw expression strings
.executeQuery();
Build an and
filtered query:
await client.indexes.use('myIndex')
.buildQuery()
.filter((f) => f.eq('myField1', 123).ne('myField2', 'foo')) // by default, filters are chained with 'and' operator
.executeQuery();
Build an or
filtered query:
await client.indexes.use('myIndex')
.buildQuery()
.filterOr((f) => f.eq('myField1', 123).ne('myField1', 456)) // chain filters with the 'or' operator
.executeQuery();
Build a not
filtered query:
await client.indexes.use('myIndex')
.buildQuery()
.filterNot((f) => f.eq('myField1', 123).ne('myField1', 456)) // chain filters with the 'or' operator
.executeQuery();
Filter on geoDistance
:
await client.indexes.use('myIndex')
.buildQuery()
.filter((f) => f.geoDistance('myGeoField', [122, 80], GeoComparison.lt, 10)) // find documents less than 10km from the point at (122, 80)
.executeQuery();
Filter on a string collection:
await client.indexes.use('myIndex')
.buildQuery()
.filter((f) => f.any('myField1', (x) => x.eq('foo'))) // find documents where any member of 'myField1' is 'foo'
.executeQuery();
Build an arbitrarily complex filter graph:
await client.indexes.use('myIndex')
.buildQuery()
.filter(QueryFilter.and(
QueryFilter.or(
new QueryFilter().eq('field1', 123).eq('field2', 456),
new QueryFilter().eq('field1', 999),
),
new QueryFilter(Logical.not).eq('field3', 'foo'),
))
.executeQuery();
Specify filter as a raw string expression
await client.indexes.use('myIndex')
.buildQuery()
.filter(`myField1 eq 123 and myField2/any(x: x eq 'foo')`)
.executeQuery();
Escape user input when using Lucene query syntax.
await client.indexes.use('myIndex')
.buildQuery()
.queryType(QueryType.full)
.search(QueryBuilder.escape('Find at&t documents')) // => "find at\&t documents"
.executeQuery();
All of the query builders support TypeScript Generics.
Indexing API
azure-search-client
automatically batches indexing operations up to 1000 documents or 16 MB, whichever is lower. Use either the fully buffered API (promise/callback based) or the streaming API, depending on your needs.
Fully buffered indexing API
Index documents using promise async
/await
const docs = getDocuments(); // arbitrarily large array of document objects
const results = await client.indexes.use('myIndex')
.index(docs);
Or using a callback
const docs = getDocuments(); // arbitrarily large array of document objects
client.indexes.use('myIndex')
.index(docs, (err, results) => {
if (err) { throw err; }
});
Streaming indexing API
When it is impractical to fully buffer an indexing payload, consider using the streaming API to pipe document objects into the index as fast as they can be read (while still buffering batches up to the Azure Search payload limits). This technique works best with streaming object sources like csv-parse
and json-stream
.
The Node pipeline API was introduced in Node.js 10.x. For use on older versions, consider using the .pipe() API.
const { pipeline } = require('stream');
const csvParse = require('csv-parse'); // nmm i csv-parse
pipeline(
createReadStream('./data.csv'),
csvParse({ columns: ['key', 'title', 'body']}), // Must specify columns. If your csv contains a header row, skip it using 'from' csv option.
search.indexes.use('myIndex').createIndexingStream(),
(err) => {
if (err) {
console.error('Indexing failed');
} else {
console.log('Indexing complete');
}
}
)
Request Options
You can set optional request-specific options for any request:
client.indexes.use('myIndex').search({
search: 'some search query',
}, {
version: 'thisRequestApiVersion',
key: 'thisRequestKey',
retry: 3,
timeout: 10000,
clientRequestId: '{guid}',
returnClientRequestId: true,
ifMatch: 'someEtag',
ifNoneMatch: 'someEtag',
});
You can set the default API version for your client:
const client = new SearchService('myService', 'myKey', 'myDefaultApiVersion');
JSON has no date representation, so Azure Search returns Date
fields as strings. The search client will automatically parse any string value that looks like a date, but you can disable date parsing in the request options:
client.indexes.use('myIndex').search({
search: 'hello world',
}, {
parseDates: false,
});
Any object with a list()
function accepts an optional $select
option to limit the fields that are fetched:
client.indexes.list({ $select: ['name', 'fields'] });
Response properties
Every API response has some common properties (not every property is available for every request):
const resp = await client.indexes.use('myIndex').search({
search: 'hello world',
});
// response body
resp.result;
// request id
resp.properties.requestId;
// time spent by the search engine
resp.properties.elapsedTime
// an identifier specified by the caller in the original request, if present
resp.properties.clientRequestId
// An opaque string representing the current version of a resource (returned for indexers, indexes, and data sources, but not documents). Use this string in the If-Match or If-None-Match header for optimistic concurrency control.
resp.properties.eTag
// The URL of the newly-created index definition for POST and PUT /indexes requests.
resp.properties.location
// HTTP status code of the current request
resp.statusCode
// Timestamp when the request started
resp.timer.start
// Node `hrtime` until the response headers arrived
resp.timer.response
// Node `hrtime` until the full response body was read
resp.timer.end
Manage search resources
With an admin key, the search client can manage the search resources
Manage Indexes
const indexes = client.indexes;
// CREATE INDEX
await indexes.create({
/* IndexSchema */
});
// LIST INDEXES
await indexes.list();
Manage an index
const index = client.indexes.use('myIndex');
// ANALYZE TEXT
await index.analyze({
/* AnalyzeQuery */
});
// COUNT DOCUMENTS
await index.count();
// DELETE THE INDEX
await index.delete();
// GET THE SCHEMA
await index.get();
// INDEX DOCUMENTS (add/update/delete)
await index.index([
/* IndexDocument */
]);
// LOOKUP DOCUMENT
await index.lookup('myKey');
// SEARCH DOCUMENTS
await index.search({
/* Query */
});
// GET INDEX STATS
await index.statistics();
// SUGGEST DOCUMENTS
await index.suggest({
/* SuggestQuery */
});
Note:
search
,suggest
, andlookup
APIs will automatically parse document fields that look like Dates, returning JavaScriptDate
objects instead. To disable this, use the parseDate option
Manage Data Sources
const dataSources = client.dataSources;
// dataSources => .create, .list
Manage a Data Source
const dataSource = client.dataSources.use('myDataSource');
// dataSource => .get, .delete
Manage Indexers
const indexers = client.indexers;
// indexers => .create, .list
Manage an Indexer
const indexer = client.indexers.use('myIndexer');
// indexer => .get, .delete
// RESET INDEXER STATE
await indexer.reset();
// RUN THE INDEXER
await indexer.run();
// GET CURRENT INDEXER STATUS
await indexer.status();
Manage Synonym Maps
const synonymMaps = client.synonymMaps;
// synonymMaps => .create, .list
Manage a Synonym Map
const synonymMap = client.synonymMaps.use('mySynonymMap');
// synonymMap => .get, .delete
Manage Skill Sets
SkillSets are currently in preview, so be sure to use
ApiVersion.preview
in theSearchService
constructor.When referencing a skill type, input name, or output name, be sure to use the corresponding
enum
value rather than a plain string value.
const skillSets = client.skillSets;
// skillSets => .create, .list
Manage a Skill Set
const skillSet = client.skillSets.use('mySkillSet');
// skillSet => .get, .delete
TypeScript Generics
If you are using TypeScript, azure-search-client
has full support for your custom document types
Strongly typed documents are optional and default to
Document
, which allows for arbitrary document fields.
import { SearchService } from 'azure-search-client';
interface MyDoc {
id: string;
num: number;
}
const resp = await client.indexes.use<MyDoc>('myIndex').search({
search: 'hello',
});
const doc = resp.result.value[0];
doc
is typed as MyDoc & SearchDocument
, giving you compile-time access to id
and num
properties, as well as @search.score
and @search.highlights
.
Use your own Document types wherever documents are used: indexing, search, suggest.
When using a generic TDocument
parameter on your SearchIndex
, type safety also applies to the QueryBuilder
, QueryFilter
, and QueryFacet
utilities.
import { QueryFacet, QueryBuilder, QueryFilter } from 'azure-search-client';
interface MyDoc {
id: string;
num: number;
date: Date;
content: string;
}
const index = client.use<MyDoc>('myIndex');
const query = index.buildQuery() // or new QueryBuilder<MyDoc>()
.searchFields('content') // ok
.facet('num') // ok
// compile errors since 'blah' and 'foo' are not part of the document model
.buildFacet('blah', (facet) => facet.count(20))
.select('id', 'foo')
.highlight('foo')
.orderbyAsc('foo');
const filter = query.buildFilter((filter) => filter // or new QueryFilter<MyDoc>()
.eq('id', 'foo') // ok
.lt('date', new Date()) // ok
.eq('num', 'oops, a string'); // compile failure: string is not assignable to number field