docs-site/content/guide/vanilla-js-search-bar.md
You don't need a fancy framework to work with Typesense. This walkthrough will take you through all the steps required to build a simple book search application using Vanilla JavaScript and the Typesense ecosystem.
Typesense is a fast, typo-tolerant search engine that you can use to add search functionality to your applications. Think of it like having your own mini Google, but for your data.
Here's a simple example: imagine you're building a bookstore website with thousands of books. Without a search engine, users would have to scroll through endless pages to find what they want. With Typesense, they can simply type "hary poter" (yes, with typos!) and still find "Harry Potter" instantly.
What makes Typesense special:
This guide will use Vite, a modern build tool that provides a fast development experience for vanilla JavaScript projects.
Please ensure you have Node.js and Docker installed on your machine before proceeding. You will need it to run a typesense server locally and load it with some data. This will be used as a backend for this project.
This guide will use a Linux environment, but you can adapt the commands to your operating system.
Once Docker is installed, you can run a Typesense container in the background using the following commands:
Create a folder that will store all searchable data stored for Typesense:
mkdir "$(pwd)"/typesense-data
Run the Docker container:
<Tabs :tabs="['Shell']"> <template v-slot:Shell> <div class="manual-highlight"> <pre class="language-bash"><code>export TYPESENSE_API_KEY=xyz docker run -p 8108:8108 \ -v"$(pwd)"/typesense-data:/data typesense/typesense:{{ $site.themeConfig.typesenseLatestVersion }} \ --data-dir /data \ --api-key=$TYPESENSE_API_KEY \ --enable-cors \ -d</code></pre> </div> </template> </Tabs>Verify if your Docker container was created properly:
docker ps
You should see the Typesense container running without any issues:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
82dd6bdfaf66 typesense/typesense:latest "/opt/typesense-serv…" 1 min ago Up 1 minutes 0.0.0.0:8108->8108/tcp, [::]:8108->8108/tcp nostalgic_babbage
That's it! You are now ready to create collections and load data into your Typesense server.
:::tip You can also set up a managed Typesense cluster on Typesense Cloud for a fully managed experience with a management UI, high availability, globally distributed search nodes and more. :::
Typesense needs you to create a <RouterLink :to="`/${$site.themeConfig.typesenseLatestVersion}/api/collections.html`">collection</RouterLink> in order to search through documents. A collection is a named container that defines a schema and stores indexed documents for search. Collection bundles three things together:
You can create the books collection for this project using this curl command:
curl "http://localhost:8108/collections" \
-X POST \
-H "Content-Type: application/json" \
-H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
-d '{
"name": "books",
"fields": [
{"name": "title", "type": "string", "facet": false},
{"name": "authors", "type": "string[]", "facet": true},
{"name": "publication_year", "type": "int32", "facet": true},
{"name": "average_rating", "type": "float", "facet": true},
{"name": "image_url", "type": "string", "facet": false},
{"name": "ratings_count", "type": "int32", "facet": true}
],
"default_sorting_field": "ratings_count"
}'
Now that the collection is set up, we can load the sample dataset.
Download the sample dataset:
curl -O https://dl.typesense.org/datasets/books.jsonl.gz
Unzip the dataset:
gunzip books.jsonl.gz
Load the dataset in to Typesense:
curl "http://localhost:8108/collections/books/documents/import" \
-X POST \
-H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
--data-binary @books.jsonl
You should see a bunch of success messages if the data load is successful.
Now you're ready to actually build the application.
Create a new Vite project using this command:
npm create vite@latest typesense-vanilla-js-search -- --template vanilla
This will scaffold a new vanilla JavaScript project with Vite.
Once your project scaffolding is ready, navigate to the project directory and install these three dependencies that will help you with implementing the search functionality:
cd typesense-vanilla-js-search
npm install
npm i typesense typesense-instantsearch-adapter instantsearch.js
Let's go over these dependencies one by one:
searchBox, hits, stats and others that make displaying search results easy.instantsearch.js and our self-hosted Typesense server.InstantSearch.js adapter that instantsearch.js expects.InstantSearch.js queries to Typesense API calls.Let's create the project structure step by step. After each step, we'll show you how the directory structure evolves.
After creating the basic Vite app and installing the required dependencies, your project structure should look like this:
typesense-vanilla-js-search/
├── node_modules/
├── public/
│ └── vite.svg
├── src/
│ ├── main.js
│ └── style.css
├── .gitignore
├── index.html
├── package-lock.json
└── package.json
Create the typesense.js file in the src directory:
touch src/typesense.js
Your project structure should now look like this:
typesense-vanilla-js-search/
├── public/
│ └── vite.svg
├── src/
│ ├── main.js
│ ├── style.css
│ └── typesense.js
├── .gitignore
├── index.html
├── package-lock.json
└── package.json
Copy this code into src/typesense.js:
import TypesenseInstantSearchAdapter from "typesense-instantsearch-adapter";
export const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter({
server: {
apiKey: import.meta.env.VITE_TYPESENSE_API_KEY || "xyz",
nodes: [
{
host: import.meta.env.VITE_TYPESENSE_HOST || "localhost",
port: Number(import.meta.env.VITE_TYPESENSE_PORT) || 8108,
protocol: import.meta.env.VITE_TYPESENSE_PROTOCOL || "http",
},
],
},
additionalSearchParameters: {
query_by: "title,authors",
},
});
This config file creates a reusable adapter that connects your application to your Typesense Backend. It can take in a bunch of additional search parameters like sort by, number of typos, etc.
:::tip
Note that Vite uses import.meta.env for environment variables, and public variables must be prefixed with VITE_.
:::
Update the index.html file with the search UI structure:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Book Search | Typesense + Vanilla JS</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/instantsearch.css@8/themes/satellite-min.css" />
</head>
<body>
<div id="app">
<header class="header">
<h1>Vanilla JS Search Bar</h1>
<p class="subtitle">
powered by
<a href="https://typesense.org/" target="_blank" rel="noopener noreferrer">
type<strong>sense</strong>|
</a>
</p>
</header>
<main class="search-container">
<div class="search-box-container">
<div id="searchbox"></div>
</div>
<div id="stats" class="results-count"></div>
<div id="hits" class="book-grid"></div>
</main>
</div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
The HTML structure provides container elements (#searchbox, #stats, #hits) that the InstantSearch widgets will mount to.
Now let's implement the main search functionality. Replace the contents of src/main.js with:
:::tip Note Since styling is not the main focus of this guide, we've excluded the CSS. You can find the complete stylesheet in the source code. :::
import './style.css';
import { typesenseInstantsearchAdapter } from './typesense.js';
import instantsearch from 'instantsearch.js';
import { searchBox, hits, stats, configure } from 'instantsearch.js/es/widgets';
const search = instantsearch({
indexName: 'books',
searchClient: typesenseInstantsearchAdapter.searchClient,
future: {
preserveSharedStateOnUnmount: true,
},
});
search.addWidgets([
configure({
hitsPerPage: 12,
}),
searchBox({
container: '#searchbox',
placeholder: 'Search by title or author...',
showReset: true,
showSubmit: true,
cssClasses: {
form: 'search-form',
input: 'search-input',
submit: 'search-submit',
reset: 'search-reset',
},
}),
stats({
container: '#stats',
templates: {
text(data, { html }) {
if (data.hasManyResults) {
return html`${data.nbHits.toLocaleString()} results found`;
} else if (data.hasOneResult) {
return html`1 result found`;
} else {
return html`No results found`;
}
},
},
}),
hits({
container: '#hits',
templates: {
item(hit, { html, components }) {
const stars = '★'.repeat(Math.round(hit.average_rating || 0));
return html`
<div class="book-card">
${hit.image_url ? html`
<div class="book-image-container">
</div>
` : ''}
<div class="book-info">
<h3 class="book-title">${components.Highlight({ attribute: 'title', hit })}</h3>
<p class="book-author">${hit.authors?.join(', ') || 'Unknown Author'}</p>
<div class="rating-container">
<span class="star-rating">${stars}</span>
<span class="rating-text">
${hit.average_rating?.toFixed(1) || '0'} (${hit.ratings_count?.toLocaleString() || 0} ratings)
</span>
</div>
${hit.publication_year ? html`<p class="book-year">Published: ${hit.publication_year}</p>` : ''}
</div>
</div>
`;
},
empty(results, { html }) {
return html`
<div class="no-results">
<h3>No books found</h3>
<p>Try adjusting your search or try different keywords.</p>
</div>
`;
},
},
}),
]);
search.start();
Unlike React-based frameworks, vanilla JavaScript with InstantSearch uses a widget-based architecture where each widget mounts to a DOM element by its container selector. Let's break down the key parts:
instantsearch() - Creates the main search instance, connecting to our Typesense backend via the adapter.configure() - Sets global search parameters like hitsPerPage.searchBox() - Renders the search input field. The widget automatically handles complex parts like debouncing and state management.stats() - Displays the number of results found using a custom template.hits() - Renders the search results using custom HTML templates with the html tagged template literal.The components.Highlight() function is provided by InstantSearch to highlight matching text in the search results.
Your final project structure should now look like this:
typesense-vanilla-js-search/
├── public/
│ └── vite.svg
├── src/
│ ├── main.js
│ ├── style.css
│ └── typesense.js
├── .gitignore
├── index.html
├── package-lock.json
└── package.json
Running the application using Vite:
npm run dev
This will start a development server and open the application in your default browser in http://localhost:5173.
That's it! You've successfully built a search interface with vanilla JavaScript and Typesense!
If you prefer not to use Vite or npm/yarn, you can load the libraries directly from CDN and use plain HTML and JavaScript files. Here's a minimal working example:
<!DOCTYPE html>
<html>
<head>
<title>Simple Typesense Search</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/instantsearch.css@7/themes/algolia-min.css" />
</head>
<body>
<div id="searchbox"></div>
<div id="hits"></div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
<script src="https://cdn.jsdelivr.net/npm/typesense-instantsearch-adapter@2/dist/typesense-instantsearch-adapter.min.js"></script>
<script>
const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter({
server: {
apiKey: 'xyz',
nodes: [
{
host: 'localhost',
port: '8108',
protocol: 'http',
},
],
},
additionalSearchParameters: {
queryBy: 'title,authors',
},
})
const searchClient = typesenseInstantsearchAdapter.searchClient
const search = instantsearch({
searchClient,
indexName: 'books',
})
search.addWidgets([
instantsearch.widgets.searchBox({
container: '#searchbox',
}),
instantsearch.widgets.hits({
container: '#hits',
templates: {
item: hit => `
<div>
<h3>${hit.title}</h3>
<p>${hit.authors?.join(', ')}</p>
</div>
`,
},
}),
])
search.start()
</script>
</body>
</html>
Just save this as index.html and open it in your browser. It will work as long as you have a Typesense server running on localhost:8108.
Here's how the final output should look like:
Here's the complete source code for this project on GitHub:
https://github.com/typesense/code-samples/tree/master/typesense-vanilla-js-search
Here's another related example that shows you how to build a search bar with vanilla JavaScript:
E-commerce Store with InstantSearch.js
Read our Help section for information on how to get additional help.