Saturday, February 25, 2023

Autocomplete with Redis Search

Summary

In this post, I'm going demonstrate a real-world usage scenario of one of the features of Redis Search: Suggestion (aka autocomplete).  This particular autocomplete scenario is around addresses, similar to what you see in Google Maps.  I pull real address data from a Canadian government statistics site and populate Redis suggestion dictionaries for autocomplete of either full address (with a street number) or just street name.  The subsequent address chosen by the user is then put into a full Redis search for an exact match.

Architecture


Application

I wrote this app completely in Javascript:  front and back end.  

Front End

The front end is a static web page with a single text input.  The expected input is either a street name or house number + street name.  The page leverages this Javascript autocomplete input module.  The module generates REST calls to the back end.  Screenshot below:



Back End

The back end consists of two Nodejs files:  dataLoader.js and app.js.  The dataLoader module handles fetching data from the Canadian gov site and loading it into Redis as JSON objects.  Additionally, it sets up two suggestion dictionaries: one that includes the street number with the address and another that does not.  Snippet below of the Redis client actions.
    async #insert(client, doc) {
        await client.json.set(`account:${doc.id}`, '.', doc);

        const addr = doc.address;
        if (addr) {
            await client.ft.sugAdd(`fAdd`, addr, 1);
            await client.ft.sugAdd(`pAdd`, addr.substr(addr.indexOf(' ') + 1), 1);
        }
    }
App.js is an ExpressJS-based REST API server.  It exposes a couple GET endpoints: one for address suggestions and the other for a full-text search of an address.  A snippet of the address suggest endpoint below.

app.get('/address/suggest', async (req, res) => {
    const address = decodeURI(req.query.address);
    console.log(`app - GET /address/suggest ${address}`);
   
    try {
        let addrs;
        if (address.match(/^\d/)) {
            addrs = await client.ft.sugGet(`fAdd`, address);
        }
        else {
            addrs = await client.ft.sugGet(`pAdd`, address);
        }
        let suggestions = []
        for (const addr of addrs) {
            suggestions.push({address: addr})
        }
        res.status(200).json(suggestions);
    }
    catch (err) {
        console.error(`app - GET /address/suggest ${req.query.address} - ${err.message}`)
        res.status(400).json({ 'error': err.message });
    }
});

Source


Copyright ©1993-2024 Joey E Whelan, All rights reserved.