{"id":686,"date":"2020-10-08T11:47:23","date_gmt":"2020-10-08T18:47:23","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/azure-sdk\/?p=686"},"modified":"2020-10-08T11:47:23","modified_gmt":"2020-10-08T18:47:23","slug":"search-app-with-cognitive-search","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/azure-sdk\/search-app-with-cognitive-search\/","title":{"rendered":"Add search to an application with the new Azure Cognitive Search SDK"},"content":{"rendered":"<p><!-- MAKE SURE ALL LINKS DO NOT HAVE LOCALE, i.e remove `en-us` from all links. --><\/p>\n<p>If you&#8217;ve ever used an application that didn&#8217;t include search functionality, you&#8217;ll know how important search is to help you find what you&#8217;re looking for. Whether you&#8217;re building an e-commerce site, an internal website for your company, or any other type of application, it&#8217;s important to help users quickly find what they&#8217;re looking for and search does just that.<\/p>\n<p>With <a href=\"https:\/\/docs.microsoft.com\/azure\/search\/search-what-is-azure-search\">Azure Cognitive Search<\/a> and the <a href=\"https:\/\/aka.ms\/azsdk\">Azure SDKs<\/a>, you can build a search application from scratch or infuse search into an existing application in just a few minutes. This blog post will walk you through the process of building and deploying a simple search application with Azure Cognitive Search and the new <a href=\"https:\/\/github.com\/Azure\/azure-sdk-for-js\/tree\/master\/sdk\/search\/search-documents\/\">Azure SDK for Javascript\/Typescript<\/a>. We&#8217;ll first create an Azure Function to encapsulate the search client and query logic. After that, we&#8217;ll deploy a React template using Azure Static Web Apps to integrate our Azure Functions with a front-end.<\/p>\n<p>This sample uses a demo search service and index hosted by Microsoft. To use this sample, you just need an IDE and access to an Azure subscription.<\/p>\n<p>By the end of this post, you&#8217;ll know how to deploy a sample application that looks like this:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-content\/uploads\/sites\/58\/2020\/10\/20201008-web-app.png\" alt=\"Screenshot of the web app\" \/><\/p>\n<p>You can find the full code for this blog post at: <a href=\"https:\/\/aka.ms\/search-react-template\">aka.ms\/search-react-template<\/a>.<\/p>\n<p>You can find the demo website at: <a href=\"https:\/\/aka.ms\/azs-good-books\">aka.ms\/azs-good-books<\/a>.<\/p>\n<h2>Thinking through the search user experience<\/h2>\n<p>At a high level, there are three pieces of search functionality we need to add to an application to provide an intuitive search experience.<\/p>\n<ol>\n<li><strong>Search<\/strong> &#8211; provides search functionality for the application.<\/li>\n<li><strong>Suggest<\/strong> &#8211; provides suggestions as the user is typing in the search bar.<\/li>\n<li><strong>Document Lookup<\/strong> &#8211; looks up a document by id to retrieve all of its contents for the details page.<\/li>\n<\/ol>\n<h2>Building the application<\/h2>\n<h3>Connecting to a search index<\/h3>\n<p>For this blog post, I&#8217;ve created a search index using the <a href=\"https:\/\/github.com\/zygmuntz\/goodbooks-10k\">goodbooks-10k dataset<\/a> that that is publicly available using the following connection information:<\/p>\n<pre><code class=\"json\">\"SearchServiceName\": \"azs-playground\",\n\"SearchIndexName\": \"good-books\",\n\"SearchAPIKey\": \"03097125077C18172260E41153975439\"\n<\/code><\/pre>\n<p>The index consists of 10,000 popular books that we&#8217;ll search over in our application.<\/p>\n<h3>Forking the repo<\/h3>\n<p>If you want to follow along with this blog post, navigate to the <a href=\"https:\/\/aka.ms\/search-react-template\">repo<\/a> and select <strong>Use this template<\/strong>.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-content\/uploads\/sites\/58\/2020\/10\/20201008-use-template.png\" alt=\"Use this template screenshot\" \/><\/p>\n<p>This will create your own copy of the code that you can deploy and edit as you please.<\/p>\n<h3>Building the Azure Functions<\/h3>\n<p>With our search index and codebase in place, we&#8217;re ready to start building our application. The repo follows a pattern for <a href=\"https:\/\/docs.microsoft.com\/azure\/static-web-apps\/overview\">Azure Static Web Apps<\/a> that we&#8217;ll be using to build and deploy the application. The functions containing search logic can be found in the <code>api<\/code> folder.<\/p>\n<p>One important consideration when building an application is keeping your API keys secure. It&#8217;s a best practice to design our application in a way that your API keys aren&#8217;t accessible from the client.<\/p>\n<p>Azure Functions combined with the Azure SDKs are a simple and effective way to keep our keys out of users&#8217; reach. These functions can also encapsulate any business logic you want to incorporate into the search experience such as security trimming or additional query proccesing.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-content\/uploads\/sites\/58\/2020\/10\/20201008-basic-arch.png\" alt=\"Simple architecture diagram\" \/><\/p>\n<h4>Creating a search client<\/h4>\n<p>With all that said, we&#8217;re ready to get to the code. The <code>api\/search\/index.js<\/code> file contains the Azure Function for search that we&#8217;ll walk through below.<\/p>\n<p>First, we import the <code>@azure\/search-documents<\/code> library from the SDK as follows:<\/p>\n<pre><code class=\"javascript\">const { SearchClient, AzureKeyCredential } = require(\"@azure\/search-documents\");\n<\/code><\/pre>\n<p>Next, we declare a <code>SearchClient<\/code>. This client is declared outside of the main function so that we don&#8217;t create a new <code>SearchClient<\/code> every time the Azure Function is called.<\/p>\n<pre><code class=\"javascript\">const indexName = process.env[\"SearchIndexName\"];\nconst apiKey = process.env[\"SearchApiKey\"];\nconst searchServiceName = process.env[\"SearchServiceName\"];\n\n\/\/ Create a SearchClient to send queries\nconst client = new SearchClient(\n    `https:\/\/${searchServiceName}.search.windows.net\/`,\n    indexName,\n    new AzureKeyCredential(apiKey)\n);\n\nmodule.exports = async function (context, req) {\n    \/\/ main function logic\n    \/\/ remainder of code goes here\n}\n<\/code><\/pre>\n<p>Note that the API key and other search service parameters are read in using <code>process.env<\/code>. This prevents our API key from being checked into source control and also gives us the flexibility to change these parameters from the Azure portal. For local development, these parameters are pulled from <code>local.settings.json<\/code> which should look like this:<\/p>\n<pre><code class=\"json\">{\n  \"IsEncrypted\": false,\n  \"Values\": {\n    \"AzureWebJobsStorage\": \"\",\n    \"FUNCTIONS_WORKER_RUNTIME\": \"node\",\n    \"SearchApiKey\": \"03097125077C18172260E41153975439\",\n    \"SearchServiceName\": \"azs-playground\",\n    \"SearchIndexName\": \"good-books\",\n    \"SearchFacets\": \"authors*,language_code\"\n  }\n}\n<\/code><\/pre>\n<p>If you need to limit access to the application or search index, this logic should be handled in the application layer. Fortunately, Azure Static Web Apps makes it easy to add <a href=\"https:\/\/docs.microsoft.com\/azure\/static-web-apps\/authentication-authorization\">authentication and authorization<\/a> to help secure your data.<\/p>\n<h4>Defining helper functions<\/h4>\n<p>Before the core function logic, we also define two helper function. The first function reads in the facets from <code>proccess.env<\/code> and gets the types of facets. We do this because we need to create filters differently for fields that are arrays. Array fields should be followed by a <code>*<\/code> in the application settings such as <code>authors*<\/code> above:<\/p>\n<pre><code class=\"javascript\">\/\/ parses facets and their types\nconst readFacets = (facetString) =&gt; {\n    let facets = facetString.split(\",\");\n    let output = {};\n    facets.forEach(function (f) {\n        if (f.indexOf('*') &gt; -1) {\n            output[f.replace('*', '')] = 'array';\n        } else {\n            output[f] = 'string';\n        }\n    })\n    return output;\n}\n<\/code><\/pre>\n<p>The next function will take our filters and convert them into odata syntax that the search service can understand:<\/p>\n<pre><code class=\"javascript\">\/\/ creates filters in odata syntax\nconst createFilterExpression = (filterList, facets) =&gt; {\n    let i = 0;\n    let filterExpressions = [];\n\n    while (i &lt; filterList.length) {\n        let field = filterList[i].field;\n        let value = filterList[i].value;\n\n        if (facets[field] === 'array') {\n            filterExpressions.push(`${field}\/any(t: search.in(t, '${value}', ','))`);\n        } else {\n            filterExpressions.push(`${field} eq '${value}'`);\n        }\n        i += 1;\n    }\n\n    return filterExpressions.join(' and ');\n}\n<\/code><\/pre>\n<h4>Adding the search logic<\/h4>\n<p>To start off the core function logic, we read in the parameters from the HTTP request. The <code>facets<\/code> parameter also comes from <code>process.env<\/code> because <a href=\"https:\/\/docs.microsoft.com\/azure\/search\/search-filters-facets\">facets<\/a> are used to create a UI component and are normally consistent across searches:<\/p>\n<pre><code class=\"javascript\">let q = (req.query.q || (req.body && req.body.q));\nconst top = (req.query.top || (req.body && req.body.top));\nconst skip = (req.query.skip || (req.body && req.body.skip));\nconst filters = (req.query.filter || (req.body && req.body.filters));\nconst facets = readFacets(process.env[\"SearchFacets\"]);\n<\/code><\/pre>\n<p>For some basic error handling, we next check if the query string, <code>q<\/code>, is undefined or empty, and we set it to <code>*<\/code> if it is. Searching <code>*<\/code> tells the search service to search everything:<\/p>\n<pre><code class=\"javascript\">\/\/ If search term is empty, search everything\nif (!q || q === \"\") {\n    q = \"*\";\n}\n<\/code><\/pre>\n<p>Now that the parameters are read in, we use them to create a <code>searchOptions<\/code> variable that adds additional parameters to the search:<\/p>\n<pre><code class=\"javascript\">\/\/ Creating SearchOptions for query\nlet searchOptions = {\n    top: top, \/\/ number of results to return\n    skip: skip, \/\/ number of results to skip; used for paging\n    includeTotalCount: true, \/\/ includes the total number of results\n    facets: Object.keys(facets), \/\/ facets to display on the web page\n    filter: createFilterExpression(filters, facets) \/\/ filter to apply to the query\n};\n<\/code><\/pre>\n<p>Next, we send our query to the service to get the search results:<\/p>\n<pre><code class=\"javascript\">\/\/ Sending the search request\nconst searchResults = await client.search(q, searchOptions);\n<\/code><\/pre>\n<p>Finally, we loop through the <code>searchResults<\/code> object to get the results and use them to create an HTTP response object that will be returned from our function:<\/p>\n<pre><code class=\"javascript\">\/\/ Getting results for output\nconst output = [];\nfor await (const result of searchResults.results) {\n    output.push(result);\n}\n\n\/\/ Creating the HTTP Response\ncontext.res = {\n    \/\/ status: 200, \/* Defaults to 200 *\/\n    headers: {\n        \"Content-type\": \"application\/json\"\n    },\n    body: {\n        count: searchResults.count,\n        results: output,\n        facets: searchResults.facets\n    }\n};\n<\/code><\/pre>\n<h4>Handling errors<\/h4>\n<p>We&#8217;ll also want to do some basic error handling to make the application easier to debug. We do this by adding a simple try catch around our search logic. If our search logic fails, we&#8217;ll return a 400 and some details regarding the failure:<\/p>\n<pre><code class=\"javascript\">try {\n    \/\/ search logic from above\n} catch (error) {\n    \/\/ logging the error\n    context.log.error(error);\n\n    \/\/ Creating the HTTP Response\n    context.res = {\n        status: 400,\n        body: {\n            innerStatusCode: error.statusCode || error.code,\n            error: error.details || error.message\n        }\n    };\n}\n<\/code><\/pre>\n<p>And with that, you have an Azure Function ready to add search functionality to an application. In the repo, you&#8217;ll find similar functions for suggestions and document lookup.<\/p>\n<h3>Deploying the application with Azure Static Web Apps<\/h3>\n<p>With the serverless function complete, we&#8217;re ready to connect the search functionality to a front end. In the <code>src<\/code> folder of the repo there is a React template that&#8217;s ready to go. Azure Static Web Apps will host both the front end and the back end of our application making the development and deployment of the application a seamless experience.<\/p>\n<p>No changes are required to run this with the sample index.<\/p>\n<p>To deploy the full application, you first need to create a Static Web App in the Azure portal. Click the button below to create one:<\/p>\n<p><a href=\"https:\/\/portal.azure.com\/?feature.customportal=false#create\/Microsoft.StaticApp\"><img decoding=\"async\" src=\"https:\/\/aka.ms\/deploytoazurebutton\" alt=\"Deploy to Azure button\" \/><\/a><\/p>\n<p>This will walk you through the process of creating the web app and connecting it to your GitHub repo.<\/p>\n<p>After connecting to the repo, you&#8217;ll be asked to include some build details. Set the Build Presets to <code>React<\/code> and then leave the other default values:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-content\/uploads\/sites\/58\/2020\/10\/20201008-setup.png\" alt=\"Azure Static Web Apps Configuration Screenshot\" \/><\/p>\n<p>Once you create the static web app, it will automatically deploy the web app to a URL you can find within the portal.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-content\/uploads\/sites\/58\/2020\/10\/20201008-static-web.png\" alt=\"Azure Static Web Apps Configuration Screenshot\" \/><\/p>\n<p>The last thing you need to do is select configuration and then edit the application settings to add the credentials from <code>local.settings.json<\/code>. It may take a few minutes for this blade to become available in the portal.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-content\/uploads\/sites\/58\/2020\/10\/20201008-config.png\" alt=\"Azure Static Web Apps Configuration Screenshot\" \/><\/p>\n<p>This post won&#8217;t dig into some of the details on building Azure Static Web Apps. However, they&#8217;re a valuable tool and if you&#8217;re interested in learning more about them, check out the <a href=\"https:\/\/docs.microsoft.com\/azure\/static-web-apps\/overview\">documentation<\/a>.<\/p>\n<h2>Use your own index<\/h2>\n<p>After deploying this sample, I&#8217;d encourage you to try it out with your own search index.<\/p>\n<p>Up until now, you&#8217;ve been working with an existing index, but creating and loading an index with your own data is straightforward. You can create an index in the <a href=\"https:\/\/docs.microsoft.com\/azure\/search\/search-import-data-portal\">Azure portal<\/a>, use the <a href=\"https:\/\/docs.microsoft.com\/rest\/api\/searchservice\/create-index\">REST APIs<\/a>, or use any of the Azure SDKs such as the new <a href=\"https:\/\/github.com\/Azure\/azure-sdk-for-js\/tree\/master\/sdk\/search\/search-documents\/\">Azure SDK for Javascript\/TypeScript<\/a> that we used in this blob post. Please see the Azure Cognitive Search <a href=\"https:\/\/docs.microsoft.com\/azure\/search\/search-get-started-portal\">documentation<\/a> for more information on how to get started.<\/p>\n<p>There are two main changes you&#8217;d need to make to use your own index:<\/p>\n<h3>1&#46; Edit application settings in the portal<\/h3>\n<p>Navigate to the Azure portal -> find your Azure Static Web App -> select configuration -> edit the application settings.<\/p>\n<h3>2&#46; Update Result and Detail components<\/h3>\n<p>Much of the UI won&#8217;t require customization, however, if you integrate a new index with this template, you&#8217;ll likely need to update the <code>Results<\/code> component and the <code>Details<\/code> component to reflect the fields in your index.<\/p>\n<p>To give an example, the following code renders the results on the search page in <code>src\/components\/Results\/Result\/Result.js<\/code>. The properties <code>id<\/code>, <code>image_url<\/code>, and <code>original_title<\/code> of <code>props.document<\/code> all correspond to fields in the search index and should be updated to reflect the fields in your own search index:<\/p>\n<pre><code class=\"javascript\">&lt;div className=\"card result\"&gt;\n    &lt;a href={`\/details\/${props.document.id}`}&gt;\n        &lt;img className=\"card-img-top\" src={props.document.image_url} alt=\"book cover\"&gt;&lt;\/img&gt;\n        &lt;div className=\"card-body\"&gt;\n            &lt;h6 className=\"title-style\"&gt;{props.document.original_title}&lt;\/h6&gt;\n        &lt;\/div&gt;\n    &lt;\/a&gt;\n&lt;\/div&gt;\n<\/code><\/pre>\n<p>Check out the <code>docs<\/code> folder of the repo for more detail on customizing the web app.<\/p>\n<p><!-- FOOTER: DO NOT EDIT OR REMOVE --><\/p>\n<p><div  class=\"d-flex justify-content-center\"><a class=\"cta_button_link btn-primary mb-24\" href=\"https:\/\/aka.ms\/azsdk\/releases\" target=\"_blank\">Azure SDK Releases<\/a><\/div><\/p>\n<h2>Azure SDK Blog Contributions<\/h2>\n<p>Thank you for reading this Azure SDK blog post! We hope that you learned something new and welcome you to share this post. We are open to Azure SDK blog contributions. Please contact us at <a href=\"&#109;&#x61;&#105;&#x6c;&#116;&#x6f;&#58;&#x61;z&#115;&#x64;&#107;&#x62;&#108;&#x6f;&#103;&#x40;&#109;&#105;&#x63;&#114;&#x6f;&#115;&#x6f;&#102;&#x74;&#46;&#x63;o&#109;\">&#x61;z&#115;&#x64;&#107;&#x62;&#108;&#x6f;&#103;&#x40;&#109;&#105;&#x63;&#114;&#x6f;&#115;&#x6f;&#102;&#x74;&#46;&#x63;o&#109;<\/a> with your topic and we&#8217;ll get you setup as a guest blogger.<\/p>\n<h2>Azure SDK Links<\/h2>\n<ul>\n<li>Azure SDK Website: <a href=\"https:\/\/aka.ms\/azsdk\">aka.ms\/azsdk<\/a><\/li>\n<li>Azure SDK Intro (3 minute video): <a href=\"https:\/\/aka.ms\/azsdk\/intro\">aka.ms\/azsdk\/intro<\/a><\/li>\n<li>Azure SDK Intro Deck (PowerPoint deck): <a href=\"https:\/\/aka.ms\/azsdk\/intro\/deck\">aka.ms\/azsdk\/intro\/deck<\/a><\/li>\n<li>Azure SDK Releases: <a href=\"https:\/\/aka.ms\/azsdk\/releases\">aka.ms\/azsdk\/releases<\/a><\/li>\n<li>Azure SDK Blog: <a href=\"https:\/\/aka.ms\/azsdk\/blog\">aka.ms\/azsdk\/blog<\/a><\/li>\n<li>Azure SDK Twitter: <a href=\"https:\/\/twitter.com\/AzureSDK\">twitter.com\/AzureSDK<\/a><\/li>\n<li>Azure SDK Design Guidelines: <a href=\"https:\/\/aka.ms\/azsdk\/guide\">aka.ms\/azsdk\/guide<\/a><\/li>\n<li>Azure SDKs &amp; Tools: <a href=\"https:\/\/azure.microsoft.com\/downloads\">azure.microsoft.com\/downloads<\/a><\/li>\n<li>Azure SDK Central Repository: <a href=\"https:\/\/github.com\/azure\/azure-sdk#azure-sdk\">github.com\/azure\/azure-sdk<\/a><\/li>\n<li>Azure SDK for .NET: <a href=\"https:\/\/github.com\/azure\/azure-sdk-for-net\">github.com\/azure\/azure-sdk-for-net<\/a><\/li>\n<li>Azure SDK for Java: <a href=\"https:\/\/github.com\/azure\/azure-sdk-for-java\">github.com\/azure\/azure-sdk-for-java<\/a><\/li>\n<li>Azure SDK for Python: <a href=\"https:\/\/github.com\/azure\/azure-sdk-for-python\">github.com\/azure\/azure-sdk-for-python<\/a><\/li>\n<li>Azure SDK for JavaScript\/TypeScript: <a href=\"https:\/\/github.com\/azure\/azure-sdk-for-js\">github.com\/azure\/azure-sdk-for-js<\/a><\/li>\n<li>Azure SDK for Android: <a href=\"https:\/\/github.com\/Azure\/azure-sdk-for-android\">github.com\/Azure\/azure-sdk-for-android<\/a><\/li>\n<li>Azure SDK for iOS: <a href=\"https:\/\/github.com\/Azure\/azure-sdk-for-ios\">github.com\/Azure\/azure-sdk-for-ios<\/a><\/li>\n<li>Azure SDK for Go: <a href=\"https:\/\/github.com\/Azure\/azure-sdk-for-go\">github.com\/Azure\/azure-sdk-for-go<\/a><\/li>\n<li>Azure SDK for C: <a href=\"https:\/\/github.com\/Azure\/azure-sdk-for-c\">github.com\/Azure\/azure-sdk-for-c<\/a><\/li>\n<li>Azure SDK for C++: <a href=\"https:\/\/github.com\/Azure\/azure-sdk-for-cpp\">github.com\/Azure\/azure-sdk-for-cpp<\/a><\/li>\n<\/ul>\n<p><!-- FOOTER: DO NOT EDIT OR REMOVE --><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Learn how to build an application with Azure Cognitive Search, Azure Static Web Apps, Azure Functions, and the Azure SDK<\/p>\n","protected":false},"author":41125,"featured_media":688,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[763,765,764,159],"class_list":["post-686","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-azure-sdk","tag-azure-cognitive-search","tag-azure-functions","tag-azure-static-web-apps","tag-javascript"],"acf":[],"blog_post_summary":"<p>Learn how to build an application with Azure Cognitive Search, Azure Static Web Apps, Azure Functions, and the Azure SDK<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/posts\/686","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/users\/41125"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/comments?post=686"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/posts\/686\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/media\/688"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/media?parent=686"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/categories?post=686"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/tags?post=686"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}