Making a search bar for blogposts

Using the search API

AtomicServer comes with a fast full-text search API out of the box. @tomic/lib provides some convenient helper functions on Store to make using this API very easy.

To use search all you need to do is:

const results = await'how to make icecream');

The method returns an array of subjects of resources that match the given query.

To further refine the query, we can pass a filter object to the method like so:

const results = await'how to make icecream', {
  filters: {
    []: myPortfolio.classes.blogpost,

This way the result will only include resources that have an is-a of blogpost.

Running code on the client

To make a working search bar, we will have to run code on the client. Astro code only runs on the server but there are a few ways to have code run on the client. The most commonly used option would be to use a frontend framework like React or Svelte but Astro also allows script tags to be added to components that will be included in the <head /> of the page.

To keep this guide framework-agnostic we will use a script tag and a web component but feel free to use any framework you're more comfortable with, the code should be simple enough to adapt to different frameworks.

First, we need to make a change to our environment variables because right now they are not available to the client and therefore getStore will not be able to access ATOMIC_SERVER_URL. To make an environment variable accessible to the client it needs to be prefixed with PUBLIC_.


// .env

Now update src/helpers/getStore.ts to reflect the name change.

// src/helpers/getStore.ts
import { Store } from '@tomic/lib';
import { initOntologies } from '../ontologies';

let store: Store;

export function getStore(): Store {
  if (!store) {
    store = new Store({
      serverUrl: import.meta.env.PUBLIC_ATOMIC_SERVER_URL,


  return store;

In src/components create a file called Search.astro.


  import { getStore } from '../../helpers/getStore';
  import { core } from '@tomic/lib';
  import { myPortfolio, type Blogpost } from '../../ontologies/myPortfolio';

  class BlogSearch extends HTMLElement {
    // Get access to the store. (Since this runs on the client a new instance will be created)
    private store = getStore();
    // Create an element to store the results in
    private resultsElement = document.createElement('div');

    // Runs when the element is mounted.
    constructor() {

      // We create an input element and add a listener to it that will trigger a search.
      const input = document.createElement('input');
      input.placeholder = 'Search...';
      input.type = 'search';

      input.addEventListener('input', (e) => {

      // Add the input and result list elements to the root of our webcomponent.
      this.append(input, this.resultsElement);

     * Search for blog posts using the given query and display the results.
    private async searchAndDisplay(query: string) {
      if (!query) {
        // Clear the results of the previous search.
        this.resultsElement.innerHTML = '';

      const results = await, {
        filters: {
          []: myPortfolio.classes.blogpost,

      // Map the result subjects to elements.
      const elements = await Promise.all( => this.createResultItem(s)),

      // Clear the results of the previous search.
      this.resultsElement.innerHTML = '';

      // Append the new results to the result list.

     * Create a result link for the given blog post.
    private async createResultItem(subject: string): Promise<HTMLAnchorElement> {
      const post = await<Blogpost>(subject);

      const resultLine = document.createElement('a');
      resultLine.innerText = post.title; = 'block';
      resultLine.href = `/blog/${post.props.titleSlug}`;

      return resultLine;

  // Register the custom element.
  customElements.define('blog-search', BlogSearch);

If you've never seen web components before, <blog-search> is our custom element that starts as just an empty shell. We then add a <script> that Astro will add to the head of our HTML. In this script, we define the class that handles how to render the <blog-search> element. At the end of the script, we register the custom element class.

Eventhough the server will most likely keep up with this many requests, lower end devices might not so it's still a good idea to add some kind of debounce to your searchbar.

Now all that's left to do is use the component to the blog page.

// src/pages/blog/index.astro

<Layout resource={homepage}>
  <h2>Blog 😁</h2>
+ <Search />
    { => (
          <BlogCard subject={post} />

And there it is! A working real-time search bar 🎉

The end, what's next?

That's all for this guide. Some things you could consider adding next if you liked working with AtomicServer and want to continue building this portfolio:

  • Add some more styling
  • Add some interactive client components using one of many Astro integrations (Consider checking @tomic/react or @tomic/svelte)
  • Do some SEO optimisation by adding meta tags to your Layout.astro.