TrailBase collections provide seamless integration between TanStack DB and TrailBase, enabling real-time data synchronization with TrailBase's self-hosted application backend.
TrailBase is an easy-to-self-host, single-executable application backend with built-in SQLite, a V8 JS runtime, auth, admin UIs and sync functionality.
The @tanstack/trailbase-db-collection package allows you to create collections that:
npm install @tanstack/trailbase-db-collection @tanstack/react-db trailbase
npm install @tanstack/trailbase-db-collection @tanstack/react-db trailbase
import { createCollection } from '@tanstack/react-db'
import { trailBaseCollectionOptions } from '@tanstack/trailbase-db-collection'
import { initClient } from 'trailbase'
const trailBaseClient = initClient(`https://your-trailbase-instance.com`)
const todosCollection = createCollection(
trailBaseCollectionOptions({
id: 'todos',
recordApi: trailBaseClient.records('todos'),
getKey: (item) => item.id,
})
)
import { createCollection } from '@tanstack/react-db'
import { trailBaseCollectionOptions } from '@tanstack/trailbase-db-collection'
import { initClient } from 'trailbase'
const trailBaseClient = initClient(`https://your-trailbase-instance.com`)
const todosCollection = createCollection(
trailBaseCollectionOptions({
id: 'todos',
recordApi: trailBaseClient.records('todos'),
getKey: (item) => item.id,
})
)
The trailBaseCollectionOptions function accepts the following options:
TrailBase uses different data formats for storage (e.g., Unix timestamps). Use parse and serialize to handle these transformations:
type SelectTodo = {
id: string
text: string
created_at: number // Unix timestamp from TrailBase
completed: boolean
}
type Todo = {
id: string
text: string
created_at: Date // JavaScript Date for app usage
completed: boolean
}
const todosCollection = createCollection<SelectTodo, Todo>(
trailBaseCollectionOptions({
id: 'todos',
recordApi: trailBaseClient.records('todos'),
getKey: (item) => item.id,
schema: todoSchema,
// Transform TrailBase data to application format
parse: {
created_at: (ts) => new Date(ts * 1000),
},
// Transform application data to TrailBase format
serialize: {
created_at: (date) => Math.floor(date.valueOf() / 1000),
},
})
)
type SelectTodo = {
id: string
text: string
created_at: number // Unix timestamp from TrailBase
completed: boolean
}
type Todo = {
id: string
text: string
created_at: Date // JavaScript Date for app usage
completed: boolean
}
const todosCollection = createCollection<SelectTodo, Todo>(
trailBaseCollectionOptions({
id: 'todos',
recordApi: trailBaseClient.records('todos'),
getKey: (item) => item.id,
schema: todoSchema,
// Transform TrailBase data to application format
parse: {
created_at: (ts) => new Date(ts * 1000),
},
// Transform application data to TrailBase format
serialize: {
created_at: (date) => Math.floor(date.valueOf() / 1000),
},
})
)
TrailBase supports real-time subscriptions when enabled on the server. The collection automatically subscribes to changes and updates in real-time:
const todosCollection = createCollection(
trailBaseCollectionOptions({
id: 'todos',
recordApi: trailBaseClient.records('todos'),
getKey: (item) => item.id,
// Real-time updates work automatically when
// enable_subscriptions is set in TrailBase config
})
)
// Changes from other clients will automatically update
// the collection in real-time
const todosCollection = createCollection(
trailBaseCollectionOptions({
id: 'todos',
recordApi: trailBaseClient.records('todos'),
getKey: (item) => item.id,
// Real-time updates work automatically when
// enable_subscriptions is set in TrailBase config
})
)
// Changes from other clients will automatically update
// the collection in real-time
Handle inserts, updates, and deletes by providing mutation handlers:
const todosCollection = createCollection(
trailBaseCollectionOptions({
id: 'todos',
recordApi: trailBaseClient.records('todos'),
getKey: (item) => item.id,
onInsert: async ({ transaction }) => {
const newTodo = transaction.mutations[0].modified
// TrailBase handles the persistence automatically
// Add custom logic here if needed
},
onUpdate: async ({ transaction }) => {
const { original, modified } = transaction.mutations[0]
// TrailBase handles the persistence automatically
// Add custom logic here if needed
},
onDelete: async ({ transaction }) => {
const deletedTodo = transaction.mutations[0].original
// TrailBase handles the persistence automatically
// Add custom logic here if needed
},
})
)
const todosCollection = createCollection(
trailBaseCollectionOptions({
id: 'todos',
recordApi: trailBaseClient.records('todos'),
getKey: (item) => item.id,
onInsert: async ({ transaction }) => {
const newTodo = transaction.mutations[0].modified
// TrailBase handles the persistence automatically
// Add custom logic here if needed
},
onUpdate: async ({ transaction }) => {
const { original, modified } = transaction.mutations[0]
// TrailBase handles the persistence automatically
// Add custom logic here if needed
},
onDelete: async ({ transaction }) => {
const deletedTodo = transaction.mutations[0].original
// TrailBase handles the persistence automatically
// Add custom logic here if needed
},
})
)
import { createCollection } from '@tanstack/react-db'
import { trailBaseCollectionOptions } from '@tanstack/trailbase-db-collection'
import { initClient } from 'trailbase'
import { z } from 'zod'
const trailBaseClient = initClient(`https://your-trailbase-instance.com`)
// Define schema
const todoSchema = z.object({
id: z.string(),
text: z.string(),
completed: z.boolean(),
created_at: z.date(),
})
type SelectTodo = {
id: string
text: string
completed: boolean
created_at: number
}
type Todo = z.infer<typeof todoSchema>
// Create collection
export const todosCollection = createCollection<SelectTodo, Todo>(
trailBaseCollectionOptions({
id: 'todos',
recordApi: trailBaseClient.records('todos'),
getKey: (item) => item.id,
schema: todoSchema,
parse: {
created_at: (ts) => new Date(ts * 1000),
},
serialize: {
created_at: (date) => Math.floor(date.valueOf() / 1000),
},
onInsert: async ({ transaction }) => {
const newTodo = transaction.mutations[0].modified
console.log('Todo created:', newTodo)
},
})
)
// Use in component
function TodoList() {
const { data: todos } = useLiveQuery((q) =>
q.from({ todo: todosCollection })
.where(({ todo }) => !todo.completed)
.orderBy(({ todo }) => todo.created_at, 'desc')
)
const addTodo = (text: string) => {
todosCollection.insert({
id: crypto.randomUUID(),
text,
completed: false,
created_at: new Date(),
})
}
return (
<div>
{todos.map((todo) => (
<div key={todo.id}>{todo.text}</div>
))}
</div>
)
}
import { createCollection } from '@tanstack/react-db'
import { trailBaseCollectionOptions } from '@tanstack/trailbase-db-collection'
import { initClient } from 'trailbase'
import { z } from 'zod'
const trailBaseClient = initClient(`https://your-trailbase-instance.com`)
// Define schema
const todoSchema = z.object({
id: z.string(),
text: z.string(),
completed: z.boolean(),
created_at: z.date(),
})
type SelectTodo = {
id: string
text: string
completed: boolean
created_at: number
}
type Todo = z.infer<typeof todoSchema>
// Create collection
export const todosCollection = createCollection<SelectTodo, Todo>(
trailBaseCollectionOptions({
id: 'todos',
recordApi: trailBaseClient.records('todos'),
getKey: (item) => item.id,
schema: todoSchema,
parse: {
created_at: (ts) => new Date(ts * 1000),
},
serialize: {
created_at: (date) => Math.floor(date.valueOf() / 1000),
},
onInsert: async ({ transaction }) => {
const newTodo = transaction.mutations[0].modified
console.log('Todo created:', newTodo)
},
})
)
// Use in component
function TodoList() {
const { data: todos } = useLiveQuery((q) =>
q.from({ todo: todosCollection })
.where(({ todo }) => !todo.completed)
.orderBy(({ todo }) => todo.created_at, 'desc')
)
const addTodo = (text: string) => {
todosCollection.insert({
id: crypto.randomUUID(),
text,
completed: false,
created_at: new Date(),
})
}
return (
<div>
{todos.map((todo) => (
<div key={todo.id}>{todo.text}</div>
))}
</div>
)
}
Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.
Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.
