Skip to main content
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: '[email protected]',
  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

Build docs developers (and LLMs) love