Skip to main content
Live Queries in Modelence provide real-time data synchronization between your server and client. When underlying data changes, connected clients automatically receive updated data without manual polling or refetching.

Version Requirements

Live Queries are available with the following minimum package versions:
  • modelence >= 0.15.1 (requires Store.watch(), introduced in 0.15.1)
  • @modelence/react-query >= 1.2.1

Overview

The live query system consists of three parts:
  1. Server-side LiveData - Defines how to fetch data and watch for changes
  2. ModelenceQueryProvider (or connectModelenceQueryClient) - Connects Modelence’s live query system to TanStack Query
  3. modelenceLiveQuery - Creates live query options for useQuery

Client Setup

1. Provide a connected QueryClient

modelenceLiveQuery needs a TanStack Query QueryClient connected to Modelence’s live-query layer. The simplest way is to let renderApp handle it: if you don’t mount your own provider, renderApp automatically wraps your app in ModelenceQueryProvider, which creates a QueryClient and connects it for you.
If you need your own QueryClient (for example, to configure defaults or share it with other libraries), connect it yourself with connectModelenceQueryClient and mount your own QueryClientProvider. renderApp detects the already-connected client and will not inject a second provider.
If you mount your own QueryClientProvider but never call connectModelenceQueryClient(queryClient), modelenceLiveQuery throws: Modelence: connect a QueryClient before using modelenceLiveQuery(). Mount <ModelenceQueryProvider> or call connectModelenceQueryClient().

2. Use modelenceLiveQuery in Components

Use modelenceLiveQuery with TanStack Query’s useQuery hook. It works just like modelenceQuery, but data updates automatically when the server detects changes:

Server Setup

Returning LiveData from Query Handlers

Live query handlers must return a LiveData object, not plain data. LiveData tells Modelence how to fetch the data and how to watch for changes.
If a live query handler returns plain data instead of a LiveData object, the server will throw: Live query handler for 'X' must return a LiveData object with fetch and watch functions.

LiveData Configuration

LiveData accepts two functions:

Using MongoDB Change Streams

The most common pattern for the watch function is MongoDB change streams, which notify you when documents in a collection are inserted, updated, or deleted:
You can also use a pipeline to filter which changes trigger updates:
MongoDB change streams require a replica set or sharded cluster. If you’re using MongoDB Atlas, this is enabled by default. For local development, you need to configure a replica set.

Complete Example

Here’s a full example of a live todo list:

Server

Client

With this setup, when any client creates or updates a todo, all connected clients see the changes immediately.

Live Queries vs WebSockets

Live queries are built on top of WebSockets internally, but provide a higher-level abstraction for the common pattern of keeping query data in sync.

Common Pitfalls

Missing QueryClient connection

If you see Modelence: connect a QueryClient before using modelenceLiveQuery(), you mounted your own QueryClientProvider without connecting it. Either drop your custom provider and let renderApp inject ModelenceQueryProvider, or connect your client explicitly:

Returning plain data instead of LiveData

If you see Live query handler for 'X' must return a LiveData object, your query handler is returning data directly. Wrap it in LiveData:

Forgetting to close change streams

Always return a cleanup function from watch to close change streams. Without this, streams accumulate and may exhaust database connections:

Migrating from @modelence/react-query

Since modelence@0.15.0, the query helpers live in modelence/client, and renderApp connects a QueryClient for you. Existing apps using @modelence/react-query keep working — this migration is optional — but new code should use the core imports. 1. Swap the import source. The helpers are drop-in identical:
2. Drop the manual provider wiring (optional). If you connected the client only to satisfy live queries, you can let renderApp inject ModelenceQueryProvider and remove the boilerplate:
If you still need your own QueryClient, replace new ModelenceQueryClient().connect(queryClient) with connectModelenceQueryClient(queryClient) (the ModelenceQueryClient class is deprecated but still exported for compatibility).

API Reference