DEV Community

Cover image for Need search for your website? THIS is the easiest way to do it!
Savvas Stephanides
Savvas Stephanides

Posted on • Updated on • Originally published at savvas.me

Need search for your website? THIS is the easiest way to do it!

Do you have a website with loads of data? Chances are your users will have a difficult time finding what they need unless you have a search box they can use. But that sounds like a difficult task! What if I told you there's a fast and easy way to add powerful search functionality to your website?

Introducing Lunr

Lunr website homepage

Lunr is a Javascript library that makes it easy to add search to your website. So how does it work?

In this article, we're going to use Lunr with vanilla JS (ie. no React or any other library is needed to follow along).

πŸ‘‰ The full code for this tutorial is available here

πŸ‘‰ Fully working demo available here

1. Let's create our html file

First things first, let's create our HTML file. Create a new directory for your project and in that, create a file called index.html. It can be a simple html file with just the basic code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

2. Let's add some data

Then, let's add some data Lunr can query so it can give us some search results back. In your new directory, create a file called post-data.js which contains some blog post data:

  • the path (or slug)
  • the title of your post
  • a brief description
var data = [
    {
        path: "intro-to-js",
        title: "Introduction to Javascript",
        description: "This is a beginner's introduction to Javascript"
    },
    {
        path: "add-search-to-your-website",
        title: "How to add search to your website",
        description: "This Javascript library is the easiest way to add powerful search to your website"
    },
    {
        path: "git-cheat-sheet",
        title: "Git Cheat Sheet",
        description: "Git is the most popular version control system out there. Here is a list of the most useful commands"
    },
    {
        path: "javascript-promises",
        title: "What are Promises in Javascript?",
        description: "Promises are a great tool in Javascript for making external requests without freezing your browser"
    },
    {
        path: "rest-api-expressjs-tutorial",
        title: "Building a RESTful API with ExpressJS",
        description: "APIs are everywhere. Let's build one with ExpressJS"
    },
    {
        path: "framework-or-library",
        title: "Is this Javascript tool a library or framework? The answer will blow your mind!",
        description: "Is it a framework? Is it a library? THE WORLD MUST KNOW!"
    }
]
Enter fullscreen mode Exit fullscreen mode

You can now add this data to your index.html file. Add them using the <script> tag:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
+    <script src="post-data.js"></script>
</head>
<body>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

3. Get Lunr to your project

Next, we will import Lunr to our project. To do this, the quickest way is to use a <script> tag:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="post-data.js"></script>
+    <script src="https://unpkg.com/lunr/lunr.js"></script>
</head>
<body>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Note: If you're using Node you can install Lunr with NPM instead (not needed for this tutorial):

npm install lunr
Enter fullscreen mode Exit fullscreen mode

4. Give the data to Lunr

The next step is to tell Lunr what we want it to search for. To do this we will need to create a "search index", which is basically a lunr object.

4.1. Create a new file

Firstly, go ahead and create a new file called search.js in the same directory.

4.2. Create a lunr object

Create a variable in that file called index, which is a new lunr object. The object itself expects a function as a single argument. Let's add an empty function which we'll populate in the next steps:

var index = lunr(function () {
})
Enter fullscreen mode Exit fullscreen mode

4.3. Tell Lunr what fields to search for

Next, we'll tell Lunr which fields from our data to look at when performing our searches. For our data, we want Lunr to perform searches on the title and description fields. Let's update our index object to reflect this:

var index = lunr(function () {
+    this.field('title')
+    this.field('description')
})
Enter fullscreen mode Exit fullscreen mode

4.4. Tell Lunr what to give us back when it finds results

When Lunr gives you back results it will give back some text for each result as a "reference". This is usually an ID or, in our case, the path of each post.

We can tell Lunr what our reference for each post is like so:

var index = lunr(function () {
    this.field('title')
    this.field('description')
+    this.ref('path')
})
Enter fullscreen mode Exit fullscreen mode

4.5. Add all our posts to the Lunr index

Finally, we can add our post data for Lunr to index using the settings we specified above:

var index = lunr(function () {
    this.field('title')
    this.field('description')
    this.ref('path')


+    data.forEach(function (post) {
+        this.add(post)
+    }, this)
})
Enter fullscreen mode Exit fullscreen mode

4.6. Add our search index to our html file

Our search index has been created successfully in our search.js file. The final step is to add our file to our index.html file:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/lunr/lunr.js"></script>
    <script src="post-data.js"></script>
+    <script src="search.js"></script>
</head>
<body>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

πŸŽ‰πŸŽ‰πŸŽ‰ That's it! Your website is ready to perform searches!

5. Let's do a few searches!

Now that we have Lunr up and running on our website, let's perform a few searches on our posts. We can do that with the search() function of the Lunr index object.

For example: perform a search with "git":

var searchResponse = index.search("git")
Enter fullscreen mode Exit fullscreen mode

The response will be something like this:

console.log(searchResponse)
Enter fullscreen mode Exit fullscreen mode
[
    {
        "ref": "git-cheat-sheet",
        "score": 1.96,
        "matchData": {
            "metadata": {
                "git": {
                    "title": {},
                    "description": {}
                }
            }
        }
    }
]
Enter fullscreen mode Exit fullscreen mode

Since our data has only one post about Git, it will give us back one record.

Two main things to notice here:

  1. The "reference" we told Lunr to give us is right there in the response as ref
  2. A score value. This is a value of how relevant each post is to the search query you provided. This helps in sorting your posts by relevance.

Let's perform another search. This time for "javascript":

var searchResponse = index.search("javascript")
console.log(searchResponse)
Enter fullscreen mode Exit fullscreen mode

Result:

[
    {
        "ref": "intro-to-js",
        "score": 0.186,
        "matchData": {
            "metadata": {
                "javascript": {
                    "title": {},
                    "description": {}
                }
            }
        }
    },
    {
        "ref": "javascript-promises",
        "score": 0.152,
        "matchData": {
            "metadata": {
                "javascript": {
                    "title": {},
                    "description": {}
                }
            }
        }
    },
    {
        "ref": "add-search-to-your-website",
        "score": 0.069,
        "matchData": {
            "metadata": {
                "javascript": {
                    "description": {}
                }
            }
        }
    },
    {
        "ref": "framework-or-library",
        "score": 0.053,
        "matchData": {
            "metadata": {
                "javascript": {
                    "title": {}
                }
            }
        }
    }
]
Enter fullscreen mode Exit fullscreen mode

This time we have 4 posts relevant to "javascript". Notice how the results are sorted by score. This means that we know which posts are the most relevant so we can display them on top of the other results.

This is one of the things that makes Lunr so powerful!

6. Let's get the actual posts

The results we got are great and all, but there's a problem. A single ref string means nothing when we want to show the search results to our users.

To get the actual posts, we'll need to do two things:

Extract the paths from all posts using map():

var postPaths = searchResponse.map((item) => item.ref)
Enter fullscreen mode Exit fullscreen mode

For each path, get the equivalent post from our data using map() and find():

var results = postPaths.map((path) => data.find((post) => post.path === path))
Enter fullscreen mode Exit fullscreen mode

The result should now be this:

[
    {
        "path": "intro-to-js",
        "title": "Introduction to Javascript",
        "description": "This is a beginner's introduction to Javascript"
    },
    {
        "path": "javascript-promises",
        "title": "What are Promises in Javascript?",
        "description": "Promises are a great tool in Javascript for making external requests without freezing your browser"
    },
    {
        "path": "add-search-to-your-website",
        "title": "How to add search to your website",
        "description": "This Javascript library is the easiest way to add powerful search to your website"
    },
    {
        "path": "framework-or-library",
        "title": "Is this Javascript tool a library or framework? The answer will blow your mind!",
        "description": "Is it a framework? Is it a library? THE WORLD MUST KNOW!"
    }
]
Enter fullscreen mode Exit fullscreen mode

NOW we're getting somewhere!

7. Let's update our website!

Now that we have everything set up and we know how to run search queries, let's make something useful from it. We're going to update our index.html file to include:

  • A search box
  • A button
  • The search results below

Let's start with a <div> to wrap our whole app:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/lunr/lunr.js"></script>
    <script src="post-data.js"></script>
    <script src="search.js"></script>
</head>
<body>
+    <div id="app">
+        
+    </div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Now let's add our input box (with a label) and a Search button:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/lunr/lunr.js"></script>
    <script src="post-data.js"></script>
    <script src="search.js"></script>
</head>
<body>
    <div id="app">
+        <label for="search-box">Search for a post:</label>
+        <input type="text" id="search-box">
+        <button>Search</button>
    </div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Your index.html file should now look like this in a browser:

Your homepage in a Chrome browser

Lastly, we're going to add an empty list <ul> which we will dynamically fill with our search results:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/lunr/lunr.js"></script>
    <script src="post-data.js"></script>
    <script src="search.js"></script>
</head>
<body>
    <div id="app">
        <label for="search-box">Search for a post:</label>
        <input type="text" id="search-box">
        <button>Search</button>
+        <ul id="search-results"></ul>
    </div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Now we're going to create a function which gets executed when a user clicks on the Search box. Let's call it respondToSearchboxClick().

We'll also use the onclick attribute of our button to execute our function whenever the button is clicked:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/lunr/lunr.js"></script>
    <script src="post-data.js"></script>
    <script src="search.js"></script>
+    <script>
+        function respondToSearchboxClick(){
+            
+        }
+    </script>
</head>
<body>
    <div id="app">
        <label for="search-box">Search for a post:</label>
        <input type="text" id="search-box">
-        <button>Search</button>
+        <button onclick="respondToSearchboxClick()">Search</button>

        <ul id="search-results">

        </ul>
    </div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Now we just need to complete our function with the search logic we described above:

  • Get the text from the search box
  • Perform a search
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/lunr/lunr.js"></script>
    <script src="post-data.js"></script>
    <script src="search.js"></script>
    <script>
        function respondToSearchboxClick(){
+            var query = document.querySelector("#search-box").value
+            var searchResponse = index.search(query)
+            var postPaths = searchResponse.map((item) => item.ref)
+            var results = postPaths.map((path) => data.find((post) => post.path === path))
        }
    </script>
</head>
<body>
    <div id="app">
        <label for="search-box">Search for a post:</label>
        <input type="text" id="search-box">
        <button onclick="respondToSearchboxClick()">Search</button>

        <ul id="search-results">

        </ul>
    </div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Lastly, for each result, use the map() function to create a list item (<li>) and display it in our #search-results list:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/lunr/lunr.js"></script>
    <script src="post-data.js"></script>
    <script src="search.js"></script>
    <script>
        function respondToSearchboxClick(){
            var query = document.querySelector("#search-box").value
            var searchResponse = index.search(query)
            var postPaths = searchResponse.map((item) => item.ref)
            var results = postPaths.map((path) => data.find((post) => post.path === path))
+
+            document.querySelector("#search-results").innerHTML = results.map((post) => {
+                return `
+                <li><a href="/${post.path}">${post.title}</a></li>
+                `
+            }).join("")
        }
    </script>
</head>
<body>
    <div id="app">
        <label for="search-box">Search for a post:</label>
        <input type="text" id="search-box">
        <button onclick="respondToSearchboxClick()">Search</button>

        <ul id="search-results">

        </ul>
    </div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

That's it! Congratulations! You now have search in your website! Let's give it a try:

"git":

Search results for 'Git'

"javascript":

Search results for 'Javascript'

"library":

Search results for 'library'

πŸŽ‰πŸŽ‰πŸŽ‰ Your search engine is good to go! Go ahead and create your own.

Let me know:

  • What have you built with this tutorial?
  • What have you learned from it?
  • What problems have you faced?

Top comments (10)

Collapse
 
epsi profile image
E.R. Nurwijayadi

You do not need lunr. Simple fetch from whatwg is enough.

πŸ•· epsi-rns.gitlab.io/ssg/2020/10/01/...

Native SSG Search

Collapse
 
savvasstephnds profile image
Savvas Stephanides

Lunr offers much more powerful tools which make searching better, like weighing and scoring. Your solution is OK, however I get results on Javascript when I search for β€œJava”

Collapse
 
epsi profile image
E.R. Nurwijayadi

Thank you for reply.

That is true, for better feature, we can utilize library.

Collapse
 
phlash profile image
Phil Ashby

Thanks Savvas, this could be useful for those sites that must have their own search (eg: non-public pages, security restrictions such as those in PCI-DSS), rather than making themselves nicely indexable by popular search tools :)

Personally - if you are operating a public site, then publishing a sitemap is really effective!

Collapse
 
promikecoder2020 profile image
ProMikeCoder2020

I have googled a bit and i dont seem to find an article that explains how to automatically let google search your site data like that. Could you provide any resource to learn that?

Collapse
 
phlash profile image
Phil Ashby • Edited

Duckduckgo have a specific page to create a custom search: duckduckgo.com/search_box
Google provide their custom search tooling here: programmablesearchengine.google.co... and have a help page on why sitemaps are useful here: developers.google.com/search/docs/...

Hope this helps!

Thread Thread
 
promikecoder2020 profile image
ProMikeCoder2020

Thanks!

Collapse
 
nithish_13 profile image
Nithish Kumar

Thanks for introducing this library. But there is an another cool Library called fuse.js πŸ™‚πŸ™‚

Collapse
 
savvasstephnds profile image
Savvas Stephanides

Thanks Nithish. Sounds like a good alternative!

Collapse
 
epsi profile image
E.R. Nurwijayadi

Thank youuuuu.