| ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄|
Open Source Alternative
to Solr and Elastic
|________________|
\(•◡•)/
\ /
---
| |
|_|_
Actual ad: x.com/typesense/status/1702152406924980670
![]() |
|
Typesense is a search engine for your website that does clever things much faster than anything else you've seen
typesense.org

| 🔥 Instantaneous sub-ms response times | |
| ✅ Faceting ✅ Sorting ✅ Curation ✅ Synonyms ✅ Scoped search ✅ Geospatial |
✅ Automatic schema detection ✅ Analytics ✅ Document text extraction ✅ Multi-language support ✅ Federated search ✅ Semantic & natural search |
| ✅ Decentralised management dashboard ✅ AI / Machine learning with GPU support |
|

| Amount | RAM | Time |
|---|---|---|
| 2.4M Addresses | 600MB | ~5 minutes |
| 2.2M Recipes | 900MB | 3.6 minutes |
| 28M Books | 14GB | 76 minutes |
| 250 concurrent search queries per second | ||


But wait, there's more!




```js
{
"name": "Colours",
"fields": [
{ "name": ".*", "type": "auto", "optional": true },
{ "name": "Charts", "type": "string[]", "facet": true, "sort": false },
{ "name": "Category", "type": "string", "facet": true, "sort": false },
{
"name": "rgb_vector",
"type": "float[]",
"facet": false,
"sort": false,
"num_dim": 3,
"vec_dist": "cosine"
}
]
}
```

```
const response = await fetch(
`${typesenseUri}/collections/${searchCollection}/documents/search
?q=*
&vector_query=${encodeURIComponent(vectorQuery)}
&exclude_fields=rgb_vector
&sort_by=Blue:desc
`,
{
headers: {
'X-TYPESENSE-API-KEY': TYPESENSE_CONFIG.apiKey
}
}
);
const data = await response.json();
```

```
{
name: Addresses
fields: [
{ name: Region, type: string, facet: true, optional: true }
{ name: Council, type: string, facet: true, optional: true }
{ name: City, type: string, facet: true, optional: true }
{ name: Suburb, type: string, facet: true, optional: true }
{ name: FullAddress, type: string}
{ name: Location, type: geopoint}
]
}
```
```php
public function run($request) {
$this->preprocess();
$this->importSuburbs();
$this->deleteTypesenseCollection();
$this->buildTypesenseCollection();
}
public function preprocess()
{
$councilsDB = DB::query("SELECT ID, Name FROM NZTerritorialAuthority");
foreach($councilsDB as $council) {
$this->councils[$council['ID']] = $council['Name'];
}
$citiesDB = DB::query("SELECT ID, Name FROM NZTownCity");
foreach($citiesDB as $city) {
$this->cities[$city['ID']] = $city['Name'];
}
$suburbsDB = DB::query("SELECT ID, Name FROM NZSuburbLocality");
foreach($suburbsDB as $suburb) {
$this->suburbs[$suburb['ID']] = $suburb['Name'];
}
}
public function deleteTypesenseCollection()
{
try {
$client = Typesense::client();
$client->collections['suburbs']->delete();
} catch (Exception $e) {}
}
public function buildTypesenseCollection()
{
try {
$client = Typesense::client();
$client->collections->create([
'name' => 'suburbs',
'enable_nested_fields' => true,
'fields' => [
[ 'name' => 'council.name', 'type' => 'string' ],
[ 'name' => 'city.name', 'type' => 'string' ],
[ 'name' => 'suburb.name', 'type' => 'string' ],
[ 'name' => 'polyline', 'type' => 'string' ],
]
]);
$client->collections['suburbs']->documents->import($this->documents, ['action' => 'emplace']);
} catch (Exception $e) {}
}
public function importSuburbs()
{
$suburbsDB = DB::query("SELECT ID, CouncilID, CityID, SuburbID, Polyline FROM NZSuburb");
foreach($suburbsDB as $suburb) {
$councilName = $this->councils[$suburb['CouncilID']];
$cityName = $this->cities[$suburb['CityID']];
$suburbName = $this->suburbs[$suburb['SuburbID']];
$polyline = $suburb['Polyline'];
if($councilName && $cityName && $suburbName && $polyline) {
$this->documents[] = [
'id' => (string) $suburb['ID'],
'council.name' => $councilName,
'city.name' => $cityName,
'suburb.name' => $suburbName,
'polyline' => $polyline,
];
}
}
}
```
```js
// Listen for refinement changes to display suburb polyline
//search is our InstantSearch client
search.on('render', function() {
//...
// Display the suburb polyline
if (cityName && councilName) {
displaySuburbPolyline(suburbName, cityName, councilName);
}
}
async function displaySuburbPolyline(...) {
//clear layers, bail if any parameters empty
try {
const searchParameters = {
q: '*', query_by: 'suburb.name', filter_by: `suburb.name:=${suburbName} && city.name:=${cityName} && council.name:=${councilName}`, per_page: 1
};
const results = await typesenseClient.collections('suburbs').documents().search(searchParameters);
if (results.hits && results.hits.length > 0) {
//decode polyline with mapbox, add it to map
}
}
}
```

```js
{
name: SharedDocuments,
enable_nested_fields: true,
fields: [
{ name: Title, type: string, sort: true },
{ name: Content, type: string, optional: true },
{ name: Link, type: string, index: false, optional: true},
{ name: Owner.ID, type: int32[], index: true, optional: false, sort: false},
{ name: Owner.Name, type: string[], facet: true, index: true, optional: false, sort: false},
{ name: Owner.Email, type: string[], facet: false, index: false, optional: false, sort: false}
]
}
```

```php
class ScopedSearchPageController extends TypesenseSearchPageController
{
public function init()
{
parent::init();
if(!($member = Security::getCurrentUser())) {
return;
}
$parentKey = $this->data()->SearchAPIKey;
if($member && !$member->TypesenseAPISearchKey) {
$client = Typesense::client();
$expiry = strtotime("+24 hours");
if(!$member->TypesenseAPISearchKey || $member->TypesenseAPISearchKeyExpires > DBDatetime::now()) {
$scopedKey = $client->keys->generateScopedSearchKey($parentKey, [
'filter_by' => 'Owner.ID:'.$member->ID,
'expires_at' => $expiry
]);
if($scopedKey) {
$member->TypesenseAPISearchKey = $scopedKey;
$member->TypesenseAPISearchKeyExpires = $expiry;
$member->write();
}
}
}
}
}
```



