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.
- Node.js
- Express
- PostgreSQL with Sequelize
- Typesense
- TypeScript
- Docker
- Node.js v18+ installed
- Docker (for Typesense and PostgreSQL)
- Basic knowledge of REST APIs and SQL
git clone https://github.com/typesense/code-samples.git
cd typesense-node-sequelize-search-appnpm installRun Typesense and PostgreSQL using Docker:
# 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:15Create a .env file in the project root by copying .env.example:
cp .env.example .env├── 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
npm run devThe server will automatically restart when you make changes to any TypeScript file.
Open http://localhost:3000 in your browser.
GET /search?q=<query>Example:
curl "http://localhost:3000/search?q=harry"Create a book:
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:
GET /books/:idGet all books (with pagination):
GET /books?page=1&limit=10Update a book:
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):
DELETE /books/:idTrigger manual sync:
POST /syncCheck sync status:
GET /sync/statusUser Request
↓
Express API (CRUD)
↓
PostgreSQL (Source of Truth)
↓
Async Sync → Typesense (Search Index)
↑
Background Worker (Every 60s)
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.
Triggered on Create, Update, Delete operations in the background.
Runs every 60 seconds automatically, doing incremental sync.
Endpoint: POST /sync