Quickstart: Create an Azure Cognitive Search index using the JavaScript SDK
Use the JavaScript/TypeScript SDK for Azure Cognitive Search to create a Node.js application in JavaScript that creates, loads, and queries a search index.
This article demonstrates how to create the application step by step. Alternatively, you can download the source code and data and run the application from the command line.
If you don't have an Azure subscription, create a free account before you begin.
Prerequisites
Before you begin, have the following tools and services:
Visual Studio Code or another IDE
Azure Cognitive Search. Create a service or find an existing service. You can use a free service for this quickstart.
Set up your project
Start by getting the endpoint and key for your search service. Then create a new project with npm as outlined below.
Copy a key and endpoint
Calls to the service require a URL endpoint and an access key on every request. As a first step, find the API key and URL to add to your project. You'll specify both values when creating the client in a later step.
Sign in to the Azure portal, and in your search service Overview page, get the URL. An example endpoint might look like
https://mydemo.search.windows.net
.In Settings > Keys, get an admin key for full rights on the service, required if you're creating or deleting objects. There are two interchangeable primary and secondary keys. You can use either one.
All requests require an api-key on every request sent to your service. Having a valid key establishes trust, on a per request basis, between the application sending the request and the service that handles it.
Create a new npm project
Begin by opening VS Code and its integrated terminal or another terminal such as the Node.js command prompt.
Create a development directory, giving it the name
quickstart
:mkdir quickstart cd quickstart
Initialize an empty project with npm by running the following command. To fully initialize the project, press Enter multiple times to accept the default values, except for the License, which you should set to "MIT".
npm init
Install
@azure/search-documents
, the JavaScript/TypeScript SDK for Azure Cognitive Search.npm install @azure/search-documents
Install
dotenv
, which is used to import the environment variables such as your search service name and API key.npm install dotenv
Confirm that you've configured the projects and its dependencies by checking that your package.json file looks similar to the following json:
{ "name": "quickstart", "version": "1.0.0", "description": "Azure Cognitive Search Quickstart", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [ "Azure", "Search" ], "author": "Your Name", "license": "MIT", "dependencies": { "@azure/search-documents": "^11.3.0", "dotenv": "^16.0.2" } }
Create a file .env to hold your search service parameters:
SEARCH_API_KEY=<search-admin-key> SEARCH_API_ENDPOINT=https://<search-service-name>.search.windows.net
Replace the <search-service-name>
value with the name of your search service. Replace <search-admin-key>
with the key value you recorded earlier.
Create index.js file
Next we create an index.js file, which is the main file that will host our code.
At the top of this file, we import the @azure/search-documents
library:
const { SearchIndexClient, SearchClient, AzureKeyCredential, odata } = require("@azure/search-documents");
Next, we need to require the dotenv
package to read in the parameters from the .env file as follows:
// Load the .env file if it exists
require("dotenv").config();
// Getting endpoint and apiKey from .env file
const endpoint = process.env.SEARCH_API_ENDPOINT || "";
const apiKey = process.env.SEARCH_API_KEY || "";
With our imports and environment variables in place, we're ready to define the main function.
Most of the functionality in the SDK is asynchronous so we make our main function async
. We also include a main().catch()
below the main function to catch and log any errors encountered:
async function main() {
console.log(`Running Azure Cognitive Search JavaScript quickstart...`);
if (!endpoint || !apiKey) {
console.log("Make sure to set valid values for endpoint and apiKey with proper authorization.");
return;
}
// remaining quickstart code will go here
}
main().catch((err) => {
console.error("The sample encountered an error:", err);
});
With that in place, we're ready to create an index.
1 - Create index
Create a file hotels_quickstart_index.json. This file defines how Azure Cognitive Search works with the documents you'll be loading in the next step. Each field will be identified by a name
and have a specified type
. Each field also has a series of index attributes that specify whether Azure Cognitive Search can search, filter, sort, and facet upon the field. Most of the fields are simple data types, but some, like AddressType
are complex types that allow you to create rich data structures in your index. You can read more about supported data types and index attributes described in Create Index (REST).
Add the following content to hotels_quickstart_index.json or download the file.
{
"name": "hotels-quickstart",
"fields": [
{
"name": "HotelId",
"type": "Edm.String",
"key": true,
"filterable": true
},
{
"name": "HotelName",
"type": "Edm.String",
"searchable": true,
"filterable": false,
"sortable": true,
"facetable": false
},
{
"name": "Description",
"type": "Edm.String",
"searchable": true,
"filterable": false,
"sortable": false,
"facetable": false,
"analyzerName": "en.lucene"
},
{
"name": "Description_fr",
"type": "Edm.String",
"searchable": true,
"filterable": false,
"sortable": false,
"facetable": false,
"analyzerName": "fr.lucene"
},
{
"name": "Category",
"type": "Edm.String",
"searchable": true,
"filterable": true,
"sortable": true,
"facetable": true
},
{
"name": "Tags",
"type": "Collection(Edm.String)",
"searchable": true,
"filterable": true,
"sortable": false,
"facetable": true
},
{
"name": "ParkingIncluded",
"type": "Edm.Boolean",
"filterable": true,
"sortable": true,
"facetable": true
},
{
"name": "LastRenovationDate",
"type": "Edm.DateTimeOffset",
"filterable": true,
"sortable": true,
"facetable": true
},
{
"name": "Rating",
"type": "Edm.Double",
"filterable": true,
"sortable": true,
"facetable": true
},
{
"name": "Address",
"type": "Edm.ComplexType",
"fields": [
{
"name": "StreetAddress",
"type": "Edm.String",
"filterable": false,
"sortable": false,
"facetable": false,
"searchable": true
},
{
"name": "City",
"type": "Edm.String",
"searchable": true,
"filterable": true,
"sortable": true,
"facetable": true
},
{
"name": "StateProvince",
"type": "Edm.String",
"searchable": true,
"filterable": true,
"sortable": true,
"facetable": true
},
{
"name": "PostalCode",
"type": "Edm.String",
"searchable": true,
"filterable": true,
"sortable": true,
"facetable": true
},
{
"name": "Country",
"type": "Edm.String",
"searchable": true,
"filterable": true,
"sortable": true,
"facetable": true
}
]
}
],
"suggesters": [
{
"name": "sg",
"searchMode": "analyzingInfixMatching",
"sourceFields": [
"HotelName"
]
}
]
}
With our index definition in place, we want to import hotels_quickstart_index.json at the top of index.js so the main function can access the index definition.
const indexDefinition = require('./hotels_quickstart_index.json');
Within the main function, we then create a SearchIndexClient
, which is used to create and manage indexes for Azure Cognitive Search.
const indexClient = new SearchIndexClient(endpoint, new AzureKeyCredential(apiKey));
Next, we want to delete the index if it already exists. This operation is a common practice for test/demo code.
We do this by defining a simple function that tries to delete the index.
async function deleteIndexIfExists(indexClient, indexName) {
try {
await indexClient.deleteIndex(indexName);
console.log('Deleting index...');
} catch {
console.log('Index does not exist yet.');
}
}
To run the function, we extract the index name from the index definition and pass the indexName
along with the indexClient
to the deleteIndexIfExists()
function.
const indexName = indexDefinition["name"];
console.log('Checking if index exists...');
await deleteIndexIfExists(indexClient, indexName);
After that, we're ready to create the index with the createIndex()
method.
console.log('Creating index...');
let index = await indexClient.createIndex(indexDefinition);
console.log(`Index named ${index.name} has been created.`);
Run the sample
At this point, you're ready to run the sample. Use a terminal window to run the following command:
node index.js
If you downloaded the source code and haven't installed the required packages yet, run npm install
first.
You should see a series of messages describing the actions being taken by the program.
Open the Overview of your search service in the Azure portal. Select the Indexes tab. You should see something like the following example:
In the next step, you'll add data to index.
2 - Load documents
In Azure Cognitive Search, documents are data structures that are both inputs to indexing and outputs from queries. You can push such data to the index or use an indexer. In this case, we'll programatically push the documents to the index.
Document inputs might be rows in a database, blobs in Blob storage, or, as in this sample, JSON documents on disk. You can either download hotels.json or create your own hotels.json file with the following content:
{
"value": [
{
"HotelId": "1",
"HotelName": "Secret Point Motel",
"Description": "The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Time's Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.",
"Description_fr": "L'hôtel est idéalement situé sur la principale artère commerciale de la ville en plein cœur de New York. A quelques minutes se trouve la place du temps et le centre historique de la ville, ainsi que d'autres lieux d'intérêt qui font de New York l'une des villes les plus attractives et cosmopolites de l'Amérique.",
"Category": "Boutique",
"Tags": ["pool", "air conditioning", "concierge"],
"ParkingIncluded": false,
"LastRenovationDate": "1970-01-18T00:00:00Z",
"Rating": 3.6,
"Address": {
"StreetAddress": "677 5th Ave",
"City": "New York",
"StateProvince": "NY",
"PostalCode": "10022"
}
},
{
"HotelId": "2",
"HotelName": "Twin Dome Motel",
"Description": "The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts.",
"Description_fr": "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.",
"Category": "Boutique",
"Tags": ["pool", "free wifi", "concierge"],
"ParkingIncluded": "false",
"LastRenovationDate": "1979-02-18T00:00:00Z",
"Rating": 3.6,
"Address": {
"StreetAddress": "140 University Town Center Dr",
"City": "Sarasota",
"StateProvince": "FL",
"PostalCode": "34243"
}
},
{
"HotelId": "3",
"HotelName": "Triple Landscape Hotel",
"Description": "The Hotel stands out for its gastronomic excellence under the management of William Dough, who advises on and oversees all of the Hotel’s restaurant services.",
"Description_fr": "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.",
"Category": "Resort and Spa",
"Tags": ["air conditioning", "bar", "continental breakfast"],
"ParkingIncluded": "true",
"LastRenovationDate": "2015-09-20T00:00:00Z",
"Rating": 4.8,
"Address": {
"StreetAddress": "3393 Peachtree Rd",
"City": "Atlanta",
"StateProvince": "GA",
"PostalCode": "30326"
}
},
{
"HotelId": "4",
"HotelName": "Sublime Cliff Hotel",
"Description": "Sublime Cliff Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 1800 palace.",
"Description_fr": "Le sublime Cliff Hotel est situé au coeur du centre historique de sublime dans un quartier extrêmement animé et vivant, à courte distance de marche des sites et monuments de la ville et est entouré par l'extraordinaire beauté des églises, des bâtiments, des commerces et Monuments. Sublime Cliff fait partie d'un Palace 1800 restauré avec amour.",
"Category": "Boutique",
"Tags": ["concierge", "view", "24-hour front desk service"],
"ParkingIncluded": true,
"LastRenovationDate": "1960-02-06T00:00:00Z",
"Rating": 4.6,
"Address": {
"StreetAddress": "7400 San Pedro Ave",
"City": "San Antonio",
"StateProvince": "TX",
"PostalCode": "78216"
}
}
]
}
Similar to what we did with the indexDefinition, we also need to import hotels.json
at the top of index.js so that the data can be accessed in our main function.
const hotelData = require('./hotels.json');
To index data into the search index, we now need to create a SearchClient
. While the SearchIndexClient
is used to create and manage an index, the SearchClient
is used to upload documents and query the index.
There are two ways to create a SearchClient
. The first option is to create a SearchClient
from scratch:
const searchClient = new SearchClient(endpoint, indexName, new AzureKeyCredential(apiKey));
Alternatively, you can use the getSearchClient()
method of the SearchIndexClient
to create the SearchClient
:
const searchClient = indexClient.getSearchClient(indexName);
Now that the client is defined, upload the documents into the search index. In this case, we use the mergeOrUploadDocuments()
method, which will upload the documents or merge them with an existing document if a document with the same key already exists.
console.log('Uploading documents...');
let indexDocumentsResult = await searchClient.mergeOrUploadDocuments(hotelData['value']);
console.log(`Index operations succeeded: ${JSON.stringify(indexDocumentsResult.results[0].succeeded)}`);
Run the program again with node index.js
. You should see a slightly different set of messages from those you saw in Step 1. This time, the index does exist, and you should see a message about deleting it before the app creates the new index and posts data to it.
Before we run the queries in the next step, define a function to have the program wait for one second. This is done just for test/demo purposes to ensure the indexing finishes and that the documents are available in the index for our queries.
function sleep(ms) {
var d = new Date();
var d2 = null;
do {
d2 = new Date();
} while (d2 - d < ms);
}
To have the program wait for one second, call the sleep
function like below:
sleep(1000);
3 - Search an index
With an index created and documents uploaded, you're ready to send queries to the index. In this section, we'll send five different queries to the search index to demonstrate different pieces of query functionality available to you.
The queries are written in a sendQueries()
function that we'll call in the main function as follows:
await sendQueries(searchClient);
Queries are sent using the search()
method of searchClient
. The first parameter is the search text and the second parameter specifies search options.
The first query searches *
, which is equivalent to searching everything and selects three of the fields in the index. It's a best practice to only select
the fields you need because pulling back unnecessary data can add latency to your queries.
The searchOptions
for this query also has includeTotalCount
set to true
, which will return the number of matching results found.
async function sendQueries(searchClient) {
console.log('Query #1 - search everything:');
let searchOptions = {
includeTotalCount: true,
select: ["HotelId", "HotelName", "Rating"]
};
let searchResults = await searchClient.search("*", searchOptions);
for await (const result of searchResults.results) {
console.log(`${JSON.stringify(result.document)}`);
}
console.log(`Result count: ${searchResults.count}`);
// remaining queries go here
}
The remaining queries outlined below should also be added to the sendQueries()
function. They're separated here for readability.
In the next query, we specify the search term "wifi"
and also include a filter to only return results where the state is equal to 'FL'
. Results are also ordered by the Hotel's Rating
.
console.log('Query #2 - Search with filter, orderBy, and select:');
let state = 'FL';
searchOptions = {
filter: odata`Address/StateProvince eq ${state}`,
orderBy: ["Rating desc"],
select: ["HotelId", "HotelName", "Rating"]
};
searchResults = await searchClient.search("wifi", searchOptions);
for await (const result of searchResults.results) {
console.log(`${JSON.stringify(result.document)}`);
}
Next, the search is limited to a single searchable field using the searchFields
parameter. This approach is a great option to make your query more efficient if you know you're only interested in matches in certain fields.
console.log('Query #3 - Limit searchFields:');
searchOptions = {
select: ["HotelId", "HotelName", "Rating"],
searchFields: ["HotelName"]
};
searchResults = await searchClient.search("sublime cliff", searchOptions);
for await (const result of searchResults.results) {
console.log(`${JSON.stringify(result.document)}`);
}
console.log();
Another common option to include in a query is facets
. Facets allow you to build out filters on your UI to make it easy for users to know what values they can filter down to.
console.log('Query #4 - Use facets:');
searchOptions = {
facets: ["Category"],
select: ["HotelId", "HotelName", "Rating"],
searchFields: ["HotelName"]
};
searchResults = await searchClient.search("*", searchOptions);
for await (const result of searchResults.results) {
console.log(`${JSON.stringify(result.document)}`);
}
The final query uses the getDocument()
method of the searchClient
. This allows you to efficiently retrieve a document by its key.
console.log('Query #5 - Lookup document:');
let documentResult = await searchClient.getDocument(key='3')
console.log(`HotelId: ${documentResult.HotelId}; HotelName: ${documentResult.HotelName}`)
Run the sample
Run the program with node index.js
. Now, in addition to the previous steps, the queries will be sent and the results written to the console.
Clean up resources
When you're working in your own subscription, it's a good idea at the end of a project to identify whether you still need the resources you created. Resources left running can cost you money. You can delete resources individually or delete the resource group to delete the entire set of resources.
You can find and manage resources in the portal, using the All resources or Resource groups link in the left-navigation pane.
If you're using a free service, remember the limit of three indexes, indexers, and data sources. You can delete individual items in the portal to stay under the limit.
Next steps
In this JavaScript quickstart, you worked through a series of tasks to create an index, load it with documents, and run queries. To continue learning, try the following tutorial and sample as a springboard for trying suggesters (type-ahead or autocomplete queries), filters, and faceted navigation.
Feedback
Submit and view feedback for