<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Algolia</title>
    <description>The latest articles on DEV Community by Algolia (@algolia).</description>
    <link>https://dev.to/algolia</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F167%2Fe7ad230f-5230-4393-9157-92cc7efeb38d.png</url>
      <title>DEV Community: Algolia</title>
      <link>https://dev.to/algolia</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/algolia"/>
    <language>en</language>
    <item>
      <title>Setting up your MacBook for mobile Flutter development</title>
      <dc:creator>Chuck Meyer</dc:creator>
      <pubDate>Tue, 20 Dec 2022 19:53:14 +0000</pubDate>
      <link>https://dev.to/algolia/setting-up-your-macbook-for-mobile-flutter-development-3pj4</link>
      <guid>https://dev.to/algolia/setting-up-your-macbook-for-mobile-flutter-development-3pj4</guid>
      <description>&lt;p&gt;This month I'm trying to level up on my mobile development skills by learning &lt;a href="https://flutter.dev" rel="noopener noreferrer"&gt;Flutter&lt;/a&gt; with and end goal of building a mobile search application. The first step was setting up a functional local dev environment on my laptop. I realized that there would be a few steps, but I didn't anticipate needing SIX different languages to get up and running! &lt;/p&gt;

&lt;p&gt;In this post, I'll walk you through setting up your MacBook for cross-platform Flutter development on iPhone and Android devices. &lt;/p&gt;

&lt;h2&gt;
  
  
  Assumptions
&lt;/h2&gt;

&lt;p&gt;You will need a terminal configured just the way a like it and the &lt;code&gt;brew&lt;/code&gt; package manager installed. If you don't have it already, you should &lt;a href="https://brew.sh/" rel="noopener noreferrer"&gt;install brew first&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install Flutter
&lt;/h2&gt;

&lt;p&gt;The easiest way to install flutter is using &lt;code&gt;brew&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;brew install --cask flutter&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Once &lt;code&gt;brew&lt;/code&gt; has installed flutter and all of its dependencies, you can use flutter itself to track what's left to do:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;flutter doctor&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is also a good time to install command completion for &lt;code&gt;flutter&lt;/code&gt;. Here's the command to that for &lt;code&gt;zsh&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;flutter bash-completion &amp;gt;&amp;gt; ~/.zshrc&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;Depending on what you already had installed, you probably have a lot left to do, so let's get to it!&lt;/p&gt;

&lt;h2&gt;
  
  
  The iOS build path
&lt;/h2&gt;

&lt;p&gt;For iOS development, you need a full version of Xcode including the iOS SDK, not just the command line tools. It's easiest to install this from the App Store. Once it's finished installing, drop back to the command line and run the following command to accept the License agreement and install any additional components.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo xcodebuild -runFirstLaunch&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You'll also want &lt;a href="https://cocoapods.org/" rel="noopener noreferrer"&gt;cocoapods&lt;/a&gt; for dependency management on the iOS side. Install it using &lt;code&gt;brew&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;cocoapods
pod setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That should be everything you need for iOS. Let's test to see if it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the iOS build path
&lt;/h2&gt;

&lt;p&gt;It's time to launch an iPhone simulator and create your first Flutter project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;open &lt;span class="nt"&gt;-a&lt;/span&gt; Simulator
flutter create my_first_app
&lt;span class="nb"&gt;cd &lt;/span&gt;my_first_app
flutter run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should launch an iPhone emulator, create the default click-counting Flutter app, and load it onto the emulated iPhone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Android build path
&lt;/h2&gt;

&lt;p&gt;On the Android side, you'll want to install Android Studio and the Android SDK. &lt;a href="https://www.lifewire.com/how-to-install-android-sdk-4770060" rel="noopener noreferrer"&gt;This guide&lt;/a&gt; walks you through the steps, but here's an overview. &lt;/p&gt;

&lt;p&gt;First, &lt;a href="https://developer.android.com/studio/" rel="noopener noreferrer"&gt;download&lt;/a&gt; and install Android Studio for MacOS.&lt;br&gt;
Launch Android Studio and create your first project.&lt;br&gt;
Select &lt;code&gt;Tools&lt;/code&gt; -&amp;gt; &lt;code&gt;SDK Manager&lt;/code&gt; -&amp;gt; &lt;code&gt;Android SDK&lt;/code&gt; -&amp;gt; &lt;code&gt;SDK Tools&lt;/code&gt; and install the &lt;code&gt;Android SDK Command-Line Tools&lt;/code&gt; and the latest &lt;code&gt;Android Emulator&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Switch back to the command line to accept all necessary licenses for Android using the following command.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;flutter doctor --android-licenses&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You'll need a Java runtime environment if you didn't already have one installed. The easiest is to just install the &lt;a href="https://www.java.com/en/download/" rel="noopener noreferrer"&gt;latest JRE form Oracle&lt;/a&gt;.&lt;br&gt;
That should do it for the Android side. Time to test.&lt;/p&gt;
&lt;h2&gt;
  
  
  Testing the Android build path
&lt;/h2&gt;

&lt;p&gt;To test the Android side, install an emulator and set it up as a target for &lt;code&gt;flutter&lt;/code&gt;. First, choose an image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;~/Library/Android/sdk/tools/bin/sdkmanager &lt;span class="nt"&gt;--list&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;system-images
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Select a recent system image to install and create an Android Virtual Device. Then you can launch the emulator and load the click-counting Flutter app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/Library/Android/sdk/tools/bin/sdkmanager --install "system-images;android-33;google_apis;arm64-v8a"
~/Library/Android/sdk/tools/bin/avdmanager create avd -n "pixel_5_api33" -k "system-images;android-33;google_apis;arm64-v8a" -b "arm64-v8a" -d "pixel"

flutter emulators –-launch pixel_5_api33
flutter run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You are now ready to begin cross-platform development of your mobile app!&lt;/p&gt;

&lt;p&gt;Next time, we'll add search to our mobile applications using the Algolia &lt;a href="https://www.algolia.com/doc/guides/building-search-ui/getting-started/flutter/" rel="noopener noreferrer"&gt;Flutter Helper&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>watercooler</category>
    </item>
    <item>
      <title>DevTool Intro: The Algolia CLI!</title>
      <dc:creator>Khalid Elassaad</dc:creator>
      <pubDate>Mon, 15 Aug 2022 19:11:00 +0000</pubDate>
      <link>https://dev.to/algolia/devtool-intro-the-algolia-cli-28c5</link>
      <guid>https://dev.to/algolia/devtool-intro-the-algolia-cli-28c5</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wake up Neo...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need you to reset the configuration for our pharmaceutical products index! We were messing around with the highlighting settings, and now results for the query “red pills” are highlighted blue, which is really very confusing…&lt;/p&gt;

&lt;p&gt;Also, we’re a little concerned about the possibility of all experienced reality being a simulation run by machines to keep humanity enslaved!&lt;/p&gt;

&lt;p&gt;Please fix the index first though!&lt;/p&gt;

&lt;p&gt;— &lt;/p&gt;

&lt;p&gt;Oh man, don’t you all just love getting woken up before sunrise on a Monday for stuff like this? In an ideal world, this would be a quick fix. With a flourish of my black trench coat, I would roll out of bed and land in a crouch in the middle of the room, laptop in hand. I’d press enter to skip the white rabbit and ignore the knocking at my door. With one hand, I’d adjust my too-dark pince-nez glasses while the other hand fires off a command in seconds. Something short and sweet and to the point. Something like this…&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;algolia settings import prod_pharmaceuticals_index -F pharm_settings_snapshot.ndjson
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A glance at the terminal reveals the cheerful response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✓ Imported settings on prod_pharmaceuticals_index
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nice… Now what was that other thing about machines enslaving humanity?&lt;/p&gt;

&lt;p&gt;—&lt;/p&gt;

&lt;h3&gt;
  
  
  Meet the Algolia CLI!
&lt;/h3&gt;

&lt;p&gt;If you’re anything like me, the command line is your friend and trusted ally. Whether I’m testing a quick API call or coding in my favorite IDE, working with the command line keeps me iterating at a smooth clip. Additionally, I love that anything I do through a command line interface (CLI) can be easily scripted and scheduled to run however and whenever I like. With a strong set of tools in my CLI toolbelt, solving problems and automating solutions is a walk in the park. &lt;/p&gt;

&lt;p&gt;That’s why we are so excited to announce the Public Beta launch for the brand new Algolia CLI Tool, with the full release coming soon!&lt;/p&gt;

&lt;p&gt;Algolia CLI makes uploading an index, automating common dashboard operations, or saving and reloading snapshots of your configurations, possible right from the command line! No API client needed!&lt;/p&gt;

&lt;p&gt;So how do I start? Don’t worry, you won’t have to visit the oracle for this one… Getting started with the Algolia CLI is a breeze!&lt;/p&gt;

&lt;p&gt;If you’re on MacOS, simply use homebrew to install the tool by running this command in terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brew install algolia/algolia-cli/algolia
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Want to poke at the code and build it yourself? The Algolia CLI is fully open-source, under an MIT license, and lives in a public github repository (which you can &lt;a href="https://github.com/algolia/cli"&gt;find here&lt;/a&gt;)!&lt;/p&gt;

&lt;p&gt;Releases for Linux and Windows coming soon!&lt;/p&gt;

&lt;p&gt;Let’s take a look at  some specific use cases to see how the Algolia CLI can make your life easier, so you can focus on saving the world, you star!&lt;/p&gt;

&lt;p&gt;—&lt;/p&gt;

&lt;h3&gt;
  
  
  Example #0: Set your active Algolia application with profiles
&lt;/h3&gt;

&lt;p&gt;Any Algolia CLI command can be called with the &lt;code&gt;--admin-api-key [string]&lt;/code&gt; and &lt;code&gt;--application-id [string]&lt;/code&gt; flags to specify which application to interact with. To enable you to fire off commands without having to lug around api-keys and app-ids in your clipboard all the time, you may use the following command to register a default profile.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;algolia profile add --name [string] --app-id [string] --admin-api-key [string] --default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✓ Profile 'test_pharm_app' (##APP#ID##) successfully added and set as default.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--default&lt;/code&gt; flag tells the CLI which app to use with future commands when no profile or API key/app ID pair are provided. Only one application can be set as default at a time. Taking time to register profiles makes future interactions fluid, especially when working with 2 or more Algolia apps. &lt;/p&gt;

&lt;p&gt;You can also interactively provide the fields for this command by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;algolia profile add
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tool will then prompt you for each field, one at a time, and add the application as specified.&lt;/p&gt;

&lt;p&gt;To see a list of all added applications, simply run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;algolia profile list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME                  APP ID      NUMBER OF INDICES  DEFAULT
test_pharm_app        #APP#ID#1#  2                  ✓
playground_pharm_app  #APP#ID#2#  1
prod_pharm_app        #APP#ID#3#  15
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Awesome! We see here that we have multiple Algolia apps added, and that the app &lt;code&gt;test_pharm_app&lt;/code&gt; is our default!&lt;/p&gt;

&lt;p&gt;Note: app profiles are saved in &lt;code&gt;~/.config/algolia/config.toml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Let’s upload some data!&lt;/p&gt;

&lt;p&gt;—&lt;/p&gt;

&lt;h3&gt;
  
  
  Example #1: Create an index and upload records from a file
&lt;/h3&gt;

&lt;p&gt;I want to create a new index, give it a relevant name, and upload the records from a file on my local system. Can the CLI handle that? Of course it can!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;algolia objects import new_index_name -F ./path_to_file.ndjson
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Did you notice that we didn’t specify an app ID, api key, nor app profile? In this case, the CLI will act upon the default application (which is &lt;code&gt;test_pharm_app&lt;/code&gt; as indicated in the previous step).&lt;/p&gt;

&lt;p&gt;We can also pass content to the CLI for upload via stdin by replacing the &lt;code&gt;-F&lt;/code&gt; flag with &lt;code&gt;-&lt;/code&gt;. Let’s pipe some commands together! The following command produces the same result as the previous one(read file and upload contents to specified index), but uses cat to pass the contents of the file to the CLI tool’s stdin as opposed to passing the file path as an argument. Neat!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat ./path_to_file.ndjson | algolia objects import new_index_name -F -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="http://ndjson.org/"&gt;What is ndjson?&lt;/a&gt; Newline delimited JSON is the format the Algolia CLI reads from and writes to files. This means that any command that passes ndjson formatted data as output or accepts it as input can be piped together with an Algolia CLI command! We’ll see more of this in the next example&lt;/p&gt;

&lt;p&gt;– &lt;/p&gt;

&lt;h3&gt;
  
  
  Example #2: Managing index settings snapshots
&lt;/h3&gt;

&lt;p&gt;So far, we’ve connected our CLI to our Algolia apps, and we’ve uploaded some data to an index! Let’s take it a step further by snapshotting an index’s settings so we can restore them to a healthy checkpoint in the future.&lt;/p&gt;

&lt;p&gt;Which indices exist in our default app, &lt;code&gt;test_pharm_app&lt;/code&gt;?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;algolia index list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME               ENTRIES  SIZE    UPDATED AT  CREATED AT  LAST BUILD DURATION  PRIMARY  REPLICAS
pills_treatments   1,000    100 kB  1 day ago   1 day ago   2s                            []
pills_cures        0        0 B     2 days ago  2 days ago  3s                            []
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s save the settings for index &lt;code&gt;pills_treatments&lt;/code&gt; to a file on our system.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;algolia settings get pills_treatments &amp;gt; ./pt_settings_snapshot.ndjson
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Snapshot created! Now, reverting to the snapshot is as easy as…&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;algolia settings import pills_treatments -F ./pt_settings_snapshot.ndjson
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✓ Imported settings on pills_treatments
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can also transfer settings from one index to another in one line! Let’s copy the settings for the &lt;code&gt;pills_treatments&lt;/code&gt; index over to the &lt;code&gt;pills_cures&lt;/code&gt; index! In the import command, we’ll use the &lt;code&gt;-&lt;/code&gt; value after the &lt;code&gt;-F&lt;/code&gt; flag to tell the CLI to read input from &lt;code&gt;stdin&lt;/code&gt; instead of a specified file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;algolia settings get pills_treatments | algolia settings import pills_cures -F -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✓ Imported settings on pills_cures
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Easy! One more scenario… we’ve tested this out on all our indices in the test app, now we’re ready to migrate these settings to our production app! We’ll target the &lt;code&gt;pills_treatments_prod&lt;/code&gt; index in the &lt;code&gt;prod_pharm_app&lt;/code&gt; profile.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;algolia settings get pills_treatments | algolia settings import pills_treatments_prod -F - -p prod_pharm_app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✓ Imported settings on pills_treatments_prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’re beginning to believe, aren’t you?&lt;/p&gt;

&lt;p&gt;—&lt;/p&gt;

&lt;h3&gt;
  
  
  Example #3: CI/CD pipelines that execute Algolia tasks
&lt;/h3&gt;

&lt;p&gt;This is where things start to get really exciting. Consider the following workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A database contains your data.&lt;/li&gt;
&lt;li&gt;An automated script runs nightly to generate an index from the data in the database.&lt;/li&gt;
&lt;li&gt;An automated script uploads that index to Algolia.
Neat! Cool! Convenient! Let’s change that. We’re gonna mess things up a bit. How about we push a bug to prod?&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Engineer Anderson is tasked with performing a database migration. He creates a change!&lt;/li&gt;
&lt;li&gt;The change is reviewed by Engineer Becky, who says LGTM!&lt;/li&gt;
&lt;li&gt;The change is submitted!&lt;/li&gt;
&lt;li&gt;The CI/CD pipeline applies the change to the test database. No errors! Database migration tests pass!&lt;/li&gt;
&lt;li&gt;The change is applied to the prod database. No errors! Database migration tests pass!&lt;/li&gt;
&lt;li&gt;Engineer Anderson signs off with a smile on his face, goes home, and helps his landlady take out the garbage.
…&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That night, while Engineer Anderson is sleeping peacefully and dreaming about dangling below a helicopter in flight, disaster strikes!&lt;/p&gt;

&lt;p&gt;…&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The automated index script runs at night, generating an index from the data in the database.&lt;/li&gt;
&lt;li&gt;However, the database migration transformed fields consumed by this script.&lt;/li&gt;
&lt;li&gt;The output index is missing an important search attribute, but uploads successfully.&lt;/li&gt;
&lt;li&gt;Searching the malformed index produces inaccurate and irrelevant results. Oh no!&lt;/li&gt;
&lt;li&gt;It isn’t until 3 days later, as search metrics drop, that the problem is even detected. It takes Engineer Anderson another day to roll back the changes and fix the index.
What can be done about this? How do you prevent this mess from ever happening again?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What if DB changes ran a few simple query test cases during the CI/CD pipeline? Each change would immediately generate an index and fire a series of test queries against it. Engineer Anderson’s change would have failed the CI/CD pipeline for producing different query results than expected. All it would take is the creation of a handful of test cases and a simple script to run the Algolia CLI’s &lt;code&gt;algolia search&lt;/code&gt; command against those test cases to detect this issue in advance!&lt;/p&gt;

&lt;p&gt;Let’s configure one such test case! When the index is working properly, we can execute a test query and save the results. The saved data is our “expected results”, against which we’ll compare the results of subsequent query tests. We’ll search an index from the previous example for “blue pills”:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;algolia search pills_treatments_prod -p prod_pharm_app --query "blue pills" &amp;gt; blue_pills_expected_results.ndjson
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cool! We have a test case for the query “blue pills”! Let’s run this on the &lt;code&gt;pills_treatments&lt;/code&gt; index in the test app, &lt;code&gt;test_pharm_app&lt;/code&gt;, and see if it matches.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;algolia search pills_treatments -p test_pharm_app --query "blue pills" &amp;gt; blue_pills_actual_results.ndjson &amp;amp;&amp;amp; diff blue_pills_expected_results.ndjson blue_pills_actual_results.ndjson | wc -m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We query the test index and save the results. Then, we compare the expected results with the actual results by invoking the &lt;code&gt;diff&lt;/code&gt; command, which outputs only the difference between the two files. Finally, we pipe the output of &lt;code&gt;diff&lt;/code&gt; to the &lt;code&gt;wc -m&lt;/code&gt; command, which counts the number of characters in the output from &lt;code&gt;diff&lt;/code&gt;. We observe an output of 0, meaning there is no difference between the two files, which indicates that our query produced the same results in both indices and that our query test passed! &lt;/p&gt;

&lt;p&gt;Along those lines, a non-zero output would mean that there is some difference in the two files, and our query test failed to produce identical output across both indices.&lt;/p&gt;

&lt;p&gt;This is just one example of how you could integrate the Algolia CLI within a CI/CD pipeline. Imagine using the index settings management workflow to promote dashboard-configured settings set by your marketing team to a production app. The promotion can then invoke a run of query testing to validate the changes, and voila! The CLI becomes a force multiplier for your business users as well!&lt;/p&gt;

&lt;p&gt;Use the Algolia CLI to automate tasks, improve your workflows, and keep your search experiences running seamlessly, even as you change and develop the content under the hood!&lt;/p&gt;

&lt;p&gt;—&lt;/p&gt;

&lt;h3&gt;
  
  
  Get Involved!
&lt;/h3&gt;

&lt;p&gt;We’ve seen that the Algolia CLI can handle almost anything, from simple operations like index upload to more complex workflows like snapshot restoration. Stay tuned as we push updates out and further expand the CLI’s capabilities to make your life as an Algolia developer even easier!&lt;/p&gt;

&lt;p&gt;Remember, the Algolia CLI is in Public Beta! On MacOS, install with Homebrew:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brew install algolia/algolia-cli/algolia
Windows and Linux installers coming soon! 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, &lt;a href="https://github.com/algolia/cli"&gt;clone the GitHub repo&lt;/a&gt; and build it yourself!&lt;/p&gt;

&lt;p&gt;Want to learn more? Check out the &lt;a href="https://www.algolia.com/doc/tools/cli/get-started/overview/"&gt;Official CLI Documentation&lt;/a&gt; for a guided tutorial!&lt;/p&gt;

&lt;p&gt;And don’t forget to mark your calendars for September 14-15! The &lt;a href="https://www.algolia.com/devcon/"&gt;Algolia DevCon&lt;/a&gt; fast approaches. We’ll be spotlighting the CLI tool and demonstrating its capabilities! The Developer Experience team will be there to host live Q&amp;amp;A and answer all your questions. You won’t want to miss it!&lt;/p&gt;

&lt;p&gt;—&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deprecation Advisory&lt;/strong&gt; - Before this CLI was created, we had an &lt;a href="https://www.npmjs.com/package/@algolia/cli"&gt;npm Algolia CLI package&lt;/a&gt; that served similar functionality. The Legacy CLI is &lt;strong&gt;DEPRECATED&lt;/strong&gt; - We will not be maintaining/updating/supporting it going forward. However, we will not remove the package from npm (since customers are still using it). Instead, we’ve announced the deprecation in the &lt;a href="https://github.com/algolia/algolia-cli-old"&gt;Legacy CLI GitHub repo&lt;/a&gt; (and renamed the repo to “algolia-cli-&lt;strong&gt;old&lt;/strong&gt;”), and are redirecting customers to the &lt;a href="https://github.com/algolia/cli"&gt;New CLI GitHub repo&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>algolia</category>
      <category>cli</category>
      <category>shell</category>
      <category>devops</category>
    </item>
    <item>
      <title>Customize Algolia React components with Tailwind classes</title>
      <dc:creator>Chuck Meyer</dc:creator>
      <pubDate>Tue, 19 Jul 2022 19:46:45 +0000</pubDate>
      <link>https://dev.to/algolia/customize-algolia-react-components-with-tailwind-classes-2di6</link>
      <guid>https://dev.to/algolia/customize-algolia-react-components-with-tailwind-classes-2di6</guid>
      <description>&lt;p&gt;While I was at Algolia's Paris office last week, &lt;a href="https://twitter.com/_dhaya_" rel="noopener noreferrer"&gt;Dhaya Benmessaoud&lt;/a&gt; from our Front-end Experience team showed me a nifty trick for styling Algolia's React widgets in your UI. Out-of-the-box, Algolia provides a couple of pre-built themes for search experiences (Algolia and Satellite) and the ability to create custom themes. Recently, the front-end experience team has added a third way to style your UI by &lt;a href="https://www.algolia.com/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react-hooks/#style-your-widgets" rel="noopener noreferrer"&gt;injecting custom CSS classes&lt;/a&gt; into your Algolia React components. &lt;/p&gt;

&lt;p&gt;This is excellent news for people who use class-based CSS frameworks like &lt;a href="https://getbootstrap.com/" rel="noopener noreferrer"&gt;Bootstrap&lt;/a&gt; and &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt;! In my case, I was working with the Algolia &lt;a href="https://www.algolia.com/developers/code-exchange/frontend-tools/ecommerce-pwa-ui-template/" rel="noopener noreferrer"&gt;Ecommerce UI Template&lt;/a&gt;, which relies on Tailwind for styling. I wanted to add a &lt;code&gt;&amp;lt;TrendingFacets&amp;gt;&lt;/code&gt; widget from the Algolia Recommend UI library to my homepage, but I wanted to style it using Tailwind classes to match the rest of my front-end.&lt;/p&gt;

&lt;p&gt;Here's how it looks before styling:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqw0qqjjzug7ykvgc8cwe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqw0qqjjzug7ykvgc8cwe.png" alt="Screenshot of Trending Facets without styling" width="483" height="173"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I can definitely do better! To accomplish this, I need to use the &lt;code&gt;classNames&lt;/code&gt; attribute for my component. It's available for all of Algolia's React widgets (including Recommend) and allows you to override styling on component-specific elements. Some of our other front-end APIs like Vanilla JavaScript (&lt;code&gt;cssClasses&lt;/code&gt;) and Vue (&lt;code&gt;class-names&lt;/code&gt;) have had this functionality for years, and now it's available in React thanks to the recent refresh that added React hooks. &lt;/p&gt;

&lt;p&gt;The documentation lists the elements I can override for each Algolia widget. For instance, here are the elements for a &lt;code&gt;&amp;lt;SearchBox&amp;gt;&lt;/code&gt; widget:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;root: The root element of the widget.&lt;/li&gt;
&lt;li&gt;form: The form element.&lt;/li&gt;
&lt;li&gt;input: The input element.&lt;/li&gt;
&lt;li&gt;submit: The submit button.&lt;/li&gt;
&lt;li&gt;reset: The reset button.&lt;/li&gt;
&lt;li&gt;loadingIndicator: The loading indicator element.&lt;/li&gt;
&lt;li&gt;submitIcon: The submit icon.&lt;/li&gt;
&lt;li&gt;resetIcon: The reset icon.&lt;/li&gt;
&lt;li&gt;loadingIcon: The loading icon.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For my &lt;code&gt;&amp;lt;TrendingFacets&amp;gt;&lt;/code&gt; widget, I want the list in a horizontal line to conserve space, so I add a &lt;code&gt;flex&lt;/code&gt; class to its &lt;code&gt;list&lt;/code&gt; element. I'll also add a new &lt;code&gt;facetItem&lt;/code&gt; class to give each &lt;code&gt;item&lt;/code&gt; a clean capsule shape with some simple hover styling. Here's how my component looks after styling.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwau13ml60m7aww6gtoqx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwau13ml60m7aww6gtoqx.png" alt="Screenshot of Trending Facets with styling" width="605" height="141"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here's the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TrendingFacets&lt;/span&gt;
  &lt;span class="nx"&gt;classNames&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;facetItem&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
  &lt;span class="nx"&gt;recommendClient&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;recommendClient&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;indexName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;maxRecommendations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;itemComponent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{({&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;facetValue&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;facetValue&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/a&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;)}&lt;/span&gt;
  &lt;span class="nx"&gt;facetName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;facetName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding &lt;code&gt;classNames&lt;/code&gt; to customize the style of Algolia widgets makes so much sense, especially for people like me who are addicted to Tailwind CSS for styling front-ends. You can read more about adding custom CSS classes to widgets in the &lt;a href="https://www.algolia.com/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react-hooks/#style-your-widget" rel="noopener noreferrer"&gt;Algolia documentation&lt;/a&gt;. If you're new to Algolia, you can try it out by signing up for a &lt;a href="https://www.algolia.com/users/sign_up?utm_source=blog&amp;amp;utm_medium=devto&amp;amp;utm_campaign=devrel&amp;amp;utm_id=customize-with-tailwind" rel="noopener noreferrer"&gt;free tier account&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I'd love to see other examples of well-styled search experiences in the comments!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>tailwindcss</category>
    </item>
    <item>
      <title>Feature Spotlight: Trends Models in Recommend</title>
      <dc:creator>Chuck Meyer</dc:creator>
      <pubDate>Thu, 07 Jul 2022 03:37:03 +0000</pubDate>
      <link>https://dev.to/algolia/feature-spotlight-trends-models-in-recommend-47a2</link>
      <guid>https://dev.to/algolia/feature-spotlight-trends-models-in-recommend-47a2</guid>
      <description>&lt;p&gt;Recommendations thrive on data – the more you know, the better the recommendations you can make. &lt;a href="https://www.algolia.com/products/recommendations/" rel="noopener noreferrer"&gt;Algolia’s Recommend&lt;/a&gt; product puts your data to work using machine-learning to suggest products or content that will interest your customers. Today, we’re putting a spotlight on Recommend’s new Trends models. You’ll learn about how they work and how they expand the ways that you can use recommendations. &lt;/p&gt;

&lt;p&gt;At launch, Recommend offered two initial machine-learning models: &lt;em&gt;Related Products&lt;/em&gt; and &lt;em&gt;Frequently Bought Together&lt;/em&gt;. Both are collaborative-filtering models, meaning you train them on the products your users click on and purchase as they interact with your website (events data). These models are great for product-centric recommendations. Given a specific product from your catalog, you can use the models to suggest other products that your customers either found similar or also purchased. You can place these recommendations on product detail pages and checkout flows to help your customers discover other products they might be interested in. Separate to what we'll be discussing in this article, these initial models have just been updated and you can now get started using them right away &lt;em&gt;without&lt;/em&gt; events data - &lt;a href="https://www.algolia.com/blog/product/ai-powered-recommendations-product-launch/" rel="noopener noreferrer"&gt;learn more here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Trends&lt;/em&gt; models are similarly powered by collaborative-filtering, but are different in that they aren’t restricted to recommendations based on a single product. Instead, they look for product trends across the entire catalog, or within a particular facet/category of your catalog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Diving into Trends
&lt;/h2&gt;

&lt;p&gt;To use the Recommend models, you’ll need to have your products stored in an Algolia index. Then, you can track trends for either items or facet values across your index. Remember that a facet is an attribute, like “color”, that you have configured in your index for filtering. The possible values for this attribute across your index are called facet values – like “red” or “purple”. &lt;/p&gt;

&lt;p&gt;Depending on the type of trend, you will &lt;a href="https://www.algolia.com/doc/guides/algolia-recommend/overview/#trending-items-and-trending-facet-values" rel="noopener noreferrer"&gt;train one of two new models&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Trending items - The most popular items either for a specific facet value or across the entire catalog.&lt;/li&gt;
&lt;li&gt;  Trending facet values - The most popular values for a specific facet (e.g. “sweaters” as a value for “category”).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both models are trained by collaborative-filtering, meaning they learn trends from events data – specifically conversion events captured from your users. A conversion event could be purchasing an item, adding it to a shopping cart, or marking it as a favorite. &lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;trending items&lt;/strong&gt; model looks at both the total number of conversion events for each item over the last few days, and the &lt;em&gt;change&lt;/em&gt; in the number of conversion events over time to learn “trendiness” for each record in the index. It then assigns a score to each item in the catalog, either globally or within a particular facet value. &lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;trending facet values&lt;/strong&gt;, this model does the same thing for a facet attribute – scoring the total number of conversions and change in conversions over a number of days for each facet value. You can collect trend information on up to three facets per model. &lt;/p&gt;

&lt;p&gt;In order to use trends, you’ll need to train the model with at least 500 conversion events spread out over at least three days. Obviously, the more information you send, the better the recommendations the model can provide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to use Trends
&lt;/h2&gt;

&lt;p&gt;Trending items complement the other product-centric models by allowing you to recommend popular items or top sellers within a particular category on your home page before your customers have selected a single product. &lt;/p&gt;

&lt;p&gt;Trending facets are great for adding popular categories or product attributes to your homepage that are relevant right now – whether it’s garden supplies in the spring or coats and hats in the fall. &lt;/p&gt;

&lt;p&gt;Both models, once trained, can provide up to 30 recommendations, either globally or within a specified facet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Training your Trends model
&lt;/h2&gt;

&lt;p&gt;Before you can use any of the Recommend models, you’ll need to &lt;a href="https://www.algolia.com/users/sign_up?utm_source=blog&amp;amp;utm_medium=main-blog&amp;amp;utm_campaign=devrel&amp;amp;utm_id=spotlight-trends" rel="noopener noreferrer"&gt;sign-up for an Algolia account&lt;/a&gt; and load your product catalog into an index. You can use an existing Search index or set one up just for Recommend. &lt;/p&gt;

&lt;p&gt;Once your index is populated with product records, you’ll need to &lt;a href="https://www.algolia.com/doc/guides/sending-events/implementing/" rel="noopener noreferrer"&gt;start sending user event information to Algolia&lt;/a&gt;. You can use the event integrations built into Algolia’s InstantSearch front-end libraries, the Algolia Insights API, or supported integrations with major ecommerce platforms and analytics back ends to collect this data. You can even upload your events as a CSV file with the appropriate record format. &lt;/p&gt;

&lt;p&gt;Remember, because the Trends models look at changes over time, you’ll need to send conversion events that span multiple days. &lt;/p&gt;

&lt;p&gt;The minimum event requirements are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  500 conversions in the last 30 days&lt;/li&gt;
&lt;li&gt;  Events across a minimum of 3 days (so not 500 conversions in 1 day)&lt;/li&gt;
&lt;li&gt;  Events for a minimum of 10 items in your index&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can track your events totals across your application in the &lt;em&gt;Data Sources&lt;/em&gt; section of the &lt;a href="https://www.algolia.com/dashboard" rel="noopener noreferrer"&gt;Dashboard&lt;/a&gt;. This section also gives you access to debugging tools for troubleshooting your events. &lt;/p&gt;

&lt;p&gt;To start training your model, choose the &lt;em&gt;Trends&lt;/em&gt; model from the Recommend Dashboard. Select your product index and Recommend will let you know if you have enough of the required conversion events to start training. If you see an error about retrieving events, you may need to disable your ad-blocking software. &lt;/p&gt;

&lt;p&gt;Next, you can select any facets that you want to take into account when training your model. For instance, you could train the model for &lt;em&gt;Trending facet values&lt;/em&gt; on the facet &lt;code&gt;category_page_id&lt;/code&gt;. You are now ready to click &lt;em&gt;Train Model&lt;/em&gt; to start the training process. Training typically takes a few hours to complete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add trends to your frontend using the Recommend UI
&lt;/h2&gt;

&lt;p&gt;Once your model is trained, you are ready to start adding Trend recommendations to your application. The easiest way to do this is with the &lt;a href="https://www.algolia.com/doc/ui-libraries/recommend/introduction/what-is-recommend/" rel="noopener noreferrer"&gt;Recommend front end UI libraries&lt;/a&gt; for vanilla JavaScript or React. &lt;/p&gt;

&lt;p&gt;Let's say that you want to add trending categories to the Algolia &lt;a href="https://www.algolia.com/doc/guides/building-search-ui/ecommerce-ui-template/overview/react/" rel="noopener noreferrer"&gt;Ecommerce UI Template&lt;/a&gt; using your newly trained &lt;em&gt;Trending facet values&lt;/em&gt; model. First, you’ll need to add the &lt;code&gt;recommend&lt;/code&gt; and &lt;code&gt;recommend-react&lt;/code&gt; libraries to your project using npm (or yarn):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save&lt;/span&gt; @algolia/recommend-react @algolia/recommend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the libraries are installed, you’ll need to write a new &lt;code&gt;FacetList&lt;/code&gt; component to retrieve and display facet recommendations from your trained model. Your component needs to initialize a Recommend client using the App ID and API key from the app that contains your trained model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;const recommendClient &lt;span class="o"&gt;=&lt;/span&gt; recommend&lt;span class="o"&gt;(&lt;/span&gt;ALGOLIA&lt;span class="se"&gt;\_&lt;/span&gt;APP&lt;span class="se"&gt;\_&lt;/span&gt;ID, ALGOLIA&lt;span class="se"&gt;\_&lt;/span&gt;SEARCH&lt;span class="se"&gt;\_&lt;/span&gt;ONLY&lt;span class="se"&gt;\_&lt;/span&gt;API&lt;span class="se"&gt;\_&lt;/span&gt;KEY&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, add the &lt;code&gt;TrendingFacets&lt;/code&gt; widget from the Recommend React library. Since you’re working in the Ecommerce UI, you’ll use the abstracted &lt;code&gt;Container&lt;/code&gt; component as a wrapper and handle titles the same way as other components in this library. That means you’ll want to suppress the default header by including &lt;code&gt;headerComponent={() =&amp;gt; null}&lt;/code&gt;. You can avoid overwhelming your visitors by reducing the list of 30 recommendations to just the top 3 using &lt;code&gt;maxRecommendations={3}&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Here’s the complete &lt;code&gt;FacetList&lt;/code&gt; component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;algoliasearch&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;algoliasearch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;recommend&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@algolia/recommend&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;TrendingFacets&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@algolia/recommend-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;classNames&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;classnames&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Container&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/container/container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;recommendClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;recommend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ALGOLIA&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;_APP&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ALGOLIA&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;_SEARCH&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;_ONLY&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;_API&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;FacetListProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;facetName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;FacetList&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;facetName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;FacetListProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;section&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;classNames&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;py-4 laptop:py-16&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Container&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h2&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text-sm font-semibold tracking-&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;[2px&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;] uppercase mb-3 laptop:mb-6 laptop:ml-3 laptop:heading-3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h2&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="p"&gt;)}&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TrendingFacets&lt;/span&gt;
          &lt;span class="nx"&gt;recommendClient&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;recommendClient&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="nx"&gt;indexName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="nx"&gt;maxRecommendations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="nx"&gt;itemComponent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{({&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;facetValue&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;facetValue&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/a&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;}
&lt;/span&gt;          &lt;span class="nx"&gt;headerComponent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="nx"&gt;facetName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;facetName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Container&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/section&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, add the finished component after the &lt;code&gt;Banner&lt;/code&gt; widget on your &lt;code&gt;index.tsx&lt;/code&gt; page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;FacetList&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Trending Categories"&lt;/span&gt;
        &lt;span class="na"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"prod\_ECOM"&lt;/span&gt;
        &lt;span class="na"&gt;facetName&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"category\_page\_id"&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After reloading, you should now see the top trending categories, just below the banner on the home page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ot8muap0eg41qovxdb2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ot8muap0eg41qovxdb2.png" alt="Screenshot of homepage with recommendations" width="720" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Trends models are a great complement to the existing, (and newly updated), Product models in Recommend. You can combine both types of models to help your customers discover new items during their entire journey on your site – facet value trends on the homepage lead them to trending items on the category page, related products on a product detail page, and finally to items frequently bought together during checkout. At each stage, customers are guided to items that they may have missed through a traditional search. &lt;/p&gt;

&lt;p&gt;You can start using Trending items and Trending facet values right now from the &lt;a href="https://www.algolia.com/users/sign_up?utm_source=blog&amp;amp;utm_medium=main-blog&amp;amp;utm_campaign=devrel&amp;amp;utm_id=spotlight-trends" rel="noopener noreferrer"&gt;Algolia Recommend dashboard&lt;/a&gt;. Or, you can read more about the &lt;a href="https://www.algolia.com/blog/engineering/recommendations-for-developers-the-complete-how-to-what-to-and-where-to-guide/" rel="noopener noreferrer"&gt;other features that Recommend has to offer&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Feature Spotlight: Optional Filters</title>
      <dc:creator>Chuck Meyer</dc:creator>
      <pubDate>Mon, 18 Apr 2022 16:40:29 +0000</pubDate>
      <link>https://dev.to/algolia/feature-spotlight-optional-filters-42lp</link>
      <guid>https://dev.to/algolia/feature-spotlight-optional-filters-42lp</guid>
      <description>&lt;p&gt;While answering Algolia forum posts last week, I did a deep dive on &lt;a href="https://www.algolia.com/doc/guides/managing-results/rules/merchandising-and-promoting/in-depth/optional-filters/" rel="noopener noreferrer"&gt;Optional Filters&lt;/a&gt; for Algolia Search. Similar to filters and facet filters, optional filters are applied at query-time, allowing you to use all sorts of contextual information to improve results. Unlike the other filters, optional filters don't remove records from the result set. Instead, they allow you to say, "If records match this filter, move them up or down the ranking," without changing the number of records returned. &lt;/p&gt;

&lt;p&gt;Some interesting use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Move posts that mention a particular user to the top of the result set &lt;/li&gt;
&lt;li&gt;Up-rank products available at a customer's local store&lt;/li&gt;
&lt;li&gt;Down-rank products that are out-of-stock&lt;/li&gt;
&lt;li&gt;Pin a specific record to the top of the listing based on an external recommendations engine&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can use filters and facet filters to reduce the number of records in the result set at query-time, then optional filters to manipulate the rankings for the remaining records. You can even apply &lt;a href="https://www.algolia.com/doc/guides/managing-results/refine-results/filtering/in-depth/filter-scoring/" rel="noopener noreferrer"&gt;filter scoring&lt;/a&gt; to control the order of the records further.&lt;/p&gt;

&lt;p&gt;Here's an example that applies facet filters for &lt;code&gt;product_type&lt;/code&gt; and &lt;code&gt;price_range&lt;/code&gt; to an index from a Shopify store. The code selects the &lt;code&gt;objectID&lt;/code&gt; of two records to promote, then injects them as optional filters into the query. If either of those products is part of the result set that matches the other criteria in the query, those products are pushed to the top of the ranking. The code uses filter scoring to ensure the &lt;code&gt;featuredProduct&lt;/code&gt; will always appear above the &lt;code&gt;alternateProduct&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;algoliasearch&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;algoliasearch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;featuredProduct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;41469303161004&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;alternateProduct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;41469346644140&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;algoliasearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;H2M6B61JEG&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;b1bdfc3258823bb4468815a664dce649&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Standard replica&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shopify_algolia_products_price_asc_standard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// with params&lt;/span&gt;
&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;facetFilters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;product_type:HardGood&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;price_range:75:100&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;]],&lt;/span&gt;
    &lt;span class="na"&gt;optionalFilters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;
        &lt;span class="s2"&gt;`objectID:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;featuredProduct&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;score=500&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;`objectID:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;alternateProduct&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;score=200&amp;gt;`&lt;/span&gt;
    &lt;span class="p"&gt;]],&lt;/span&gt;
    &lt;span class="na"&gt;hitsPerPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;hits&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`- &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;product_type&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;objectID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price_range&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that this code uses a standard replica of the Shopify index sorted by price. Optional filters don't play well with &lt;a href="https://www.algolia.com/doc/guides/managing-results/refine-results/sorting/in-depth/replicas/" rel="noopener noreferrer"&gt;Virtual Replica&lt;/a&gt; indices since both re-rank records at query-time, leading to unpredictable results. If you plan to use optional filters, you should use a standard replica that applies ranking at index-time.&lt;/p&gt;

&lt;p&gt;You can also use negative optional filters to push records down the ranking. For example, if you wanted to push posts written by the current user lower in the rankings but not remove them completely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`date_timestamp &amp;gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;optionalFilters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;`author:-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;hitsPerPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;hits&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Optional filters are a powerful tool to add to your ranking tool belt, but remember that any query-time calculations will impact search performance. For instance, you shouldn’t use filter scoring on searches that may return more than 100,000 results. Always try to move ranking criteria to index configuration when possible. Use optional filters only when you need to further tune your results at query-time using more real-time context.&lt;/p&gt;

&lt;p&gt;Let me know if you find a great (or not so great) use case for optional filters in your search UI!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>search</category>
    </item>
    <item>
      <title>Add autocomplete search to your Strapi blog</title>
      <dc:creator>Chuck Meyer</dc:creator>
      <pubDate>Tue, 05 Apr 2022 17:26:20 +0000</pubDate>
      <link>https://dev.to/algolia/add-autocomplete-search-to-your-strapi-blog-263h</link>
      <guid>https://dev.to/algolia/add-autocomplete-search-to-your-strapi-blog-263h</guid>
      <description>&lt;p&gt;&lt;a href="https://strapi.io" rel="noopener noreferrer"&gt;Strapi&lt;/a&gt; is an open-source, headless CMS that builds API abstractions to retrieve your content regardless of where it's stored. It's a great tool for building content-driven applications like blogs and other media sites.&lt;/p&gt;

&lt;p&gt;In this post, you're going to add interactive search to a Strapi blog with a Next.js front end using &lt;a href="https://www.algolia.com/users/sign_up?utm_source=blog&amp;amp;utm_medium=main-blog&amp;amp;utm_campaign=devrel&amp;amp;utm_id=agency-finder" rel="noopener noreferrer"&gt;Algolia&lt;/a&gt;'s Autocomplete.js library and the community-built Search plugin for Strapi.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Strapi and Algolia Autcomplete.js are both build using Javascript, so you'll want node v.12 installed.&lt;/p&gt;

&lt;p&gt;You'll build on the basic blog application from &lt;a href="https://strapi.io/blog/build-a--react-js-strapi" rel="noopener noreferrer"&gt;this guide&lt;/a&gt; using Strapi and Next.js. You should familiarize yourself with the steps in that post before building your search experience on top of it.&lt;/p&gt;

&lt;p&gt;You'll also need an Algolia account for search. You can use your existing Algolia account or &lt;a href="https://www.algolia.com/users/sign_up?utm_source=blog&amp;amp;utm_medium=main-blog&amp;amp;utm_campaign=devrel&amp;amp;utm_id=agency-finder" rel="noopener noreferrer"&gt;sign up for a free account&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the back end
&lt;/h2&gt;

&lt;p&gt;Start by creating a directory to hold your project's front and back ends:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;blog-strapi-algolia &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;blog-strapi-algolia
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Strapi has a set of pre-baked templates you can use to get a CMS up and running quickly. These all include Strapi itself with pre-defined content types and sample data. If you don't have a Strapi blog already, you can use the blog template to quickly set one up.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-strapi-app backend  &lt;span class="nt"&gt;--quickstart&lt;/span&gt; &lt;span class="nt"&gt;--template&lt;/span&gt; @strapi/template-blog@1.0.0 blog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the script finishes installing, add an admin user at &lt;a href="http://localhost:1337" rel="noopener noreferrer"&gt;http://localhost:1337&lt;/a&gt; so you can log in to the Strapi dashboard. This script sets up most of back end for us, including a few demo blog posts. You can read more about everything that was setup in the &lt;a href="https://strapi.io/blog/build-a-blog-with-next-react-js-strapi" rel="noopener noreferrer"&gt;quick start&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Next you need to index your demo content. Fortunately, the Strapi community has you covered with a Search plugin and Algolia indexing provider built by community member Mattias van den Belt. You can read more about Mattie's plugin in &lt;a href="https://mattie-bundle.mattiebelt.com/quick-start" rel="noopener noreferrer"&gt;the documentation&lt;/a&gt;, but getting up and running only requires a couple of pieces of configuration.&lt;/p&gt;

&lt;p&gt;Go ahead and stop your Strapi server so you can install the plugin using &lt;code&gt;npm&lt;/code&gt; (or &lt;code&gt;yarn&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;backend &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install&lt;/span&gt; @mattie-bundle/strapi-plugin-search @mattie-bundle/strapi-provider-search-algoliai &lt;span class="nt"&gt;--save&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll need to add an Algolia API key and App ID to your Strapi environment. You can manage your keys by navigating the Algolia Dashboard under Settings &amp;gt; Team and Access &amp;gt; API Keys, or go directly to  &lt;a href="https://algolia.com/account/api-keys" rel="noopener noreferrer"&gt;https://algolia.com/account/api-keys&lt;/a&gt;. Since Strapi is modifying your Algolia index, you'll need to provide either the admin API key (for demos) or create a key with appropriate access for your production project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .env&lt;/span&gt;
// ...
&lt;span class="nv"&gt;ALGOLIA_PROVIDER_APPLICATION_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;XXXXXXXXXX
&lt;span class="nv"&gt;ALGOLIA_PROVIDER_ADMIN_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With your credentials in place, the last step is to configure the plugin. Create or modify the &lt;code&gt;./config/plugins.js&lt;/code&gt; file in your &lt;code&gt;backend&lt;/code&gt; directory. You want to tell the plugin to index the &lt;code&gt;article&lt;/code&gt; and &lt;code&gt;category&lt;/code&gt; content types for your blog.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use strict&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="na"&gt;search&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;algolia&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;providerOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ALGOLIA_PROVIDER_ADMIN_API_KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;applicationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ALGOLIA_PROVIDER_APPLICATION_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;contentTypes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;api::article.article&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;api::category.category&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart your Strapi server to pick up these environment variables and the new plugin (&lt;code&gt;npm run develop&lt;/code&gt;). The Search plugin triggers when new content is &lt;code&gt;Published&lt;/code&gt;, so you'll need to either &lt;code&gt;Unpublish&lt;/code&gt; and &lt;code&gt;Publish&lt;/code&gt; the demo articles, or create a new one. Load the Strapi admin panel (&lt;a href="http://localhost:1337/admin" rel="noopener noreferrer"&gt;http://localhost:1337/admin&lt;/a&gt;) and navigate to Content Manager &amp;gt; COLLECTION TYPES &amp;gt; Article and either click on an existing article to &lt;code&gt;Unpublish&lt;/code&gt; or click on &lt;code&gt;Create new Entry&lt;/code&gt;. Click &lt;code&gt;Publish&lt;/code&gt; on your article to index this entry into your Algolia application. You can do the same for the &lt;code&gt;category&lt;/code&gt; content type if you'd like to index those as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the front end
&lt;/h2&gt;

&lt;p&gt;Now that you've built your back end and populated your index, it's time to build the front end for you users.&lt;/p&gt;

&lt;p&gt;Strapi has a great blog post walking you through building a Next.js powered front end. You'll build on top of those steps here. You can either walk through their &lt;a href="https://strapi.io/blog/build-a-blog-with-next-react-js-strapi" rel="noopener noreferrer"&gt;quick start&lt;/a&gt; yourself, or you can just clone &lt;a href="https://github.com/chuckmeyer/blog-strapi-frontend/tree/no-search-version" rel="noopener noreferrer"&gt;this repo&lt;/a&gt; if you want to jump directly to adding search.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone &lt;span class="nt"&gt;--single-branch&lt;/span&gt; &lt;span class="nt"&gt;--branch&lt;/span&gt; no-search-version git@github.com:chuckmeyer/blog-strapi-frontend.git frontend 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't forget to run &lt;code&gt;cd frontend &amp;amp;&amp;amp; npm install&lt;/code&gt; if you cloned the front end from the repo.&lt;/p&gt;

&lt;p&gt;This is enough to get the basic blog site up and running. You can test it out by running &lt;code&gt;npm run dev&lt;/code&gt; in the &lt;code&gt;frontend&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;The only thing missing is search. You'll be using the Algolia Autocomplete.js library to add an autocomplete search experience to your blog's navigation bar. When a user types into the field, the autocomplete "completes" their thought by providing full terms or results. The Autocomplete library is source agnostic, so you'll also need the Algolia InstantSearch library to connect to your index on the back end.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @algolia/autocomplete-js algoliasearch @algolia/autocomplete-theme-classic &lt;span class="nt"&gt;--save&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To use the autocomplete library in a React project, you first need to create an Autocomplete component to wrap the library. You can find the boilerplate for this in the &lt;a href="https://www.algolia.com/doc/ui-libraries/autocomplete/integrations/using-react/" rel="noopener noreferrer"&gt;autocomplete documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;./frontend/components/autocomplete.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;autocomplete&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@algolia/autocomplete-js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Fragment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-dom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Autocomplete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;containerRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;containerRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;search&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;autocomplete&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;containerRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Fragment&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;    
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;containerRef&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just like in the back end, you'll need your Algolia credentials to connect to the API. Since the front end only needs to &lt;em&gt;read&lt;/em&gt; from the index, you can use your search key for the application. Create a &lt;code&gt;./frontend/.env.local&lt;/code&gt; file to store your credentials.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;NEXT_PUBLIC_ALGOLIA_APP_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;XXXXXXXXXX
&lt;span class="nv"&gt;NEXT_PUBLIC_ALGOLIA_SEARCH_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can initialize your connection to Algolia and add your new Autocomplete component by updating the code in &lt;code&gt;./frontend/components/nav.js&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getAlgoliaResults&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@algolia/autocomplete-js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;algoliasearch&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;algoliasearch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Autocomplete&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./autocomplete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;SearchItem&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./searchItem&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@algolia/autocomplete-theme-classic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;searchClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;algoliasearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_ALGOLIA_APP_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_ALGOLIA_SEARCH_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Nav&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;categories&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;nav&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uk-navbar-container&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;uk&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;navbar&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uk-navbar-left&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ul&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uk-navbar-nav&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Strapi&lt;/span&gt; &lt;span class="nx"&gt;Blog&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/a&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;              &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Link&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/li&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/ul&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uk-navbar-center&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Autocomplete&lt;/span&gt;
            &lt;span class="nx"&gt;openOnFocus&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nx"&gt;detachedMediaQuery&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;
            &lt;span class="nx"&gt;placeholder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Search for articles&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="nx"&gt;getSources&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{({&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
              &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;sourceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;articles&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nf"&gt;getItemUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`/article/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="nf"&gt;getItems&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;getAlgoliaResults&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                    &lt;span class="nx"&gt;searchClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                      &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;development_api::article.article&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="p"&gt;]&lt;/span&gt;
                  &lt;span class="p"&gt;})&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="na"&gt;templates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SearchItem&lt;/span&gt; &lt;span class="nx"&gt;hit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;                  &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
              &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;]}&lt;/span&gt;
          &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uk-navbar-right&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ul&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uk-navbar-nav&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`/category/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uk-link-reset&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/a&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Link&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/li&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;              &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;})}&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/ul&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/nav&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Nav&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, you're passing a few parameters to the &lt;code&gt;Autocomplete&lt;/code&gt; component:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;openOnFocus={false}&lt;/code&gt; - tells your search not to populate results until a user starts typing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;detachedMediaQuery=''&lt;/code&gt;- opens search in a detached modal, providing more room for your results&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;placeholder="Search for articles"&lt;/code&gt; - the text that appears in the searchbox before a search&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getSources={({ query }) =&amp;gt;&lt;/code&gt; - where you define your data sources for your autocomplete experience&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Remember that Autocomplete is source agnostic. You define sources based on APIs, libraries, or static content within your application. Here, you're binding a source called &lt;code&gt;articles&lt;/code&gt; to your Algolia index using the &lt;code&gt;getAlgoliaResults&lt;/code&gt; function from the &lt;code&gt;autocomplete-js&lt;/code&gt; library.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;              &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nl"&gt;sourceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;articles&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nf"&gt;getItemUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`/article/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="nf"&gt;getItems&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;getAlgoliaResults&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                    &lt;span class="nx"&gt;searchClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                      &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;development_api::article.article&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="p"&gt;]&lt;/span&gt;
                  &lt;span class="p"&gt;})&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="nx"&gt;templates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SearchItem&lt;/span&gt; &lt;span class="nx"&gt;hit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;                  &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
              &lt;span class="p"&gt;},&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;development_api::article.article&lt;/code&gt; is the index generated by the Strapi Search plugin above for your &lt;code&gt;article&lt;/code&gt; content type. When you move to production, the plugin will create a separate &lt;code&gt;production_api::article.article&lt;/code&gt; index in the same application.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;getItemUrl()&lt;/code&gt; section sets up keyboard navigation, while &lt;code&gt;getItems()&lt;/code&gt; handles retrieving articles from your index using the query term(s) from the searchbox.&lt;/p&gt;

&lt;p&gt;Notice the code above references a &lt;code&gt;SearchItem&lt;/code&gt; component. This is the &lt;code&gt;template&lt;/code&gt; you'll use to tell Autocomplete how to render your search results. Add a new component called &lt;code&gt;./frontend/components/searchItem.js&lt;/code&gt; with the following code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SearchItem&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;hit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;components&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aa-ItemLink&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`/article/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;hit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aa-ItemContent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ItemCategory&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;hit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aa-ItemContentBody&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aa-ItemContentTitle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Highlight&lt;/span&gt; &lt;span class="nx"&gt;hit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;hit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;attribute&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aa-ItemContentDescription&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Highlight&lt;/span&gt; &lt;span class="nx"&gt;hit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;hit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;attribute&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/a&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;SearchItem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this code, you're displaying the &lt;code&gt;category&lt;/code&gt; associated with the article, the title, and the description. Use the &lt;code&gt;components.Highlight&lt;/code&gt; component to emphasize the part of the attribute that matched the user's query.&lt;/p&gt;

&lt;p&gt;And with that, you're done! Start your front end server with &lt;code&gt;npm run dev&lt;/code&gt;. You should now see the autocomplete searchbox at the top of the page. Clicking on it opens the modal search interface where you can start typing your search term.&lt;/p&gt;

&lt;p&gt;You can see a hosted version of this front end on &lt;a href="https://codesandbox.io/s/github/chuckmeyer/blog-strapi-frontend" rel="noopener noreferrer"&gt;codesandbox&lt;/a&gt;, although it may take some time for the back end container to start. The &lt;a href="https://github.com/chuckmeyer/blog-strapi-frontend/tree/no-search-version" rel="noopener noreferrer"&gt;before&lt;/a&gt; and &lt;a href="https://github.com/chuckmeyer/blog-strapi-frontend/tree/with-search-version" rel="noopener noreferrer"&gt;after&lt;/a&gt; versions of the front end code are both available on Github as well.&lt;/p&gt;

&lt;p&gt;If you build something cool using this blog, share it with us on &lt;a href="https://twitter.com/algolia" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; (@algollia).&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Adding OAuth2 authentication to an AWS S3 static bucket with Okta</title>
      <dc:creator>Guillaume Truchot</dc:creator>
      <pubDate>Wed, 23 Mar 2022 10:40:28 +0000</pubDate>
      <link>https://dev.to/algolia/adding-oauth2-authentication-to-an-aws-s3-static-bucket-with-okta-196j</link>
      <guid>https://dev.to/algolia/adding-oauth2-authentication-to-an-aws-s3-static-bucket-with-okta-196j</guid>
      <description>&lt;p&gt;Our team recently implemented an internal corporate static website that allows employees to download technical reports.&lt;/p&gt;

&lt;p&gt;Since we're heavy AWS users, we naturally decided to host it on AWS S3, which provides a dedicated feature to build static websites (&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/WebsiteHosting.html"&gt;S3 static website hosting&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;However, we quickly ran into an issue: AWS S3 does not provide any native, out-of-the-box authentication/authorization process. Because it was an internal-only website, we needed some kind of authorization mechanism to prevent non-authorized users from accessing our website and reports.&lt;/p&gt;

&lt;p&gt;We needed to find a solution to secure our internal static website on AWS S3.&lt;/p&gt;

&lt;h2&gt;
  
  
  Discovering the solution with CloudFront and Lambda@Edge
&lt;/h2&gt;

&lt;p&gt;We use &lt;a href="https://www.okta.com/"&gt;Okta&lt;/a&gt; for all Identity and User Management, so whatever solution we found had to plug-in with Okta.&lt;/p&gt;

&lt;p&gt;Okta has several authentication/authorization flows, all of which require the application to perform a back-end check, such as verifying that the response/token returned by Okta is legit.&lt;/p&gt;

&lt;p&gt;So we needed to find a way to carry these checks/actions on a static website which uses a back end that we don't control. That's when we learned about &lt;a href="https://aws.amazon.com/lambda/edge/"&gt;AWS Lambda@Edge&lt;/a&gt;, which lets you run &lt;a href="https://aws.amazon.com/lambda/"&gt;Lambda Functions&lt;/a&gt; at different stages of a request and response to and from CloudFront:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fW4KSSSX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hwhsx769mp3yzvpdo716.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fW4KSSSX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hwhsx769mp3yzvpdo716.png" alt="CloudFront events that trigger Lambda functions" width="545" height="194"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can trigger a Lambda Function at four different stages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When the request enters CloudFront (&lt;code&gt;viewer-request&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;When the request goes out to the origin (&lt;code&gt;origin-request&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;When the response is returned from the origin (&lt;code&gt;origin-response&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;When the response is returned from CloudFront (&lt;code&gt;viewer-response&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We saw a solution to our original issue: trigger a Lambda at the &lt;code&gt;viewer-request&lt;/code&gt; stage that would check if the user is authorized. Two conditions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the user is authorized, let the request continue and return the restricted content&lt;/li&gt;
&lt;li&gt;If the user is not authorized, send an HTTP response to redirect them to a login page&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vPl0tJz8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8s9wr3qspcgkk82tybqy.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vPl0tJz8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8s9wr3qspcgkk82tybqy.jpg" alt="Authentication check flow with Lambda@Edge" width="880" height="311"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing the Lambda@Edge function
&lt;/h2&gt;

&lt;p&gt;We'll cover here the key elements and main issues we faced. The complete code is available &lt;a href="https://github.com/GuiTeK/aws-s3-oauth2-okta"&gt;here&lt;/a&gt;. Feel free to use it in your project!&lt;/p&gt;

&lt;h3&gt;
  
  
  Lambda@Edge restrictions and caveats
&lt;/h3&gt;

&lt;p&gt;During the development of the solution, we ran into several restrictions and caveats of Lambda@Edge.&lt;/p&gt;

&lt;h4&gt;
  
  
  1 – Environment variables
&lt;/h4&gt;

&lt;p&gt;Lambda@Edge Functions &lt;strong&gt;cannot use environment variables&lt;/strong&gt;. That meant that we needed to find another way to pass data to our function. We opted for &lt;strong&gt;SSM parameters&lt;/strong&gt; and &lt;strong&gt;templated parameter names&lt;/strong&gt; in the Node.js code (we use Terraform to render the template when deploying the Lambda Function).&lt;/p&gt;

&lt;h4&gt;
  
  
  2 – Lambda package size limit
&lt;/h4&gt;

&lt;p&gt;For viewer events (reminder: we use the &lt;code&gt;viewer-request&lt;/code&gt; event), the Lambda package can be &lt;strong&gt;1 MB at most&lt;/strong&gt;. One MB is pretty small considering that it includes &lt;em&gt;all dependencies&lt;/em&gt; (except of course the runtime/standard library) of your Lambda Function.&lt;/p&gt;

&lt;p&gt;That's why &lt;strong&gt;we had to rewrite our Lambda in Node.js&lt;/strong&gt; instead of the original Python, because the Python package with its dependencies exceeded the 1 MB limit.&lt;/p&gt;

&lt;h4&gt;
  
  
  3 – Lambda region
&lt;/h4&gt;

&lt;p&gt;Lambda@Edge functions can &lt;strong&gt;only be created in the &lt;code&gt;us-east-1&lt;/code&gt; region&lt;/strong&gt;. It's not a big issue but it means you'll need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provision your AWS resources in that region to make things easier&lt;/li&gt;
&lt;li&gt;In Terraform, you'll need to have a separate AWS &lt;code&gt;provider&lt;/code&gt; to access the bucket you want to protect if it's not in &lt;code&gt;us-east-1&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  4 – Lambda role permission
&lt;/h4&gt;

&lt;p&gt;The IAM execution role associated with the Lambda@Edge functions &lt;strong&gt;must allow the principal service &lt;code&gt;edgelambda.amazonaws.com&lt;/code&gt;&lt;/strong&gt; in addition to the usual &lt;code&gt;lambda.amazonaws.com&lt;/code&gt;. See &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-edge-permissions.html"&gt;AWS - Setting IAM permissions and roles for Lambda@Edge&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authorization mechanism with Okta
&lt;/h3&gt;

&lt;p&gt;Once we managed the above restrictions and caveats, we focused on the authorization/authorization.&lt;/p&gt;

&lt;p&gt;Okta offers several ways to authenticate and authorize users. We decided to go with &lt;strong&gt;OAuth2&lt;/strong&gt;, the industry-standard protocol for authorization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;br&gt;
Okta implements the &lt;strong&gt;OpenID Connect (OIDC) standard&lt;/strong&gt; which adds a thin authentication layer on top of OAuth2 (that's the purpose of the ID token mentioned hereafter). Our solution would also work with pure OAuth2 with minimal modifications (removal of the ID token use in the code).&lt;/p&gt;

&lt;p&gt;OAuth2 itself offers several &lt;em&gt;authorization flows&lt;/em&gt; depending on the kind of application using it. In our case, we need the &lt;a href="https://developer.okta.com/docs/guides/implement-grant-type/authcode/main/"&gt;Authorization Code flow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is the complete diagram of the Authorization Code flow taken from &lt;a href="https://developer.okta.com/docs/guides/implement-grant-type/authcode/main/"&gt;developer.okta.com&lt;/a&gt; that shows how it works:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ebod1Uvk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i7s9tmajy6vup4xe6oof.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ebod1Uvk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i7s9tmajy6vup4xe6oof.png" alt="OAuth2 Authorization code flow diagram" width="880" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Our Lambda Function redirects the user to Okta where they will be prompted to login&lt;/li&gt;
&lt;li&gt;Okta redirects the user to our website/Lambda Function with a &lt;em&gt;code&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Our Lambda Function checks if the code is legit and exchanges it for access and ID &lt;em&gt;tokens&lt;/em&gt; by sending a request to Okta &lt;/li&gt;
&lt;li&gt;Depending on the result returned by Okta, we:

&lt;ul&gt;
&lt;li&gt;Allow or deny access to the restricted content&lt;/li&gt;
&lt;li&gt;If access is allowed, save the access and ID tokens in a cookie to avoid having to re-authorize the user on every page&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Using JSON Web Tokens to store authorization result
&lt;/h3&gt;

&lt;p&gt;So far we have a working authorization process; however, we need to check the access/ID token on &lt;strong&gt;every request&lt;/strong&gt; (a malicious user could forge an invalid cookie/tokens). Checking the tokens means sending a request to Okta and waiting for the response on &lt;strong&gt;every page&lt;/strong&gt; the user visits, which &lt;strong&gt;slows down the loading times significantly&lt;/strong&gt; and is clearly sub-optimal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; &lt;br&gt;
While local verification of the Okta token is &lt;em&gt;theoretically possible&lt;/em&gt;, as of this writing &lt;a href="https://github.com/okta/okta-jwt-verifier-js"&gt;the SDK provided by Okta&lt;/a&gt; uses a &lt;em&gt;LRU&lt;/em&gt; (in-memory) cache when fetching the keys used to check the tokens. Because we're using AWS Lambda, and memory/state of the program isn't kept between invocations, the SDK is useless to us: it would still send one HTTP request to Okta for every user request, to retrieve the JWKs (JSON Web Keys). Worse, there is a limitation of 10 JWK requests per minute, which would make our solution stop working if there is more than 10 requests per minute.&lt;/p&gt;

&lt;p&gt;We decided to use &lt;strong&gt;&lt;a href="https://jwt.io/introduction"&gt;JSON Web Tokens&lt;/a&gt;&lt;/strong&gt; to work around this. The initial authorization process is the same except that, instead of saving the access/ID tokens into a cookie, we create a JWT containing these tokens, and then save the JWT into a cookie.&lt;/p&gt;

&lt;p&gt;Since the JWT is cryptographically signed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A malicious actor cannot forge one (they would need the private key used to sign them)&lt;/li&gt;
&lt;li&gt;The checking step required on every request is fast: we traded a long and I/O expensive HTTP request for a quick cryptographic check.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Note on JWT expiration and renewal
&lt;/h4&gt;

&lt;p&gt;The JWT has a &lt;strong&gt;pre-defined expiration time which should be reasonably short&lt;/strong&gt;, to avoid having a valid JWT containing expired or revoked access/ID tokens. Another option would be to check the access/ID tokens regularly and revoke the associated JWT if needed, but then we would need a revocation mechanism, which would makes things more complex.&lt;/p&gt;

&lt;p&gt;Finally, as suggested above, the tokens provided by Okta have an expiration time. It is possible &lt;strong&gt;to transparently renew them using a refresh token&lt;/strong&gt; (so the user doesn't have to re-login when the tokens expire) but we didn't implement that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;While adding OAuth2 authentication to an S3 static bucket with Okta (or any other OAuth2 provider) is possible in an AWS-integrated and secure manner, it's certainly not straightforward.&lt;/p&gt;

&lt;p&gt;It requires writing a middleware between AWS and the OAuth2 provider (Okta in our case) using &lt;a href="mailto:Lambda@Edge"&gt;Lambda@Edge&lt;/a&gt;. We had to do the following ourselves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validate the user authentication&lt;/li&gt;
&lt;li&gt;Remember the user authentication&lt;/li&gt;
&lt;li&gt;Refresh the user authentication (not implemented in our solution)&lt;/li&gt;
&lt;li&gt;Revoke the user authentication (TTL is implemented, but revocation before the end of the TTL is not)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, a bunch of AWS resources must be created to glue everything together and make it work.&lt;/p&gt;

&lt;p&gt;But it's worth it, because it works and our website is now more secure.&lt;/p&gt;

&lt;p&gt;You can find the code of the Lambda@Edge as well as the infrastructure (Terraform) here:&lt;br&gt;
&lt;a href="https://github.com/GuiTeK/aws-s3-oauth2-okta"&gt;https://github.com/GuiTeK/aws-s3-oauth2-okta&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>s3</category>
      <category>oauth2</category>
      <category>okta</category>
    </item>
    <item>
      <title>Build a store locator that includes online sellers</title>
      <dc:creator>Chuck Meyer</dc:creator>
      <pubDate>Tue, 22 Mar 2022 14:35:39 +0000</pubDate>
      <link>https://dev.to/algolia/build-a-store-locator-that-includes-online-sellers-38hm</link>
      <guid>https://dev.to/algolia/build-a-store-locator-that-includes-online-sellers-38hm</guid>
      <description>&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Let's say you've been tasked to build an application to help consumers find agencies providing a specific service. Some of these agencies are local brick-and-mortar storefronts and others are online-only agencies that service the same local area. This problem was initially posed by Alejo Arias in the Algolia &lt;a href="https://discourse.algolia.com/t/aroundlatlng-or-faceted/13655" rel="noopener noreferrer"&gt;Discourse forum&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I have a series of providers in my index:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;some are national providers (they have a &lt;code&gt;national: true&lt;/code&gt; attribute)&lt;/li&gt;
&lt;li&gt;some are state-wide providers (they have a &lt;code&gt;state&lt;/code&gt; attribute with the list of states they service, e.g. &lt;code&gt;[“NY”, “FL”]&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;some are local (they have specific lat/lng in their &lt;code&gt;_geoloc&lt;/code&gt; attribute)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I want in my results anything that matches local providers by location close to my users, but also provide (in the same results) state and national providers. Adding a &lt;code&gt;aroundLatLng&lt;/code&gt; filter automatically removes other results, no matter what facets or filters I try.&lt;/p&gt;

&lt;p&gt;How can I achieve this?&lt;br&gt;
Basically I want to have something like: &lt;code&gt;aroundLatLng: x,y OR state: NY OR national: true&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So how do you combine results from a geographic search for brick-and-mortar stores with other results from boolean or text-based queries? And how do you build an interface to display them in a unified way?&lt;/p&gt;

&lt;h2&gt;
  
  
  Geographic data and Algolia Search
&lt;/h2&gt;

&lt;p&gt;As Alejo mentions, you can use Algolia for geographic searches by adding a special &lt;code&gt;_geoloc&lt;/code&gt; attribute on your records. You put one or more sets of latitude/longitude tuples into this attribute to indicate geographic locations linked to the record.&lt;/p&gt;

&lt;p&gt;Then you use the Algolia client libraries to query against these geocoded records -- filtering either within a radius around a fixed point (&lt;code&gt;aroundLatLong&lt;/code&gt;) or the area within a shape (&lt;code&gt;insideBoundingBox&lt;/code&gt; or &lt;code&gt;insidePolygon&lt;/code&gt;). The &lt;a href="https://www.algolia.com/doc/guides/managing-results/refine-results/geolocation/#geographical-filtering-and-sorting" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; goes into more detail about the differences between these methods. You can also read through &lt;a href="https://www.algolia.com/blog/engineering/building-a-store-locator-in-react-using-algolia-mapbox-and-twilio-part-1/" rel="noopener noreferrer"&gt;these posts&lt;/a&gt; that walk you through building a pure-geographic store locator.&lt;/p&gt;

&lt;p&gt;However, you cannot extract geographic and non-geographic results from the same query. If you are searching for proximity, records lacking &lt;code&gt;_geoloc&lt;/code&gt; attributes will not show up in the result set.&lt;/p&gt;

&lt;p&gt;So how do you perform this search when not all records have geographic coordinates?&lt;/p&gt;

&lt;h2&gt;
  
  
  A single index solution
&lt;/h2&gt;

&lt;p&gt;You could do the whole thing via geographic search. By adding &lt;code&gt;_geoloc&lt;/code&gt; data to the state and national records, you could search for everything using a geographic query. For instance, placing the statewide agencies at coordinates at the center of each state. This was the initial solution I added to the forum post, but there are several problems with this solution:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Alejo specifically mentions that some providers span multiple states&lt;/li&gt;
&lt;li&gt;Placing providers at the center of the state will cause inaccurate results for consumers who live near state borders&lt;/li&gt;
&lt;li&gt;National providers would need records in every state&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  A multi-index solution
&lt;/h2&gt;

&lt;p&gt;As an alternative you can build a multi-index solution with one index for the brick-and-mortar storefronts that include geographic data, and another for the state and national providers. You can then search the two data sources independently and blend the result sets. This approach requires two Algolia queries per search, but it will allow us to guarantee results from both types of providers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preparing your indices
&lt;/h2&gt;

&lt;p&gt;First, you'll need an agency data set. You can build one from scratch using a couple of sources. You can start with anonymized address data from &lt;a href="https://github.com/EthanRBrown/rrad" rel="noopener noreferrer"&gt;this repo&lt;/a&gt; containing about 3000 addresses across the United State. Then, run these addresses through a &lt;a href="https://github.com/chuckmeyer/agency-finder/tree/main/scripts" rel="noopener noreferrer"&gt;small script&lt;/a&gt; to add fictional agency names and randomly flag some of the agencies as "preferred".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;transform_records&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;address_records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;record_geocode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="c1"&gt;# One in ten chance agency is preferred 
&lt;/span&gt;    &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;preferred&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;objectID&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_name&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;preferred&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
      &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;objectID&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; Agency (Preferred)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;objectID&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; Agency&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;address&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;address1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;city&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;city&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;zip&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;postalCode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;record_geocode&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;lat&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;coordinates&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;lat&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;record_geocode&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;lng&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;coordinates&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;lng&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;_geoloc&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record_geocode&lt;/span&gt;
    &lt;span class="n"&gt;address_records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;address_records&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can use another script to generate statewide and multi-state agencies for the second index. Both data sets reside in &lt;a href="https://github.com/chuckmeyer/agency-finder/tree/main/data" rel="noopener noreferrer"&gt;this repo&lt;/a&gt;. You can create indices from these data sets under your existing Algolia account or &lt;a href="https://www.algolia.com/users/sign_up?utm_source=blog&amp;amp;utm_medium=main-blog&amp;amp;utm_campaign=devrel&amp;amp;utm_id=agency-finder" rel="noopener noreferrer"&gt;sign up for a free account&lt;/a&gt; and set up a new &lt;code&gt;agency_finder&lt;/code&gt; application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the front end
&lt;/h2&gt;

&lt;p&gt;Now that you've populated your indices, it's time to build the front end. Algolia's &lt;code&gt;geoSearch&lt;/code&gt; component in the InstantSearch library includes a helper component to initialize the Google Maps API, render a Map, and tie that map to geolocation queries in your Algolia index. It's the same component I used previously to build a &lt;a href="https://www.algolia.com/blog/engineering/building-a-covid-19-geosearch-index-using-csv-files-mongodb-or-graphql/" rel="noopener noreferrer"&gt;COVID-19 case visualizer&lt;/a&gt;. However, for this project, you want the user to type in an address and resolve the geolocation information for them using the Google Places API. This proves challenging using the out-of-the-box components in InstantSearch, so you'll build your own interface from scratch.&lt;/p&gt;

&lt;p&gt;This &lt;a href="https://www.tracylum.com/blog/2017-05-20-autocomplete-an-address-with-a-react-form/" rel="noopener noreferrer"&gt;blog post&lt;/a&gt; gives us a solid model for building an address autocomplete form in React. You'll use this as the foundation for your &lt;a href="https://github.com/chuckmeyer/agency-finder/tree/main/src/AgencyFinderForm.js" rel="noopener noreferrer"&gt;&lt;code&gt;AgencyFinderForm&lt;/code&gt;&lt;/a&gt; component to render the address autocomplete input field as well as read-only fields to display the resulting address. The latitude/longitude are stored in state, but not shown on the form&lt;/p&gt;

&lt;p&gt;You can modernize the code from the blog by using the Google &lt;a href="https://cloud.google.com/blog/products/maps-platform/loading-google-maps-platform-javascript-modern-web-applications" rel="noopener noreferrer"&gt;Wrapper&lt;/a&gt; around your React components to initialize the &lt;code&gt;google&lt;/code&gt; object and add the Places API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;   &lt;span class="nx"&gt;renderForm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SUCCESS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AgencyFinderForm&lt;/span&gt; &lt;span class="nx"&gt;handleCallback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handleCallback&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;      &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h3&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h3&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;      &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Find&lt;/span&gt; &lt;span class="nx"&gt;an&lt;/span&gt; &lt;span class="nx"&gt;Agency&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;instructions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;🔍&lt;/span&gt; &lt;span class="nx"&gt;Search&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;find&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;closest&lt;/span&gt; &lt;span class="nx"&gt;agencies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left-panel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Wrapper&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REACT_APP_GOOGLE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;renderForm&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;libraries&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;places&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right-panel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AgencyFinderResults&lt;/span&gt; &lt;span class="nx"&gt;hits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next you add a &lt;code&gt;clear&lt;/code&gt; button to the basic form.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="nf"&gt;handleClear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialState&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;autocomplete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;autocompleteListener&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initAutocomplete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, you'll clean up processing the &lt;code&gt;address_components&lt;/code&gt; from the Places API with following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="nf"&gt;handlePlaceSelect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addressObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;autocomplete&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPlace&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;addressObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address_components&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;short_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;types&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;short_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="nx"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;)({&lt;/span&gt;
      &lt;span class="na"&gt;streetAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;street_number&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;locality&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;locality&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sublocality_level_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;administrative_area_level_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;zipCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postal_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;geoCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;addressObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;addressObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Querying for results
&lt;/h2&gt;

&lt;p&gt;After the user has selected a location and you have the latitude, longitude, and address information stored in the component state, you're ready to query the indices. You use the &lt;code&gt;multipleQueries&lt;/code&gt; method from the &lt;a href="https://www.algolia.com/doc/api-reference/api-methods/multiple-queries/?client=javascript" rel="noopener noreferrer"&gt;Javascript API client&lt;/a&gt; to batch together the two queries and combine the results. This will still count as two queries against your Algolia limit, but it reduces the number of round trips to the API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="na"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;statesIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;hitsPerPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;geoIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;aroundLatLng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geoCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;facetFilters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preferred&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;preferred:true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;hitsPerPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}];&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;multipleQueries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;allHits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
      &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;allHits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handleCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;allHits&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, you initialize the two queries. Notice how the &lt;code&gt;multipleQueries&lt;/code&gt; method allows us to mix geographic and string-based queries, and even layer in an&lt;br&gt;
optional &lt;code&gt;facetFilter&lt;/code&gt; for your "Preferred" agencies. You then pass the array of queries to the client. The response includes the individual results from each&lt;br&gt;
query, but you can just smash the &lt;code&gt;hits&lt;/code&gt; from the two result sets into a single array and pass them to the &lt;code&gt;AgencyFinderResults&lt;/code&gt; component.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it all together
&lt;/h2&gt;

&lt;p&gt;You now have a solid proof-of-concept React component for layering geographic and non-geographic results into a single result set. At this point you could improve the example by adding a Google Map to display the geographic results. You could also pivot back to a single index, using &lt;code&gt;multipleQueries&lt;/code&gt; ability to query the same index multiple times with different parameters.&lt;/p&gt;

&lt;p&gt;The complete example is available in this &lt;a href="https://github.com/chuckmeyer/agency-finder" rel="noopener noreferrer"&gt;Github repo&lt;/a&gt; or you can try out a &lt;a href="https://agency-finder.vercel.app/" rel="noopener noreferrer"&gt;live demo&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Centralizing state and data handling with React Hooks: on the road to reusable components</title>
      <dc:creator>Emmanuel Krebs</dc:creator>
      <pubDate>Thu, 10 Mar 2022 14:31:33 +0000</pubDate>
      <link>https://dev.to/algolia/centralizing-state-and-data-handling-with-react-hooks-on-the-road-to-reusable-components-3f1i</link>
      <guid>https://dev.to/algolia/centralizing-state-and-data-handling-with-react-hooks-on-the-road-to-reusable-components-3f1i</guid>
      <description>&lt;p&gt;Application development is often reactive. We see the need, we deliver a solution as fast as possible. During this fast software cycle, we gather requirements and implement them as soon as they appear. I’m not talking about quick and dirty. I’m referring to using the best &lt;a href="https://en.wikipedia.org/wiki/Rapid_application_development"&gt;RAD&lt;/a&gt; practices - rapid application development. &lt;br&gt;
​&lt;br&gt;
The RAD cycle is as follows: you implement great core features (&lt;a href="https://en.wikipedia.org/wiki/Minimum_viable_product"&gt;MVP-style&lt;/a&gt;), relying on years of experience to create maintainable code. But over time, several things occur: requirements change, more code gets written, and the codebase starts to rebel against your intuitively brilliant but perhaps not fully robust architecture. So you start refactoring. Also, you discover that technology changes, offering new ways to make your code simpler, cleaner, and more powerful. &lt;br&gt;
​&lt;br&gt;
&lt;strong&gt;Enter game changer &lt;a href="https://reactjs.org/docs/hooks-intro.html"&gt;React Hooks&lt;/a&gt;&lt;/strong&gt;. And, a fast growing business that requires you to rewrite your application with loads of new features. &lt;br&gt;
​&lt;br&gt;
&lt;em&gt;Rewrite&lt;/em&gt; – from scratch. Life offers a second opportunity. &lt;br&gt;
​&lt;/p&gt;
&lt;h2&gt;
  
  
  How React Hooks saved our administration application
&lt;/h2&gt;

&lt;p&gt;​&lt;br&gt;
Application development can also be pro(Re)active. Our administration application is data-intensive. Previously, many separate (and competing) components had managed their data independently - connecting, formatting, displaying, updating, etc.. &lt;br&gt;
​&lt;/p&gt;
&lt;h3&gt;
  
  
  The requirements of an Admin application
&lt;/h3&gt;

&lt;p&gt;​&lt;br&gt;
An Admin application is a good candidate for centralizing data handling. Administrators need to see the data as is, so the onscreen views usually match the structure of the underlying data. So, while our client-facing dashboard presents functional views for business users, an administrator needs to see user or client subscription information in a consistent and straightforward manner. &lt;br&gt;
​&lt;br&gt;
What we needed was a more scalable solution. Since we pull data from multiple sources – all accessible via one API with many endpoints – we wanted to centralize the common aspects of data handling. This not only gave us immediate benefits (better testing, caching, syncing, standard typing), it facilitated and simplified future data integrations.&lt;br&gt;&lt;br&gt;
​&lt;/p&gt;
&lt;h3&gt;
  
  
  A customized hook
&lt;/h3&gt;

&lt;p&gt;​&lt;br&gt;
We implemented a custom React hook called &lt;code&gt;useData&lt;/code&gt;, which manages and therefore centralizes all data-retrieval API calls, data exchanges, type checking, caching, and other such data-based functionality. The caching alone enhanced user-facing speed enormously. Equally important, the speed and centralization enabled our front-end developers to reuse their components and UI elements in different parts of the interface. Such reusability created a feature-rich, user-friendly UI/UX without front-end developers needing to maintain unique state information within each component. Lastly, under the hood, data reusability enabled a coherence in the models that drove the front-end functionality. We will discuss front-end benefits of React hooks in future articles; this article is about how we served the front-end with a reliable and scalable layer of data handling.&lt;br&gt;&lt;br&gt;
​&lt;/p&gt;
&lt;h3&gt;
  
  
  How our &lt;code&gt;useData&lt;/code&gt;hook centralized the process
&lt;/h3&gt;

&lt;p&gt;​&lt;br&gt;
We use different data sources, some more complex than others but all following the same JsonAPI specification. Additionally, they all have the same needs – a means to:&lt;br&gt;
​&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retrieve data&lt;/li&gt;
&lt;li&gt;Deserialize and format it&lt;/li&gt;
&lt;li&gt;Validate its format&lt;/li&gt;
&lt;li&gt;Perform error handling (data quality, network)&lt;/li&gt;
&lt;li&gt;Synchronize with app refreshes and other data/workflows&lt;/li&gt;
&lt;li&gt;Cache the data and keep it up to date
​
Enough talking, here is our &lt;code&gt;useData&lt;/code&gt; hook code:
​
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useCallback&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useQueryClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ZodObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Infer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useApi&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;metaBuilder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MetaInstance&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;models&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Options&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;forceCallApi&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;preventGetData&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ApiData&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;MetaInstance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;DataResult&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Output&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Model&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;ZodObject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ModelType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Output&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;ModelType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ModelType&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ModelType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;forceCallApi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;preventGetData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;Options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;DataResult&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Output&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useQueryClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getDataFromApi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ApiData&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Output&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// here we get the data (and meta) using getData, and handle errors and various states&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiData&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;metaBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiMeta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;getData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;forceCallApi&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;getDataResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useQuery&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ApiData&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Output&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;forceCallApi&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="nx"&gt;getDataFromApi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;preventGetData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;cacheTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;forceCallApi&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;Infinity&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;refresh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refetchQueries&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;forceCallApi&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;exact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;forceCallApi&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;getDataResult&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;getDataResult&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="nx"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;​&lt;br&gt;
As you can see, this hook takes three parameters that, when combined, give us all the following functionalities: &lt;br&gt;
​&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A “builder” function that transforms and enhances the data for use by our components&lt;/li&gt;
&lt;li&gt;The URL of the API endpoint that retrieves the data&lt;/li&gt;
&lt;li&gt;Optional parameters. For example, to ignore cache or wait for some other data to be ready before calling the API
​
The result is that our components no longer need to manage all that. We’ve abstracted and encapsulated the complexity.
​
The &lt;code&gt;useData&lt;/code&gt; hook returns some values we can use in our components:
​&lt;/li&gt;
&lt;li&gt;Some states: loading and errors (if any)&lt;/li&gt;
&lt;li&gt;The data (if any)&lt;/li&gt;
&lt;li&gt;Meta information (if present – pagination information, for example)&lt;/li&gt;
&lt;li&gt;A refresh function (to refresh the data by calling the API again)
​
## Building the data 
​
Let's take a deeper look at what this code does and how we use it.
​
## Schema validation with Zod
​
Getting the data is one thing. Ensuring that the data is correctly structured, or typed, is another. Complex data types require validation tools like &lt;a href="https://github.com/jquense/yup"&gt;yup&lt;/a&gt; or &lt;a href="https://github.com/colinhacks/zod"&gt;zod&lt;/a&gt; that enforce efficient and clean methods, and offer tools and error handling runtime errors based on faulty types. Our front end relies on strongly-typed data sets, so the validation stage is crucial for us.
​
We use &lt;a href="https://github.com/colinhacks/zod"&gt;zod&lt;/a&gt;. Zod is used to build a model of the data. For example, here is what the model for our Application could look like:
​
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;applicationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;ownerEmail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;planVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;planName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;​&lt;br&gt;
​&lt;br&gt;
Then, to construct our builder function, we use in-house-built generic helpers on top of the zod model.This helper takes two parameters: &lt;br&gt;
​&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The model of our data (Application in our example above)&lt;/li&gt;
&lt;li&gt;A transformer function that is used to enrich that model. 
​
In our case, that transformer would look like this:
​
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Infer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transformer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;application&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;Application&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;application&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;planName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; v&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;planVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;​&lt;br&gt;
Another example of enrichment is if a model has a date: we usually want it to expose a javascript date rather than a string date.&lt;br&gt;
​&lt;br&gt;
We have 2 versions of that helper function (one for objects and one for arrays). Below is the first one:&lt;br&gt;
​&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ZodType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TypeOf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Infer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SentryClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utils/sentry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buildObjectModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
  &lt;span class="nx"&gt;Model&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;ZodType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ModelType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Output&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;ModelType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ModelType&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;transformer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TypeOf&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Output&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ModelType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ModelType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;safeParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;validation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;SentryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;validation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;extra&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod error:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;validation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data object is:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;transformer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;transformer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;validation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;​&lt;br&gt;
The typed output by zod is very clean and looks like a typescript type that we would have written ourselves, with the addition that zod parses the JSON using our model. For safety, we use the &lt;code&gt;safeParse&lt;/code&gt; method from zod, which allows us to send back the JSON “as is” in case of an error during the parsing step. We would also receive an error on our error tracking tool, &lt;a href="https://sentry.io/welcome/"&gt;Sentry&lt;/a&gt;.&lt;br&gt;
​&lt;br&gt;
With our example, our builder function would look like:&lt;br&gt;
​&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;applicationBuilder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buildObjectModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;transformer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// and for the record, here is how to get the type output by this builder:&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ApplicationModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ReturnType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;applicationBuilder&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// which looks like this in your code editor:&lt;/span&gt;
&lt;span class="c1"&gt;// type ApplicationModel = {&lt;/span&gt;
&lt;span class="c1"&gt;//   plan: string;&lt;/span&gt;
&lt;span class="c1"&gt;//   applicationId: string;&lt;/span&gt;
&lt;span class="c1"&gt;//   name: string;&lt;/span&gt;
&lt;span class="c1"&gt;//   ownerEmail: string;&lt;/span&gt;
&lt;span class="c1"&gt;//   planVersion: number;&lt;/span&gt;
&lt;span class="c1"&gt;//   planName: string;&lt;/span&gt;
&lt;span class="c1"&gt;// }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;​&lt;/p&gt;

&lt;h3&gt;
  
  
  Calling the API
&lt;/h3&gt;

&lt;p&gt;​&lt;br&gt;
Internally, we use another custom hook &lt;code&gt;useApi&lt;/code&gt; (less than 200 lines of code) to handle the GET/POST/PATCH/DELETE. In this hook, we use &lt;a href="https://github.com/axios/axios"&gt;axios&lt;/a&gt; to call the backend API and perform all typical &lt;a href="https://en.wikipedia.org/wiki/Create,_read,_update_and_delete"&gt;CRUD&lt;/a&gt; functionality. For example, on the read side, Axios deserializes the data we receive before it is converted from the JSON API spec to a more classic JSON, and switching from snake_case to camelCase. It also handles any meta information we receive. &lt;br&gt;
​&lt;br&gt;
Also, from a process point of view, it manages request canceling and errors when calling the API.&lt;br&gt;
​&lt;/p&gt;
&lt;h3&gt;
  
  
  Caching the data
&lt;/h3&gt;

&lt;p&gt;​&lt;br&gt;
At this point, we can summarize: the &lt;code&gt;useApi&lt;/code&gt; hook gets the data, which is then passed through the builder to be validated and enriched; and the resulting data is cached using &lt;a href="https://github.com/tannerlinsley/react-query"&gt;react-query&lt;/a&gt;.&lt;br&gt;
​&lt;br&gt;
We implemented react-query for caching the data on the front end, using the API endpoint URL as the cache key. React-query uses the &lt;code&gt;useApi&lt;/code&gt; hook mentioned above to fetch, synchronize, update, and cache remote data, allowing us to leverage all these functionalities with a very small codebase. &lt;br&gt;
All we have to do on top of that is to implement react-query’s provider. To do so, we’ve constructed a small react component: &lt;br&gt;
​&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FC&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;QueryClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;QueryClientProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;QueryClientProviderProps&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;QueryClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;defaultOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;refetchOnWindowFocus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;refetchInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;refetchIntervalInBackground&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;refetchOnMount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;refetchOnReconnect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;IProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Omit&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;QueryClientProviderProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;QueryClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GlobalContextProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FC&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;IProps&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;QueryClientProvider&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/QueryClientProvider&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;​&lt;br&gt;
Most importantly, it manages our caching. We have many components that need the same data, so we wanted to avoid unnecessary network traffic to retrieve the same information. Performance is always key. So is limiting potential errors performing unnecessary network calls. Now, with caching, if one component asks for data, our cache will store that data and give it to other components that ask for the same information. In the background, React-query of course ensures that the data in the cache is kept up to date. &lt;br&gt;
​&lt;br&gt;
To sum up, here is an example of a component built using this &lt;code&gt;useData&lt;/code&gt; hook and our Application model as defined above:&lt;br&gt;
​&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FC&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ApplicationProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;applicationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ApplicationCard&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FC&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ApplicationProps&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;applicationId&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;application&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;applicationBuilder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`/applications/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;applicationId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;))&lt;/span&gt;&lt;span class="sr"&gt;}&amp;lt;/&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;applicationId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ownerEmail&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;​&lt;br&gt;
As you can see, our &lt;code&gt;useData&lt;/code&gt; hook lets us standardize  the loading &amp;amp; errors states, thus encouraging us to write &lt;strong&gt;reusable components&lt;/strong&gt; that handle those states. For example, we have reusable &lt;code&gt;StateCard&lt;/code&gt; and &lt;code&gt;StateContainer&lt;/code&gt; components. With the data now easily available, we can go about integrating those &lt;strong&gt;reusable components&lt;/strong&gt;  and focus exclusively on building a great front end experience – cleanly, fully-featured, and scalable.&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
    </item>
    <item>
      <title>API keys vs JWT authorization – Which is best?</title>
      <dc:creator>Julien Bourdeau</dc:creator>
      <pubDate>Mon, 28 Feb 2022 10:18:29 +0000</pubDate>
      <link>https://dev.to/algolia/api-keys-vs-jwt-authorization-which-is-best-4o3e</link>
      <guid>https://dev.to/algolia/api-keys-vs-jwt-authorization-which-is-best-4o3e</guid>
      <description>&lt;p&gt;As you build your own APIs, examining your &lt;em&gt;use cases&lt;/em&gt; will help you decide which security methods to implement for each API. For some use cases, API keys are sufficient; in others, you’ll want the additional protection and flexibility that comes with JSON Web Tokens (JWT) authorization. &lt;/p&gt;

&lt;p&gt;So in the comparison &lt;em&gt;API keys versus JWT authorizations&lt;/em&gt;, the winner is .. it depends.&lt;/p&gt;

&lt;p&gt;All API calls require some measure of security and access control. API keys with a sensible ACL can provide enough security without adding too much overhead. But with the increased use of microservices for nearly every small and large task, your API ecosystem may need a more unified, granular, and secure method like JWT authorization. &lt;/p&gt;

&lt;h2&gt;
  
  
  When API keys are fine
&lt;/h2&gt;

&lt;p&gt;Online businesses that use a cloud-based Search API can normally expose read-only API keys without great risk – if the underlying index of data contains no secrets. In fact, client-side apps &lt;em&gt;should&lt;/em&gt; connect to the cloud search engine directly for performance reasons – thereby exposing their API key – to avoid the lengthier trip to the back-end server before going to the cloud. On the other hand, index updates require restricted access API keys, which should never be exposed. &lt;/p&gt;

&lt;p&gt;But in both use cases (search and indexing), API keys are generally fine; there’s no urgent need for the overhead of JWT authorization.&lt;/p&gt;

&lt;h2&gt;
  
  
  When it’s time to consider JWT authorization
&lt;/h2&gt;

&lt;p&gt;Increasingly, however, APIs demand more flexibility and protection. JWT authorization not only adds an additional level of security (more about this below), it also provides a more manageable and easier method to orchestrate the multitude and network of APIs used on a daily basis. As described in the next sections, &lt;a href="https://en.wikipedia.org/wiki/JSON_Web_Token" rel="noopener noreferrer"&gt;&lt;strong&gt;JWT&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;centralizes authentication &amp;amp; authorization by generating a&lt;/strong&gt; &lt;strong&gt;&lt;em&gt;single shared token&lt;/em&gt;&lt;/strong&gt; &lt;strong&gt;that contains user and app-level information (encrypted or hashed) to help any API in the same ecosystem determine what the token-holder is allowed to do.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;API keys, at first glance, seem so simple – you just need to send the proper API key and you’re ready to go. But that’s a bit deceptive. When your ecosystem relies on many integrated microservices, managing numerous API keys becomes messy, unreliable, and nearly impossible to manage. They grow in number, they change, they expire, they get deleted, their ACLs change – all that and more, without notifying the apps and users relying on these same API keys. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With JWT, you lay the foundation for a single sign-on architecture.&lt;/strong&gt; We discuss this below in the section &lt;em&gt;Switching over to JWT.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Using API keys vs JWT authorization
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Using Api Keys
&lt;/h3&gt;

&lt;p&gt;API keys are direct, simple, and fully transparent. They don’t represent any underlying information, they do not encrypt a secret message. They are simply an unreadable unique id. &lt;/p&gt;

&lt;p&gt;Here’s an example of a publicly available API key in client-side javascript. The code includes an App ID (&lt;code&gt;app-id-BBRSSHR\&lt;/code&gt;) that uses the API key (&lt;code&gt;temp-search-key-ere452sdaz56qsjh565d\&lt;/code&gt;) to allow it to search. The App ID refers to one of your user-facing apps (like an online website or streaming service). The API key is temporary and short-lived (expiring after some period of time) to provide some protection from unwanted use or abuse.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;hitTemplate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./helpers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;search&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;instantsearch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app-id-BBRSSHR&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;temp-search-key-ere452sdaz56qsjh565d&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;demo&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;_ecommerce&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another example: Indexing, which requires a more secure API key. It has the same format (&lt;code&gt;appId&lt;/code&gt; + &lt;code&gt;apiKey&lt;/code&gt;), but it is private because it is hidden from the public, either in compiled code or a secure database on your back end. The App ID (&lt;code&gt;YourApplicationID&lt;/code&gt;) refers to the back office system. The API key (&lt;code&gt;YourAdminAPIKey&lt;/code&gt;) might be a permanent admin key that changes only once a year for simpler maintenance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Algolia&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nc"&gt;\AlgoliaSearch&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nc"&gt;\SearchClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SearchClient&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'YourApplicationID'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'YourAdminAPIKey'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;initIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'demo\_ecommerce'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$index&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;saveObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'firstname'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Jimmie'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'lastname'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Barninger'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'city'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'New York'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'objectID'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'myID'&lt;/span&gt;
  &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using the JWT token
&lt;/h3&gt;

&lt;p&gt;A &lt;a href="https://jwt.io/introduction" rel="noopener noreferrer"&gt;JWT token&lt;/a&gt; is a large unreadable set of characters that contains hidden and encoded information, masked by a signature or encryption algorithm. It’s made up of three parts: a header, body, and signature. They are separated by a period: &lt;code&gt;Header.Body.Signature&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;EZPZAAdsqfqfzeezarEUARLEA.sqfdqsTIYfddhtreujhgGSFJ.fkdlsqEgfdsgkerGAFSLEvdslmgIegeVDEzefsqd&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The JWT header&lt;/strong&gt; is &lt;code&gt;EZPZAAdsqfqfzeezarEUARLEA&lt;/code&gt;, which contains the following information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"alg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HS256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"typ"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JWT"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1234567890"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Doe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1516239022&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are different algorithms available, for example &lt;code&gt;RS256&lt;/code&gt; and &lt;code&gt;HS256&lt;/code&gt;. Here we use &lt;code&gt;HS256&lt;/code&gt; which requires a private key to be used when generating the signature. &lt;code&gt;RS256\&lt;/code&gt; uses both a private and a public key combination. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The JWT body&lt;/strong&gt; (called the &lt;strong&gt;payload&lt;/strong&gt;) is &lt;code&gt;sqfdqsTIYfddhtreujhgGSFJ&lt;/code&gt;, which contains the user’s identity to help establish the token user’s rights. It also gives other information, such as an expiration date (the shorter the more secure):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1234567890"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Doe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1516239022&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The signature&lt;/strong&gt; is &lt;code&gt;fkdlsqEgfdsgkerGAFSLEvdslmgIegeVDEzefsqd&lt;/code&gt;, which is generated by combining the header, body, and a shared private key, using the HS256 hashing method, as indicated in the header.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;HMACSHA&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;base&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="err"&gt;UrlEncode(header)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;+&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;base&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="err"&gt;UrlEncode(payload),&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;secret)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And so that’s how you get the following token: &lt;code&gt;Header.Body.Signature&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;EZPZAAdsqfqfzeezarEUARLEA.sqfdqsTIYfddhtreujhgGSFJ.fkdlsqEgfdsgkerGAFSLEvdslmgIegeVDEzefsqd&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A word about authentication &amp;amp; authorization
&lt;/h2&gt;

&lt;p&gt;Both API key and JWT are used for authentication and authorization, but they do it differently.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Authentication&lt;/em&gt; allows the user or application to use one or more methods of the API. &lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Authorization&lt;/em&gt; defines how they can use those methods. Some apps or users can only read the data; others can update; others are administrators (roles and permissions). Same goes for API keys, as managed by their ACLS - they can be read-only, write-access, or admin.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;API keys authenticate and authorize using the same API key. JWT Authorization requires an initial authentication process before it generates the authorization token. Once the token is generated, it is used across the ecosystem to determine what the token holder can and cannot do. &lt;/p&gt;

&lt;p&gt;Additionally, API keys authenticate the &lt;em&gt;application&lt;/em&gt; not the &lt;em&gt;user&lt;/em&gt;; whereas, JWT authenticates both the user and the application. Of course, you could use API keys for user-level authorization, but it’s not well-designed for that - an ecosystem would need to generate and manage API keys for every user or session id, which is unnecessarily burdensome for a system. &lt;/p&gt;

&lt;h2&gt;
  
  
  A word about better protection and security
&lt;/h2&gt;

&lt;p&gt;In terms of security, both API keys and JWT are open to attacks_._ The best security measure is to implement a secure architecture for all end-to-end communications. &lt;/p&gt;

&lt;p&gt;That said, API keys are historically less secure because they rely on being &lt;em&gt;hidden.&lt;/em&gt; You can hide keys with SSL/TLS/HTTPS, or by restricting their usage to back-end processes. However, you can’t control all API use; API keys are likely to leak; HTTPS is not always possible; and so on. With JWT, because the token is hashed / encrypted, it comes with a more secure methodology that is less likely to be exposed. &lt;/p&gt;

&lt;h2&gt;
  
  
  What information is in a JWT token?
&lt;/h2&gt;

&lt;p&gt;The most notable difference between an API &lt;em&gt;key&lt;/em&gt; and a JWT &lt;em&gt;token&lt;/em&gt; is that JWT tokens are self-contained: they contain information an API needs to secure the transaction and determine the granularity of the token-holder’s rights. In contrast, API keys use their uniqueness to gain initial access; but then the API needs to find a key’s associated ACL in a central table to determine exactly what the key gives access to. Typically, the API key provides only application-level security, giving every user the same access; whereas the JWT token provides user-level access. &lt;/p&gt;

&lt;p&gt;A JWT token can contain information like its expiration date and a user identifier to determine the rights of the user across the entire ecosystem. &lt;/p&gt;

&lt;p&gt;Let’s take a look at some of the information you can include in a JWT token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;iss&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(issuer):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;identifies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;principal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;issued&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;JWT.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;sub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(subject):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;identifies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;principal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;subject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;JWT.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Must&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;unique&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;aud&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(audience):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;identifies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;recipients&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;JWT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;intended&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(array&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;strings/uri)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;exp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(expiration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;time):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;identifies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;expiration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(UTC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Unix)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;after&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;which&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;must&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;no&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;longer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;accept&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;token.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;It&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;after&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;issued-at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;time.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;nbf(not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;before):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;identifies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;UTC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Unix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;before&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;which&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;JWT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;must&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;accepted&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;iat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(issued&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;at):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;identifies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;UTC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Unix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;which&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;JWT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;was&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;issued&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;jti&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(JWT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ID):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;provides&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;unique&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;identifier&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;JWT.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"iss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stackoverflow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
    &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"joe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
    &lt;/span&gt;&lt;span class="nl"&gt;"aud"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"all"&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; 
    &lt;/span&gt;&lt;span class="nl"&gt;"iat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1300819370&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
    &lt;/span&gt;&lt;span class="nl"&gt;"exp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1300819380&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
    &lt;/span&gt;&lt;span class="nl"&gt;"jti"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3F2504E0-4F89-11D3-9A0C-0305E82C3301"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; 
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; 
        &lt;/span&gt;&lt;span class="nl"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; 
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; 
            &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"joe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
            &lt;/span&gt;&lt;span class="nl"&gt;"displayName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Joe Smith"&lt;/span&gt;&lt;span class="w"&gt; 
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; 
        &lt;/span&gt;&lt;span class="nl"&gt;"roles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"admin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"finaluser"&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; 
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  JWT authorization offers flexibility, reliability, and more security
&lt;/h2&gt;

&lt;p&gt;So here’s the scenario. You have many applications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Apps that allow us to track the API usage of our all our users&lt;/li&gt;
&lt;li&gt;Apps that give access to billing and customer data&lt;/li&gt;
&lt;li&gt;Apps that allow API users to change settings on different systems&lt;/li&gt;
&lt;li&gt;Apps that retrieve product data or business content&lt;/li&gt;
&lt;li&gt;And other such use cases&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Doing it with API keys
&lt;/h3&gt;

&lt;p&gt;The problem arises when there are more APIs running the show. Where do you store the 100s+ of API keys needed for all this access? Managing too many API keys requires a table of API keys to be made available to all apps that run within the ecosystem. &lt;/p&gt;

&lt;p&gt;Thus, every app in the ecosystem would have to be aware of the database. Each app would need to connect and read from that table. And some apps would be allowed to generate new keys or modify existing keys. All of which is fine, but becomes difficult to maintain. One way to manage this is to create an API that validates a key against this database. But for that, you’ll need a second authentication system to connect to this API. &lt;/p&gt;

&lt;p&gt;Not only is retrieving the API keys cumbersome, but maintaining their duration and level of authorization becomes tedious. And not every app functions at the app level, they need user-level rights. Additionally, APIs can be used in ways that differ from system to system. Worse, different apps share API keys, and are therefore dependent on the different apps to maintain correct access-levels of the shared API keys.&lt;/p&gt;

&lt;h3&gt;
  
  
  Switching over to JWT
&lt;/h3&gt;

&lt;p&gt;Any API that requires authentication can easily switch over to JWT’s authorization. With JWT authorization, you get a user-based authentication. Once the user is authenticated, the user gets a secure token that they can use on all systems. The management of the user (and therefore the token) is centralized. You set up access rights and you give each user different rights for each system. The JWT authorization endpoint authenticates the user and creates the token. &lt;/p&gt;

&lt;p&gt;With that architecture, the next step is to create a single sign on into the full ecosystem and rely only on the token for rights. In the end, the simplest and most robust and manageable approach is to create this single endpoint dedicated to do both authentication &amp;amp; authorization, such that all other servers across the entire ecosystem can rely on that central point to authorize the API interactions between client and server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2yxgd9emlmnnjlkll8b1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2yxgd9emlmnnjlkll8b1.jpg" alt="Authentication and Authorization flow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  In sum, sometimes JWT is absolutely needed and sometimes it’s overkill
&lt;/h2&gt;

&lt;p&gt;As an application developer, my primary concern when building APIs is that they are used properly and provide the correct data and functionality. I therefore rely heavily on the advice of DevOps to suggest the best and most manageable security. But it’s not just about security. In a large ecosystem, we need a simple and robust way to access microservices across multiple systems and servers, which is best served by centralizing the authentication and authorization processes.&lt;/p&gt;

&lt;h3&gt;
  
  
  JWT is overkill in the following two situations:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A simple ecosystem with only a few APIs&lt;/li&gt;
&lt;li&gt;Suppliers of third-party APIs. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Third-party APIs should be easy to use and fast to implement, with little upfront work when integrating. Additionally, you need to share the key used to sign the token. So it's best when you control both ends, which is unlikely in third-party sceneries. You also need to trust the implementation. for example an API could choose to ignore the expiration date or the NBF (“not before”).&lt;/p&gt;

&lt;h3&gt;
  
  
  JWT is not overkill when …
&lt;/h3&gt;

&lt;p&gt;You’ll want to use JWT when multiple services need to communicate with each other over a vast network. Centralizing and securing those exchanges is crucial. It’s especially important when each network or app requires different levels of access based on the user, not just the app. It’s also important to have control over the traffic and to be able to prioritize network calls. Finally, you want the simple plug-and-play experience when adding new microservices or improving existing ones.&lt;/p&gt;

</description>
      <category>jwt</category>
      <category>auth</category>
      <category>api</category>
      <category>keys</category>
    </item>
    <item>
      <title>Integrating custom dictionaries into NLP for greater clarity</title>
      <dc:creator>Joris Valette</dc:creator>
      <pubDate>Fri, 18 Feb 2022 09:07:41 +0000</pubDate>
      <link>https://dev.to/algolia/integrating-custom-dictionaries-into-nlp-for-greater-clarity-424c</link>
      <guid>https://dev.to/algolia/integrating-custom-dictionaries-into-nlp-for-greater-clarity-424c</guid>
      <description>&lt;p&gt;Language is a funny thing. For example, we take for granted that Cinderella wore glass slippers. Only in a fairy tale can people walk in glass shoes. But maybe it’s metaphorical – the fragility of being Cinderella. Or maybe it’s simply a &lt;a href="https://writinginmargins.weebly.com/home/cinderellas-shoes-glass-or-fur"&gt;mistranslation&lt;/a&gt; of the original Latin word “vair” (squirrel fur) for the French “verre” (glass).&lt;br&gt;
​&lt;br&gt;
Language is also hard to pin down. Especially when lost in translation. But no need to despair – sometimes what gets lost can have &lt;a href="https://www.newyorker.com/books/page-turner/lost-in-translation-what-the-first-line-of-the-stranger-should-be"&gt;surprising and moving results&lt;/a&gt;.&lt;br&gt;
​&lt;br&gt;
But we don’t always want to be surprised. Like when we ask direct questions or search for specific items that match our queries. At that point, we aspire to be crystal-clear. &lt;br&gt;
​&lt;br&gt;
That’s where dictionaries come in. &lt;strong&gt;Dictionaries allow us to be clear, by reinforcing the clarity of each word within the context of the larger phrase.&lt;/strong&gt; We use &lt;em&gt;custom&lt;/em&gt; dictionaries to reinforce our natural language processing (NLP). Here’s how.&lt;br&gt;
​&lt;/p&gt;
&lt;h2&gt;
  
  
  Stop words and plurals, and compounds and segments – a few of my favorite things
&lt;/h2&gt;

&lt;p&gt;​&lt;br&gt;
Many users still type questions like “What is the best search engine?” instead of the shorter “best search engine”. It’s natural for them to type the way they speak. But other people prefer to use shorter, incomplete phrases to return the same results. With advances in search technology, even nonsensical queries, such as “engine best search”, return great results.&lt;br&gt;
​&lt;br&gt;
Nevertheless, the full phrase is still in fashion – even more so with voice. The success of voice search depends on allowing people to speak naturally. And &lt;strong&gt;stop words&lt;/strong&gt; are key to that. Stop words reduce a natural phrase to its bare essence: &lt;strong&gt;keywords&lt;/strong&gt;. By dropping such words as “what”, “is”, and “the” from the above query, and leaving only the keywords “best”, “search”, and “engine”, the search engine can match the query to the underlying data in a more reliable and relevant way. &lt;br&gt;
​&lt;br&gt;
Granted, all words are important – “What” and “Why” are indeed meaningful distinctions – but if a search algorithm relies on textual matching (as opposed to matching on meaning or semantics), its only job is to compare characters and words. By removing stop words, therefore, you remove the false positives that match on the word “the”. &lt;br&gt;
​&lt;br&gt;
We can say the same about &lt;strong&gt;normalization&lt;/strong&gt; (e.g., removing accents), &lt;strong&gt;plurals&lt;/strong&gt;. Any search algorithm that focuses on text, and not the meaning of text, should ignore textual variations (like plurals) to enable a more relevant and non-ambiguous word matching.&lt;br&gt;
​&lt;br&gt;
Lastly, textual matching also needs to separate words into useful parts. A “boat house” is not a house or a boat but a boat specially made to be used as a house. To help reach that level of precision, a textual search algorithm needs to break out the constituent parts of a word (atoms), by using techniques like &lt;strong&gt;segmentation&lt;/strong&gt; and &lt;strong&gt;decompounding&lt;/strong&gt;. &lt;br&gt;
​&lt;br&gt;
The goal of segmentation or decompounding is not to understand the meaning of words, but to find out what a complex word can be decomposed into. We’re trying to find the “atoms” of the word. We don’t use it in English because most words are already decompounded, it’s in the language’s DNA. Same for French. But German, for example, &lt;em&gt;Hundehütte&lt;/em&gt;, meaning "dog kennel", is composed of “Hund” (dog) and “Hütte”‘ (kennel/house). The space we already have between the two words in English is why we don’t need decompounding. Segmentation is essentially the same thing, but for languages where there’s no space at all (i.e., most Asian languages).&lt;br&gt;
​&lt;br&gt;
That’s where dictionaries come in.&lt;br&gt;
​&lt;/p&gt;
&lt;h2&gt;
  
  
  Using dictionaries for text-based matching
&lt;/h2&gt;

&lt;p&gt;​&lt;br&gt;
One approach to natural language processing is to use dictionaries, such as a stop-word dictionary, plurals dictionary, and a compound-word dictionary. For example, you can parse a downloaded list of stop words from &lt;a href="https://www.wiktionary.org/"&gt;Wiktionary&lt;/a&gt;, not only in English, but in many other languages.&lt;br&gt;
​&lt;br&gt;
Here’s the process we used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Download the full wiktionary dictionary – words, definitions, and much more&lt;/li&gt;
&lt;li&gt;Extract the words&lt;/li&gt;
&lt;li&gt;Store them in text files&lt;/li&gt;
&lt;li&gt;Compile them into a binary format&lt;/li&gt;
&lt;li&gt;Optimize code for performance
​
We do this for every language and it works fairly well for most use cases. But when it doesn’t work, it breaks relevance – which is a critical show-stopper for search engines. Here are some problems we encountered&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  I’m “down” with relevance
&lt;/h3&gt;

&lt;p&gt;“Down” is a reasonable stop word, except when your searching for “down jackets”. Companies who sell “leather”, “suede”, and “down” jackets cannot remove “down” from the query.&lt;/p&gt;
&lt;h3&gt;
  
  
  Ambiguity cloaked in “fur” (not squirrel fur)
&lt;/h3&gt;

&lt;p&gt;Languages that use accents, like French and Spanish, fare well when normalized with accent removal. For example, “voilà” to “voila” causes no loss in meaning. In fact, it’s rare in French that removing an accent would create an ambiguity. German is not so lucky. For example, the accented “ä”, when normalized to “a”, will change the meaning of some words. &lt;br&gt;
​&lt;br&gt;
A curious example of this is the German word “wählen”, meaning “to choose” in English. If you remove the accent, most people will not object – except for the 1500 residents of the small German-speaking Swiss town, Wahlen. It might be hard to find the town “Wahlen” among the many results that match on “wählen” – thus, hurting tourism in that part of the world. &lt;br&gt;
​&lt;br&gt;
The solution is to do a special &lt;strong&gt;custom normalization&lt;/strong&gt; for german. In this case, normalize “ä” to “ae”. Here’s a complete list:&lt;br&gt;
​&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ä → ae
ö → oe
ü → ue
Ä → Ae
Ö → Oe
Ü → Ue
ß → ss (or SZ for capital)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But this leads to a second problem, which illustrates the gymnastics search engines go through when dealing with languages. (Remember, language is funny…) So we normalize “für” to “fuer”, but now we lose the stop word “fur”, because the now normalized “fuer” is not a stop word.&lt;br&gt;&lt;br&gt;
​&lt;br&gt;
That’s where &lt;strong&gt;&lt;em&gt;custom&lt;/em&gt; dictionaries&lt;/strong&gt; come in.&lt;br&gt;
​&lt;/p&gt;
&lt;h2&gt;
  
  
  The solution – Giving customers control with custom dictionaries
&lt;/h2&gt;

&lt;p&gt;​&lt;br&gt;
We realized that one dictionary per category wasn’t enough, we needed to come up with an additional dictionary per customer that they could use to override the defaults of Wiktionary or add their own words.  So now we have two dictionaries per category (stop words, plurals, etc.): one per language, which we ship out with our software, and one custom dictionary per customer, which they can add words to.  Adding custom dictionaries – meaning, allowing each customer to override and add their own words to our dictionaries – required a bit of refactoring in how we dealt with our standard dictionaries: each dictionary-retrieval function had a different interface and each dictionary dataset had different formats. So the first step was to normalize our code and data.&lt;br&gt;
​&lt;/p&gt;
&lt;h2&gt;
  
  
  Normalizing our codebase and standardizing our dictionaries interface
&lt;/h2&gt;

&lt;p&gt;​&lt;br&gt;
We examined the current dictionaries that we shipped out to our customers as part of our base product. We wanted to abstract the similarity of every dictionary. Since they all had the same kind of data and goals, we were able to do the following: &lt;br&gt;
​&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create similar data = a list of words&lt;/li&gt;
&lt;li&gt;Code the same goal = the ability to retrieve words
​
To put these dictionaries into a single interface, the main tasks included (in this order):
​&lt;/li&gt;
&lt;li&gt;Transforming all the dictionary datasets to have the same data structure: &lt;a href="https://en.wikipedia.org/wiki/Trie"&gt;the trie&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Migrating existing dictionaries to the new format
In the end, our new format for the plurals dictionary file is:
​
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[2-letter country code]=[word1,word2,..]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;​&lt;br&gt;
In keeping with our introduction, here’s a good example of plurals:&lt;br&gt;
​&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;en=feet,feets,foot,foots
en=slipper,slippers
en=squirrel,squirrels
en=fur,furs
en=Cinderella,Cinderellas
en=Cinderfella,Cinderfellas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;​&lt;br&gt;
That’s the first part: unifying both the interface and structure of the data.&lt;br&gt;
​&lt;br&gt;
With that, we achieved the following goals:&lt;br&gt;
​&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A simpler dictionary interface for all dictionaries&lt;/li&gt;
&lt;li&gt;Mutualized toolings and tests&lt;/li&gt;
&lt;li&gt;Easier to maintain&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Plugging in customer-created, custom dictionaries
&lt;/h2&gt;

&lt;p&gt;​&lt;br&gt;
Now that we had a single interface for every dictionary, we were able to integrate customer-defined words for every NLP technique, for example, customer-specific stop words (see “down” example above), customer-specific normalization (see “für” example above), and so on.&lt;br&gt;
​&lt;br&gt;
These custom dictionaries are added to the index on top of the static dictionaries. We prioritized the dictionary lookups: a query first consults the custom dictionary before the static one. If the word is found, then the engine doesn’t need to look at the static dictionary. &lt;br&gt;
​&lt;br&gt;
And that’s it: our customers can now slip on one slipper and help their own customers find the other slipper(s) – in fur or glass.&lt;/p&gt;

</description>
      <category>nlp</category>
      <category>search</category>
    </item>
    <item>
      <title>Pest: a PHP testing framework that goes above and beyond PHPUnit</title>
      <dc:creator>Peter Villani</dc:creator>
      <pubDate>Fri, 18 Feb 2022 08:56:54 +0000</pubDate>
      <link>https://dev.to/algolia/pest-a-php-testing-framework-that-goes-above-and-beyond-phpunit-253h</link>
      <guid>https://dev.to/algolia/pest-a-php-testing-framework-that-goes-above-and-beyond-phpunit-253h</guid>
      <description>&lt;p&gt;When building third-party APIs, it’s important to test your code in every runtime scenario you’ve seen or can foresee. For this, a robust and easy to use &amp;amp; maintain testing framework is a must. As PHP developers, we relied heavily on &lt;a href="https://phpunit.de/" rel="noopener noreferrer"&gt;PHPUnit&lt;/a&gt;, but we switched over to the &lt;a href="https://pestphp.com/" rel="noopener noreferrer"&gt;Pest Testing Framework&lt;/a&gt;, which simplified and reduced our large library of testing code.&lt;/p&gt;

&lt;p&gt;Given the number of tests we perform, and trying to cover every function in our codebase, we needed to simplify the testing code and make it easier to maintain, understand, and debug. We also wanted to make sure our testing framework would require little effort to integrate into &lt;a href="https://laravel.com/" rel="noopener noreferrer"&gt;Laravel&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;In this article, we take a look at Pest, a clean &amp;amp; robust testing framework built on top of PHP’s standard testing library PHPUnit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reducing the testing codebase of PHPUnit with PHP chaining
&lt;/h2&gt;

&lt;p&gt;Inspired in part by the one-line code “it” syntax in &lt;a href="https://relishapp.com/rspec/rspec-core/docs/subject/one-liner-syntax" rel="noopener noreferrer"&gt;Ruby Rspec&lt;/a&gt;, Pest’s own syntax is fairly straightforward. Pest removes the namespace and library references needed in PHPUnit, and it doesn’t require &lt;em&gt;extending&lt;/em&gt; hundreds of functions. It simply needs you to specify the test function – making it far easier to maintain and debug:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;an&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Still, 3 lines of code might be too much when multiplied by 100s of tests. Pest allows you to &lt;em&gt;chain&lt;/em&gt; the functions. The result is a one liner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'is an example'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thus, Pest has reduced the below 8 lines of PHPUnit code to the above 1 line of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Tests\Unit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;PHPUnit\Framework\TestCase&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExampleTest&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;TestCase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/** @test */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;testBasicTest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;        
    &lt;span class="p"&gt;}&lt;/span&gt; 
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Improving the output
&lt;/h2&gt;

&lt;p&gt;PHPUnit offers PHP users the ability to visualize the testing results in a very compact read:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmrd9pu0ptj85vhzhmqgd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmrd9pu0ptj85vhzhmqgd.png" alt="phpunit testing output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While less verbosity is generally good, we felt we needed a bit more. Pest gave us more information per test:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F74kco8e38u6brswu7xaj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F74kco8e38u6brswu7xaj.png" alt="pest testing output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For errors, Pest gives you direct access to the line of code that has failed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvp3wmj0dthzv7lc845lh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvp3wmj0dthzv7lc845lh.png" alt="pest testing output failure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, you can get more info from the &lt;code&gt;coverage&lt;/code&gt; option, if you want to display the number of lines of source code that have been tested and executed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsvmapsudknue2ddkoiyb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsvmapsudknue2ddkoiyb.png" alt="pest testing output verbose"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding scalability with datasets
&lt;/h2&gt;

&lt;p&gt;As with most tests, the quality of the data is key. Along with using realistic and relevant mocked-up content, testing quality data requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Performing multiple tests on small and large variations of data&lt;/li&gt;
&lt;li&gt;Ability to add new use cases without any recoding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With Pest’s “datasets”, you can create one test that takes an inline array of data. So, for example, you can test multiple emails, each one representing a different use case (maybe connectivity):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'has emails'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;not&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toBeEmpty&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
   &lt;span class="s1"&gt;'someone@jmail.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s1"&gt;'other@example.com'&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can use multi-dimensional arrays as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'has emails'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;not&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toBeEmpty&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
   &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Someone'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'someone@jmail.com'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
   &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Other'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'other@example.com'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s how it’s done inline. You can also move the data outside of the test to gain more flexibility, For this, you’ll need the following folder structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;—Tests
—---testemails.php
—Datasets
—---emails.php 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where the test functions go in folder &lt;code&gt;Tests&lt;/code&gt;, and the datasets are defined in individual files in the &lt;code&gt;Datasets&lt;/code&gt; folder. Here’s an example of the contents of the dataset file &lt;code&gt;emails.php&lt;/code&gt;, which replaces the above inline email references:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'emails'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;Return&lt;/span&gt; &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'other@example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'enunomaduro@gmail.com'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And so the test function can now reuse &lt;code&gt;emails.php&lt;/code&gt; for testing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'has emails'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;not&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toBeEmpty&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
   &lt;span class="nf"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'emails'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  And more
&lt;/h2&gt;

&lt;p&gt;There are many additional features and options, as well as a number of assertions, expectations, and exceptions. And don’t forget that Pest is an extension of PHPUnit, so you can perform actions before and after a test, like &lt;code&gt;setup&lt;/code&gt; in PHPUnit.&lt;/p&gt;

&lt;p&gt;Here’s a last example. You can &lt;code&gt;skip&lt;/code&gt; tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'has home'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="c1"&gt;// ..&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;skip&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;skip&lt;/code&gt; function shows you how Pest really thinks about the tester and simplifies the important things. I’d advise you not to “skip” on Pest – it’s really a great addition to the PHP ecosystem.&lt;/p&gt;

&lt;p&gt;Learn more about &lt;a href="https://pestphp.com/" rel="noopener noreferrer"&gt;Pest&lt;/a&gt;, built by &lt;a href="https://twitter.com/enunomaduro" rel="noopener noreferrer"&gt;Nuno Maduro&lt;/a&gt; from &lt;a href="https://github.com/laravel" rel="noopener noreferrer"&gt;@laravel&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>php</category>
      <category>phpunit</category>
      <category>laravel</category>
    </item>
  </channel>
</rss>
