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 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>
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!"
}
]
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>
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>
Note: If you're using Node you can install Lunr with NPM instead (not needed for this tutorial):
npm install lunr
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 () {
})
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')
})
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')
})
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)
})
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>
๐๐๐ 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")
The response will be something like this:
console.log(searchResponse)
[
{
"ref": "git-cheat-sheet",
"score": 1.96,
"matchData": {
"metadata": {
"git": {
"title": {},
"description": {}
}
}
}
}
]
Since our data has only one post about Git, it will give us back one record.
Two main things to notice here:
- The "reference" we told Lunr to give us is right there in the response as
ref
- 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)
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": {}
}
}
}
}
]
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 path
s from all posts using map()
:
var postPaths = searchResponse.map((item) => item.ref)
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))
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!"
}
]
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>
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>
Your index.html
file should now look like this in a 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>
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>
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>
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>
That's it! Congratulations! You now have search in your website! Let's give it a try:
"git":
"javascript":
"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)
You do not need
lunr
. Simplefetch
from whatwg is enough.๐ท epsi-rns.gitlab.io/ssg/2020/10/01/...
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โ
Thank you for reply.
That is true, for better feature, we can utilize library.
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!
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?
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!
Thanks!
Thanks for introducing this library. But there is an another cool Library called fuse.js ๐๐
Thanks Nithish. Sounds like a good alternative!
Thank youuuuu.