Documentation Index
Fetch the complete documentation index at: https://mintlify.com/remix-run/remix/llms.txt
Use this file to discover all available pages before exploring further.
Remix provides a comprehensive data management layer through two complementary packages:
data-table - Typed relational query toolkit with ORM capabilities
data-schema - Runtime validation and coercion for data schemas
Features
data-table
- One API Across Databases: Same query and relation APIs across PostgreSQL, MySQL, and SQLite adapters
- Two Complementary Query Styles: Chainable query builder for advanced queries or high-level database helpers for common CRUD
- Type-Safe Reads: Typed
select, relation loading, and predicate keys
- Optional Runtime Validation: Add
validate(context) at the table level for create/update validation and coercion
- Relation-First Queries:
hasMany, hasOne, belongsTo, hasManyThrough, and nested eager loading
- Safe Scoped Writes:
update/delete with orderBy/limit run safely in a transaction
- First-Class Migrations: Up/down migrations with schema builders, runner controls, and dry-run planning
- Raw SQL Escape Hatch: Execute SQL directly with
db.exec(sql…)
data-schema
- Standard Schema v1 Compatible: Works with any Standard Schema-compatible library
- Sync-First: Minimal API surface with synchronous validation
- Runtime Validation: Validate and coerce data at runtime with custom error messages
- Composable Checks: Reusable validation rules with
.pipe() and .refine()
Installation
npm i remix
npm i pg # or mysql2, or better-sqlite3
Quick Start
Define Tables
import { Pool } from 'pg'
import { column as c, createDatabase, table, hasMany } from 'remix/data-table'
import { createPostgresDatabaseAdapter } from 'remix/data-table-postgres'
let users = table({
name: 'users',
columns: {
id: c.uuid(),
email: c.varchar(255),
role: c.enum(['customer', 'admin']),
created_at: c.integer(),
},
})
let orders = table({
name: 'orders',
columns: {
id: c.uuid(),
user_id: c.uuid(),
status: c.enum(['pending', 'processing', 'shipped', 'delivered']),
total: c.decimal(10, 2),
created_at: c.integer(),
},
})
let userOrders = hasMany(users, orders)
let pool = new Pool({ connectionString: process.env.DATABASE_URL })
let db = createDatabase(createPostgresDatabaseAdapter(pool))
Query Builder
Use db.query(table) for joins, custom shape selection, eager loading, or aggregate logic:
import { eq, ilike } from 'remix/data-table'
let recentPendingOrders = await db
.query(orders)
.join(users, eq(orders.user_id, users.id))
.where({ status: 'pending' })
.where(ilike(users.email, '%@example.com'))
.select({
orderId: orders.id,
customerEmail: users.email,
total: orders.total,
placedAt: orders.created_at,
})
.orderBy(orders.created_at, 'desc')
.limit(20)
.all()
CRUD Helpers
For common operations, use the simplified CRUD helpers:
// Find by primary key
let user = await db.find(users, 'u_001')
// Create with metadata
let createResult = await db.create(users, {
id: 'u_002',
email: 'sam@example.com',
role: 'customer',
created_at: Date.now(),
})
// Update by primary key
let updatedUser = await db.update(users, 'u_003', { role: 'admin' })
// Delete by primary key
let deleted = await db.delete(users, 'u_002')
Validation with data-schema
Integrate runtime validation into your tables:
import { object, string, parse } from 'remix/data-schema'
import { email, minLength } from 'remix/data-schema/checks'
let users = table({
name: 'users',
columns: {
id: c.uuid(),
email: c.varchar(255),
username: c.varchar(50),
},
validate({ value }) {
let schema = object({
email: string().pipe(email()),
username: string().pipe(minLength(3)),
})
try {
let validated = parse(schema, value)
return { value: validated }
} catch (error) {
return {
issues: [{ message: error.message }],
}
}
},
})
Type Safety
Both packages are fully typed:
import type { TableRow, InferOutput } from 'remix/data-table'
import type { InferOutput as InferSchemaOutput } from 'remix/data-schema'
// Table row types
type User = TableRow<typeof users>
// { id: string; email: string; role: 'customer' | 'admin'; created_at: number }
// Schema output types
let UserSchema = object({ name: string(), age: number() })
type UserOutput = InferSchemaOutput<typeof UserSchema>
// { name: string; age: number }
Next Steps