Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
8abb098
[FEAT]: initialize full-text search application with Spring Boot, Typ…
Nikhiladiga Apr 25, 2026
f00cd35
First revision: update sample env file with dummy db credentials
Nikhiladiga Apr 25, 2026
1f515a0
[FEAT]: implement Typesense search integration with PostgreSQL and Se…
Nikhiladiga May 2, 2026
15a27c4
First revision: add Node.js Sequelize app to README and include Types…
Nikhiladiga May 10, 2026
8e31239
[FEAT]: implement Typesense-integrated Node.js and Prisma full-text s…
Nikhiladiga May 2, 2026
afb89e2
First revision: add Typesense Prisma search sample and agent syntax g…
Nikhiladiga May 10, 2026
754746b
[FEAT]: initialize Typesense and PostgreSQL project with Drizzle ORM …
Nikhiladiga May 2, 2026
022bbbc
First revision: add Drizzle implementation guide to README and includ…
Nikhiladiga May 10, 2026
64de032
[FEAT]: Create sample Kotlin Android project demonstrating Typesense …
Nikhiladiga May 15, 2026
0b915d3
First revision: register Kotlin search project in README and rename r…
Nikhiladiga May 17, 2026
86b9c14
[FEAT]: initialize Typesense Swift search application with SwiftUI an…
Nikhiladiga May 20, 2026
b410ea6
First revision: migrate networking to official Typesense Swift SDK
Nikhiladiga May 20, 2026
1030b22
[FEATURE]: add new SvelteKit search application using Typesense and i…
Nikhiladiga May 24, 2026
1d50959
[FEAT]: initialise Typesense AI movie search project with metadata se…
Nikhiladiga Jun 3, 2026
d44b1b2
First revision: add typesense-langchain-ai-search project to README
Nikhiladiga Jun 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 26 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,24 @@ This is a monorepo containing multiple standalone projects. Each project lives i

```plaintext
code-samples/
├── typesense-angular-search-bar/ # Angular + Typesense search implementation
├── typesense-astro-search/ # Astro + Typesense search implementation
├── typesense-gin-full-text-search/ # Go (Gin) + Typesense backend implementation
├── typesense-next-search-bar/ # Next.js + Typesense search implementation
├── typesense-nuxt-search-bar/ # Nuxt.js + Typesense search implementation
├── typesense-qwik-js-search/ # Qwik + Typesense search implementation
├── typesense-react-native-search-bar/ # React Native + Typesense search implementation
├── typesense-solid-js-search/ # SolidJS + Typesense search implementation
├── typesense-vanilla-js-search/ # Vanilla JS + Typesense search implementation
└── README.md # You are here
├── typesense-angular-search-bar/ # Angular + Typesense search implementation
├── typesense-astro-search/ # Astro + Typesense search implementation
├── typesense-gin-full-text-search/ # Go (Gin) + Typesense backend implementation
├── typesense-kotlin/ # Kotlin (Android) + Typesense search implementation
├── typesense-langchain-ai-search/ # Python + LangChain + Typesense AI search implementation
├── typesense-next-search-bar/ # Next.js + Typesense search implementation
├── typesense-nuxt-search-bar/ # Nuxt.js + Typesense search implementation
├── typesense-qwik-js-search/ # Qwik + Typesense search implementation
├── typesense-react-native-search-bar/ # React Native + Typesense search implementation
├── typesense-solid-js-search/ # SolidJS + Typesense search implementation
├── typesense-springboot-full-text-search/ # Spring Boot + Typesense backend implementation
├── typesense-node-prisma-full-text-search/ # Node.js (Express) + Typesense + Prisma backend implementation
├── typesense-node-sequelize-full-text-search/ # Node.js (Express) + Typesense + Sequelize backend implementation
├── typesense-node-drizzle-full-text-search/ # Node.js (Express) + Typesense + Drizzle backend implementation
├── typesense-sveltekit-search-app/ # SvelteKit + Typesense search implementation
├── typesense-swift-search/ # Swift (iOS) + Typesense search implementation
├── typesense-vanilla-js-search/ # Vanilla JS + Typesense search implementation
└── README.md # You are here
```

## Projects
Expand All @@ -27,11 +35,19 @@ code-samples/
| [typesense-angular-search-bar](./typesense-angular-search-bar) | Angular | A modern search bar with instant search capabilities |
| [typesense-astro-search](./typesense-astro-search) | Astro | A modern search bar with instant search capabilities |
| [typesense-gin-full-text-search](./typesense-gin-full-text-search) | Go (Gin) | Backend API with full-text search using Typesense |
| [typesense-kotlin](./typesense-kotlin) | Kotlin (Android) | A native Android search bar with instant search capabilities |
| [typesense-langchain-ai-search](./typesense-langchain-ai-search) | LangChain (Python) | Conversational AI-powered movie search and recommendation app |
| [typesense-next-search-bar](./typesense-next-search-bar) | Next.js | A modern search bar with instant search capabilities |
| [typesense-nuxt-search-bar](./typesense-nuxt-search-bar) | Nuxt.js | A modern search bar with instant search capabilities |
| [typesense-qwik-js-search](./typesense-qwik-js-search) | Qwik | Resumable search bar with real-time search and modern UI |
| [typesense-react-native-search-bar](./typesense-react-native-search-bar) | React Native | A mobile search bar with instant search capabilities |
| [typesense-solid-js-search](./typesense-solid-js-search) | SolidJS | A modern search bar with instant search capabilities |
| [typesense-springboot-full-text-search](./typesense-springboot-full-text-search) | Spring Boot | Backend API with full-text search using Typesense |
| [typesense-node-prisma-full-text-search](./typesense-node-prisma-full-text-search) | Node.js (Express) + Typesense + Prisma | Backend API with full-text search using Typesense |
| [typesense-node-sequelize-full-text-search](./typesense-node-sequelize-full-text-search) | Node.js (Express) + Typesense + Sequelize | Backend API with full-text search using Typesense |
| [typesense-node-drizzle-search-app](./typesense-node-drizzle-search-app) | Node.js (Express) + Typesense + Drizzle | Backend API with full-text search using Typesense |
| [typesense-sveltekit-search-app](./typesense-sveltekit-search-app) | SvelteKit | A modern search bar with instant search capabilities |
| [typesense-swift-search](./typesense-swift-search) | Swift (iOS) | A native iOS search app with instant search capabilities |
| [typesense-vanilla-js-search](./typesense-vanilla-js-search) | Vanilla JS | A modern search bar with instant search capabilities |

## Getting Started
Expand Down
15 changes: 15 additions & 0 deletions typesense-kotlin-search/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
1 change: 1 addition & 0 deletions typesense-kotlin-search/app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
54 changes: 54 additions & 0 deletions typesense-kotlin-search/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
}

android {
namespace = "org.typesense.samplekotlin"
compileSdk = 34

defaultConfig {
applicationId = "org.typesense.samplekotlin"
minSdk = 26
targetSdk = 34
versionCode = 1
versionName = "1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
buildFeatures {
viewBinding = true
}
}

dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
implementation(libs.typesense)
implementation(libs.androidx.lifecycle.viewmodel.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.coil)

testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
}
21 changes: 21 additions & 0 deletions typesense-kotlin-search/app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.typesense.samplekotlin

import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4

import org.junit.Test
import org.junit.runner.RunWith

import org.junit.Assert.*

/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("org.typesense.samplekotlin", appContext.packageName)
}
}
26 changes: 26 additions & 0 deletions typesense-kotlin-search/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.INTERNET" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.TypesenseWithKotlin"
android:usesCleartextTraffic="true">

<activity
android:name=".presentation.MainActivity"
android:exported="true"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.typesense.samplekotlin.data.repository

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.typesense.api.Client
import org.typesense.model.SearchParameters
import org.typesense.samplekotlin.domain.model.Book
import org.typesense.samplekotlin.domain.repository.BookRepository

class TypesenseBookRepository(private val client: Client) : BookRepository {

override suspend fun searchBooks(query: String): List<Book> = withContext(Dispatchers.IO) {
val searchParameters = SearchParameters()
.q(query)
.queryBy("title,authors")
.sortBy("average_rating:desc")

val searchResult = client.collections("books").documents().search(searchParameters)

searchResult.hits?.map { hit ->
val document = hit.document
Book(
id = document["id"]?.toString() ?: "",
title = document["title"]?.toString() ?: "",
authors = (document["authors"] as? List<*>)?.map { it.toString() } ?: emptyList(),
publicationYear = (document["publication_year"] as? Double)?.toInt(),
imageUrl = document["image_url"]?.toString(),
averageRating = document["average_rating"] as? Double
)
} ?: emptyList()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.typesense.samplekotlin.domain.model

data class Book(
val id: String,
val title: String,
val authors: List<String>,
val publicationYear: Int?,
val imageUrl: String?,
val averageRating: Double?
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.typesense.samplekotlin.domain.repository

import org.typesense.samplekotlin.domain.model.Book

interface BookRepository {
suspend fun searchBooks(query: String): List<Book>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.typesense.samplekotlin.domain.usecase

import org.typesense.samplekotlin.domain.model.Book
import org.typesense.samplekotlin.domain.repository.BookRepository

class SearchBooksUseCase(private val repository: BookRepository) {
suspend operator fun invoke(query: String): Result<List<Book>> {
return try {
if (query.isBlank()) {
Result.success(emptyList())
} else {
Result.success(repository.searchBooks(query))
}
} catch (e: Exception) {
Result.failure(e)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.typesense.samplekotlin.presentation

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import coil.load
import org.typesense.samplekotlin.databinding.ItemBookBinding
import org.typesense.samplekotlin.domain.model.Book

class BookAdapter : ListAdapter<Book, BookAdapter.BookViewHolder>(BookDiffCallback()) {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BookViewHolder {
val binding = ItemBookBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return BookViewHolder(binding)
}

override fun onBindViewHolder(holder: BookViewHolder, position: Int) {
holder.bind(getItem(position))
}

class BookViewHolder(private val binding: ItemBookBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(book: Book) {
binding.titleTextView.text = book.title
binding.authorsTextView.text = book.authors.joinToString(", ")
binding.yearTextView.text = book.publicationYear?.toString() ?: ""
binding.ratingBar.rating = book.averageRating?.toFloat() ?: 0f

binding.bookCoverImageView.load(book.imageUrl) {
crossfade(true)
placeholder(android.R.color.darker_gray)
error(android.R.color.darker_gray)
}
}
}

class BookDiffCallback : DiffUtil.ItemCallback<Book>() {
override fun areItemsTheSame(oldItem: Book, newItem: Book): Boolean = oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Book, newItem: Book): Boolean = oldItem == newItem
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.typesense.samplekotlin.presentation

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import org.typesense.samplekotlin.domain.model.Book
import org.typesense.samplekotlin.domain.usecase.SearchBooksUseCase

class BookViewModel(private val searchBooksUseCase: SearchBooksUseCase) : ViewModel() {

private val _uiState = MutableStateFlow<BookUiState>(BookUiState.Idle)
val uiState: StateFlow<BookUiState> = _uiState

fun search(query: String) {
if (query.isBlank()) {
_uiState.value = BookUiState.Idle
return
}

_uiState.value = BookUiState.Loading
viewModelScope.launch {
searchBooksUseCase(query)
.onSuccess { books ->
_uiState.value = BookUiState.Success(books)
}
.onFailure { error ->
_uiState.value = BookUiState.Error(error.message ?: "Unknown error")
}
}
}
}

sealed class BookUiState {
object Idle : BookUiState()
object Loading : BookUiState()
data class Success(val books: List<Book>) : BookUiState()
data class Error(val message: String) : BookUiState()
}
Loading