Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 14 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,18 @@ 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-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-sequelize-search-app/ # Node.js (Express) + Typesense + Sequelize backend implementation
├── typesense-vanilla-js-search/ # Vanilla JS + Typesense search implementation
└── README.md # You are here
```

## Projects
Expand All @@ -32,6 +34,8 @@ code-samples/
| [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-sequelize-search-app](./typesense-node-sequelize-search-app) | Node.js (Express) + Typesense + Sequelize | Backend API with full-text search using Typesense |
| [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-node-sequelize-full-text-search/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Server Configuration
PORT=3000

# Database Configuration
DB_HOST=localhost
DB_USER=postgres
DB_PASSWORD=password
DB_NAME=typesense_books
DB_PORT=5432

# Typesense Configuration
TYPESENSE_HOST=localhost
TYPESENSE_PORT=8108
TYPESENSE_PROTOCOL=http
TYPESENSE_API_KEY=xyz
212 changes: 212 additions & 0 deletions typesense-node-sequelize-full-text-search/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
# Node.js Express Full-Text Search with Typesense

A production-ready RESTful search API built with Node.js, Express, PostgreSQL (Sequelize), and Typesense. Features full-text search, CRUD operations, real-time async indexing, and background sync workers.

## Tech Stack

- Node.js
- Express
- PostgreSQL with Sequelize
- Typesense
- TypeScript
- Docker

## Prerequisites

- Node.js v18+ installed
- Docker (for Typesense and PostgreSQL)
- Basic knowledge of REST APIs and SQL

## Quick Start

### 1. Clone the repository

```bash
git clone https://github.com/typesense/code-samples.git
cd typesense-node-sequelize-search-app
```

### 2. Install dependencies

```bash
npm install
```

### 3. Start Typesense and PostgreSQL

Run Typesense and PostgreSQL using Docker:

```bash
# Start Typesense (replace TYPESENSE_VERSION with the latest from https://typesense.org/docs/guide/install-typesense.html)
docker run -d \
-p 8108:8108 \
-v typesense-data:/data \
typesense/typesense:27.1 \
--data-dir /data \
--api-key=xyz \
--enable-cors

# Start PostgreSQL
docker run -d \
-p 5432:5432 \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=password \
-e POSTGRES_DB=typesense_books \
-v postgres-data:/var/lib/postgresql/data \
postgres:15
```

### 4. Set up environment variables

Create a `.env` file in the project root by copying `.env.example`:

```bash
cp .env.example .env
```

### 5. Project Structure

```text
├── src/
│ ├── config/
│ │ ├── database.ts # Sequelize configuration
│ │ └── env.ts # Environment variable validation
│ ├── models/
│ │ └── Book.ts # Sequelize Book model
│ ├── routes/
│ │ ├── books.ts # CRUD endpoints for books
│ │ └── search.ts # Search and sync endpoints
│ ├── search/
│ │ ├── client.ts # Typesense client initialization
│ │ ├── collections.ts # Typesense collection schema
│ │ ├── sync.ts # Sync logic (incremental, full, soft delete)
│ │ └── worker.ts # Background sync worker
│ └── server.ts # Main application entry point
├── package.json
├── tsconfig.json
└── .env
```

### 6. Start the development server

```bash
npm run dev
```

The server will automatically restart when you make changes to any TypeScript file.

Open [http://localhost:3000](http://localhost:3000) in your browser.

### 7. API Endpoints

#### Search

```bash
GET /search?q=<query>
```

Example:

```bash
curl "http://localhost:3000/search?q=harry"
```

#### CRUD Operations

**Create a book:**

```bash
POST /books
Content-Type: application/json

{
"title": "The Go Programming Language",
"authors": ["Alan Donovan", "Brian Kernighan"],
"publication_year": 2015,
"average_rating": 4.5,
"image_url": "https://example.com/image.jpg",
"ratings_count": 1000
}
```

**Get a book:**

```bash
GET /books/:id
```

**Get all books (with pagination):**

```bash
GET /books?page=1&limit=10
```

**Update a book:**

```bash
PUT /books/:id
Content-Type: application/json

{
"title": "Updated Title",
"authors": ["Author Name"],
"publication_year": 2024,
"average_rating": 4.8,
"image_url": "https://example.com/updated.jpg",
"ratings_count": 1500
}
```

**Delete a book (soft delete):**

```bash
DELETE /books/:id
```

#### Sync Operations

**Trigger manual sync:**

```bash
POST /sync
```

**Check sync status:**

```bash
GET /sync/status
```

### 8. How It Works

#### Architecture

```plaintext
User Request
Express API (CRUD)
PostgreSQL (Source of Truth)
Async Sync → Typesense (Search Index)
Background Worker (Every 60s)
```

#### Sync Strategies

##### 1. Startup Sync (Smart)

On every server start, the sync worker checks whether the Typesense collection already has documents. If empty, it seeds `lastSyncTime` to zero and runs a full sync. If it has data, it runs an incremental sync since `MAX(updated_at)` of PostgreSQL books table.

##### 2. Real-time Sync (Async)

Triggered on Create, Update, Delete operations in the background.

##### 3. Background Periodic Sync

Runs every 60 seconds automatically, doing incremental sync.

##### 4. Manual Sync

Endpoint: `POST /sync`
29 changes: 29 additions & 0 deletions typesense-node-sequelize-full-text-search/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "typesense-node-sequelize-search-app",
"version": "1.0.0",
"description": "A production-ready RESTful search API built with Node.js, Express, PostgreSQL, and Typesense.",
"main": "dist/server.js",
"scripts": {
"start": "node dist/server.js",
"dev": "ts-node-dev --respawn --transpile-only src/server.ts",
"build": "tsc"
},
"dependencies": {
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"node-cron": "^3.0.3",
"pg": "^8.11.5",
"pg-hstore": "^2.3.4",
"sequelize": "^6.37.3",
"typesense": "^1.8.2"
},
"devDependencies": {
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/node": "^20.12.7",
"@types/node-cron": "^3.0.11",
"ts-node-dev": "^2.0.0",
"typescript": "^5.4.5"
}
}
15 changes: 15 additions & 0 deletions typesense-node-sequelize-full-text-search/src/config/database.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Sequelize } from 'sequelize';
import { env } from './env';

export const sequelize = new Sequelize(env.DB_NAME, env.DB_USER, env.DB_PASSWORD, {
host: env.DB_HOST,
port: env.DB_PORT,
dialect: 'postgres',
logging: console.log,
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
}
});
18 changes: 18 additions & 0 deletions typesense-node-sequelize-full-text-search/src/config/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import dotenv from 'dotenv';

dotenv.config();

export const env = {
PORT: parseInt(process.env.PORT || '3000', 10),

DB_HOST: process.env.DB_HOST || 'localhost',
DB_USER: process.env.DB_USER || 'postgres',
DB_PASSWORD: process.env.DB_PASSWORD || 'password',
DB_NAME: process.env.DB_NAME || 'typesense_books',
DB_PORT: parseInt(process.env.DB_PORT || '5432', 10),

TYPESENSE_HOST: process.env.TYPESENSE_HOST || 'localhost',
TYPESENSE_PORT: parseInt(process.env.TYPESENSE_PORT || '8108', 10),
TYPESENSE_PROTOCOL: process.env.TYPESENSE_PROTOCOL || 'http',
TYPESENSE_API_KEY: process.env.TYPESENSE_API_KEY || 'xyz',
};
Loading