# Getting Started with Meilisearch

> Build instant, typo-tolerant search with Meilisearch and TypeScript. Learn why database queries fail on misspelled input and how Meilisearch fixes that fast.

Published: 2026-03-06
Canonical: https://layerbase.com/blog/getting-started-with-meilisearch

Short version: `npm i -g layerbase` then `lbase create meili1 -e meilisearch --start` puts a Meilisearch server on your machine in about half a minute, no Docker. For a managed one, pick Meilisearch on [Layerbase Cloud](https://cloud.layerbase.com/create?engine=meilisearch) and take the host and API key from Quick Connect; it sits on the Pro plan rather than the free tier.

Every database can search. Run `WHERE title LIKE '%redemption%'` and you'll find The Shawshank Redemption. But what happens when a user types "shawshnk rdmption"? Nothing. Traditional databases match characters, not intent. One typo, zero results.

Meilisearch fills that gap. It's a search engine built for the moment a user starts typing: typo-tolerant, prefix-aware, and fast enough to return results on every keystroke. Search for "interst" and get Interstellar. Search for "shawshnk rdmption" and get exactly the right movie. No special syntax, no fuzzy matching operators, no configuration.

Everything below fits in one TypeScript file. Run it against a local Meilisearch instance, or point it at [Layerbase Cloud](https://cloud.layerbase.com/create?engine=meilisearch) if you'd rather skip the local install.

## Contents

- [Create a Meilisearch Instance](#create-a-meilisearch-instance)
  - [Local with the Layerbase CLI](#local-with-the-layerbase-cli)
  - [Layerbase Cloud](#layerbase-cloud)
- [Set Up the Project](#set-up-the-project)
- [The Movie Dataset](#the-movie-dataset)
- [Create an Index and Add Documents](#create-an-index-and-add-documents)
- [Configure Filterable and Sortable Attributes](#configure-filterable-and-sortable-attributes)
- [Typo-Tolerant Search](#typo-tolerant-search)
- [Faceted Search](#faceted-search)
- [When to Reach for Meilisearch](#when-to-reach-for-meilisearch)
- [FAQ](#faq)

## Create a Meilisearch Instance

### Local with the Layerbase CLI

[The Layerbase CLI](/docs/cli) is the quickest path to a local Meilisearch instance: one CLI, no Docker, no manual downloads. ([What is the Layerbase CLI?](https://layerbase.com/docs/cli))

Install the Layerbase CLI globally:

```bash
npm i -g layerbase    # npm
pnpm add -g layerbase # pnpm
```

Or run it directly without installing:

```bash
npx layerbase create meili1 -e meilisearch --start  # npm
pnpx layerbase create meili1 -e meilisearch --start # pnpm
```

If you installed globally, create and start a Meilisearch instance:

```bash
lbase create meili1 -e meilisearch --start
```

The Layerbase CLI downloads the Meilisearch binary for your platform, configures it, and starts the server. Verify it's running:

```bash
lbase url meili1
```

```text
http://127.0.0.1:7700
```

Leave the server running. We'll connect to it from TypeScript in the next section.

### Layerbase Cloud

Prefer managed? [Layerbase Cloud](https://cloud.layerbase.com/create?engine=meilisearch) spins one up in seconds. You'll get a connection URL and API key from the dashboard's Quick Connect panel.

Cloud instances use TLS, so the connection code is slightly different:

```typescript
const client = new Meilisearch({
  host: 'https://cloud.layerbase.dev:PORT',
  apiKey: 'YOUR_API_KEY',
})
```

Replace `PORT` and `YOUR_API_KEY` with the values from your Quick Connect panel. Everything else in this guide works the same locally or on Cloud.

## Set Up the Project

```bash
mkdir meilisearch-movie-search && cd meilisearch-movie-search
pnpm init
pnpm add meilisearch
pnpm add -D tsx typescript
```

Create a file called `search.ts`. All the code in this post goes into that one file.

## The Movie Dataset

Here are 15 movies with genres and descriptions. In production this would come from your database or API. The titles and descriptions are what Meilisearch will index and search against:

```typescript
const movies = [
  {
    id: 1,
    title: 'Alien',
    year: 1979,
    genres: ['sci-fi', 'horror'],
    description:
      'The crew of a deep-space mining ship encounters a deadly extraterrestrial creature that stalks them through narrow corridors.',
  },
  {
    id: 2,
    title: 'The Shawshank Redemption',
    year: 1994,
    genres: ['drama'],
    description:
      'A banker wrongly imprisoned for murder befriends a fellow inmate and uses patience, cunning, and a rock hammer to find freedom.',
  },
  {
    id: 3,
    title: 'Inception',
    year: 2010,
    genres: ['sci-fi', 'thriller'],
    description:
      "A thief who enters people's dreams takes on the job of planting an idea in a CEO's mind, navigating increasingly unstable layers of consciousness.",
  },
  {
    id: 4,
    title: 'The Intouchables',
    year: 2011,
    genres: ['comedy', 'drama'],
    description:
      'A wealthy quadriplegic hires an unlikely caretaker from the projects, and they form a life-changing bond through humor and honesty.',
  },
  {
    id: 5,
    title: 'Spirited Away',
    year: 2001,
    genres: ['animation', 'fantasy'],
    description:
      'A young girl navigates a world of spirits and gods to rescue her parents from a witch who runs a magical bathhouse.',
  },
  {
    id: 6,
    title: 'Mad Max: Fury Road',
    year: 2015,
    genres: ['action', 'sci-fi'],
    description:
      "In a post-apocalyptic wasteland, a war captain defects with the tyrant's captive wives and drives across the desert with a lone drifter.",
  },
  {
    id: 7,
    title: 'Interstellar',
    year: 2014,
    genres: ['sci-fi', 'drama'],
    description:
      "A former pilot travels through a wormhole near Saturn to find a habitable planet as Earth's crops fail and humanity faces extinction.",
  },
  {
    id: 8,
    title: 'The Grand Budapest Hotel',
    year: 2014,
    genres: ['comedy', 'drama'],
    description:
      'A legendary hotel concierge and his protégé become entangled in theft, murder, and the upheaval of a continent between two wars.',
  },
  {
    id: 9,
    title: 'Parasite',
    year: 2019,
    genres: ['thriller', 'drama'],
    description:
      'A poor family infiltrates a wealthy household one job at a time, until a hidden secret in the basement throws everything into chaos.',
  },
  {
    id: 10,
    title: 'Finding Nemo',
    year: 2003,
    genres: ['animation', 'comedy'],
    description:
      "An overprotective clownfish crosses the ocean to rescue his son, who was captured by a diver and placed in a dentist's fish tank.",
  },
  {
    id: 11,
    title: 'The Matrix',
    year: 1999,
    genres: ['sci-fi', 'action'],
    description:
      'A computer hacker discovers that reality is a simulation built by machines, and joins a rebellion to free humanity from digital enslavement.',
  },
  {
    id: 12,
    title: 'WALL-E',
    year: 2008,
    genres: ['animation', 'sci-fi'],
    description:
      "A lonely trash-compacting robot on an abandoned Earth falls in love with a sleek probe robot and follows her into space, inadvertently deciding humanity's future.",
  },
  {
    id: 13,
    title: 'Jaws',
    year: 1975,
    genres: ['thriller', 'horror'],
    description:
      'A police chief, a marine biologist, and a grizzled fisherman hunt a great white shark that is terrorizing a summer beach community.',
  },
  {
    id: 14,
    title: 'Coco',
    year: 2017,
    genres: ['animation', 'fantasy'],
    description:
      "A boy who dreams of being a musician accidentally ends up in the Land of the Dead and discovers the truth about his family's musical past.",
  },
  {
    id: 15,
    title: 'The Silence of the Lambs',
    year: 1991,
    genres: ['thriller', 'horror'],
    description:
      'An FBI trainee seeks the help of an imprisoned cannibal psychiatrist to catch a serial killer who is kidnapping women.',
  },
]
```

## Create an Index and Add Documents

An **index** in Meilisearch is like a table. Each index stores **documents** (JSON objects with a primary key), and Meilisearch automatically indexes every field for search.

```typescript
import { Meilisearch } from 'meilisearch'

const client = new Meilisearch({ host: 'http://localhost:7700' })

const INDEX = 'movies'

// Delete the index if it exists from a previous run
await client.deleteIndexIfExists(INDEX)

// Create the index with 'id' as the primary key
await client.createIndex(INDEX, { primaryKey: 'id' }).waitTask()

// Add all movies
await client.index(INDEX).addDocuments(movies).waitTask()

console.log(`Added ${movies.length} movies to "${INDEX}" index`)
```

Meilisearch processes tasks asynchronously, one at a time in the order they were queued, so the create cannot run before the delete. The `.waitTask()` calls wait for the create and the import to finish before moving on. In production you'd handle task status differently, but for a script like this, waiting is fine.

## Configure Filterable and Sortable Attributes

Meilisearch indexes all fields for full-text search by default but doesn't enable filtering or sorting. To filter by genre or sort by year, you opt in:

```typescript
await client
  .index(INDEX)
  .updateSettings({
    filterableAttributes: ['genres', 'year'],
    sortableAttributes: ['year'],
  })
  .waitTask()

console.log('Configured filterable and sortable attributes')
```

This builds the internal data structures for filtering and sorting on those fields. You only do this once per index.

## Typo-Tolerant Search

Now the fun part. Let's throw some mangled queries at it:

```typescript
const queries = [
  'shawshnk rdmption',
  'interst',
  'the matix',
  'spirted away',
]

for (const query of queries) {
  const results = await client.index(INDEX).search(query)
  console.log(`\n"${query}"`)
  for (const hit of results.hits) {
    console.log(`  ${hit.title} (${hit.year})`)
  }
}
```

Run it:

```bash
npx tsx search.ts
```

Expected output:

```text
"shawshnk rdmption"
  The Shawshank Redemption (1994)

"interst"
  Interstellar (2014)

"the matix"
  The Matrix (1999)
  Mad Max: Fury Road (2015)

"spirted away"
  Spirited Away (2001)
```

Every query has typos or is incomplete. Meilisearch nails it anyway. "shawshnk rdmption" is missing letters in both words. "interst" is barely a fragment of "Interstellar." A traditional database would return nothing for any of these.

### Why SQL Can't Do This

To make the contrast concrete, here's what a `LIKE` query would return for the same inputs:

```typescript
function sqlLikeSearch(query: string) {
  const pattern = query.toLowerCase()
  return movies.filter(
    (m) =>
      m.title.toLowerCase().includes(pattern) ||
      m.description.toLowerCase().includes(pattern),
  )
}

for (const query of queries) {
  const matches = sqlLikeSearch(query)
  console.log(`\nSQL LIKE "%${query}%": ${matches.length} results`)
}
```

```text
SQL LIKE "%shawshnk rdmption%": 0 results
SQL LIKE "%interst%": 0 results
SQL LIKE "%the matix%": 0 results
SQL LIKE "%spirted away%": 0 results
```

Zero across the board. `LIKE` requires an exact substring match. One wrong character and the row vanishes. PostgreSQL's `pg_trgm` extension can handle _some_ fuzzy matching, but it requires manual configuration, trigram indexes, and a similarity threshold. Meilisearch does this out of the box.

## Faceted Search

Meilisearch can combine full-text search with attribute filters. This is the "filter by category, then search within results" pattern you see on every e-commerce site:

Find all sci-fi movies, then search within that subset:

```typescript
// All sci-fi movies
const sciFi = await client.index(INDEX).search('', {
  filter: ['genres = "sci-fi"'],
})

console.log('\nAll sci-fi movies:')
for (const hit of sciFi.hits) {
  console.log(`  ${hit.title} (${hit.year})`)
}

// Sci-fi movies matching "space"
const sciFiSpace = await client.index(INDEX).search('space', {
  filter: ['genres = "sci-fi"'],
})

console.log('\nSci-fi movies matching "space":')
for (const hit of sciFiSpace.hits) {
  console.log(`  ${hit.title} (${hit.year})`)
}

// Dramas from 2010 or later, sorted by year
const recentDramas = await client.index(INDEX).search('', {
  filter: ['genres = "drama"', 'year >= 2010'],
  sort: ['year:desc'],
})

console.log('\nDramas from 2010+, newest first:')
for (const hit of recentDramas.hits) {
  console.log(`  ${hit.title} (${hit.year})`)
}
```

```text
All sci-fi movies:
  Alien (1979)
  Inception (2010)
  Mad Max: Fury Road (2015)
  Interstellar (2014)
  The Matrix (1999)
  WALL-E (2008)

Sci-fi movies matching "space":
  Alien (1979)
  Interstellar (2014)
  WALL-E (2008)

Dramas from 2010+, newest first:
  Parasite (2019)
  Interstellar (2014)
  The Grand Budapest Hotel (2014)
  The Intouchables (2011)
```

Filters use a simple string syntax. Array elements are ANDed, comparison operators work on numeric fields, and you can sort on any attribute you've marked as sortable. Meilisearch narrows by filter first, then ranks by relevance within those results.

You can also request facet distributions to build dynamic filter UIs:

```typescript
const faceted = await client.index(INDEX).search('', {
  facets: ['genres'],
})

console.log('\nGenre distribution:')
for (const [genre, count] of Object.entries(
  faceted.facetDistribution?.genres ?? {},
)) {
  console.log(`  ${genre}: ${count}`)
}
```

```text
Genre distribution:
  action: 2
  animation: 4
  comedy: 3
  drama: 5
  fantasy: 2
  horror: 3
  sci-fi: 6
  thriller: 3
```

That's exactly the data you need for a sidebar with genre checkboxes and counts. I love that this comes for free.

## When to Reach for Meilisearch

Meilisearch shines when users are typing into a search box and expect it to just work:

- **Search bars and autocomplete**: any UI where users type and expect instant, forgiving results
- **Product catalogs**: typo-tolerant search combined with faceted filtering by category, price, or attributes
- **Documentation search**: find the right page even when users don't remember the exact term
- **Content platforms**: search across blog posts, articles, or media libraries with prefix matching
- **Internal tools**: search employees, tickets, or records without forcing users to type exact names

If your search needs to feel instant and forgive sloppy typing, Meilisearch is probably what you want.

Where it's _not_ the right fit: if you need to search by _meaning_ rather than text (e.g., "scary movie set in space" matching a description about a "deadly creature in corridors"), that's semantic search. A vector database like [Qdrant](/blog/getting-started-with-qdrant) is built for that.

## FAQ

### Does the Layerbase free tier include Meilisearch?

No. Meilisearch is a Pro-plan engine on Layerbase Cloud, partly because its index grows resident in memory with the document set. Running it locally through the CLI is free, and the code in this guide is identical either way.

### How does my application talk to Meilisearch?

Over HTTP, using the `meilisearch` JavaScript client with a host and an API key. Locally the host is `http://127.0.0.1` on the port `lbase url meili1` prints; on Cloud it is an `https://` host from Quick Connect. Keep the key server-side.

### Do I need to configure anything before searching?

Only the attributes you intend to filter or sort on. Meilisearch indexes documents for search automatically, but `filterableAttributes` and `sortableAttributes` have to be declared before a facet or a sort will work. That is the one setup step people miss.

### Is Meilisearch a semantic search engine?

Not in the embedding sense. It matches text with typo tolerance and prefix awareness, which is what a search box needs. Searching by meaning, where "scary movie set in space" should match a description that never uses those words, is a vector database job; see [Qdrant](/blog/getting-started-with-qdrant) for that.

## Wrapping Up

The full script is under 80 lines of real code. You indexed a dataset, ran typo-tolerant and faceted searches that traditional databases can't touch, and got results in milliseconds. The same pattern scales from 15 movies to millions of documents.

The [Meilisearch documentation](https://www.meilisearch.com/docs) covers advanced features like synonyms, stop words, ranking rules, multi-index search, geo search, and tenant tokens for multi-tenant applications.

To manage your local Meilisearch instance:

```bash
lbase stop meili1    # Stop the server
lbase start meili1   # Start it again
lbase list           # See all your database instances
```

The Layerbase CLI handles [20+ database engines](/docs/cli), so Meilisearch can sit alongside Qdrant, CockroachDB, or whatever else your project needs. [Layerbase Desktop](https://layerbase.com/desktop/download) wraps the same thing in a GUI on macOS.
