Skip to content

Getting started

This guide walks through a complete flow: configure jsorm.config.ts, define models, create a runtime instance, and run typed queries.

  1. Define models

    Import defineModel and t. One definition drives both TypeScript inference and runtime metadata.

    // src/schema/main/user.ts
    import { defineModel, t } from '@jsorm/core';
    export const Role = defineModel('roles', {
    id: t.number().primary(),
    name: t.string().unique(),
    });
    export const User = defineModel('users', {
    id: t.number().primary(),
    name: t.string(),
    email: t.string().optional(),
    active: t.boolean().default(true),
    createdAt: t.date(),
    role: t.belongsTo(Role),
    });

    Export inferred types when needed:

    import type { InferInput, InferModel } from '@jsorm/core';
    type UserRecord = InferModel<typeof User>;
    type UserInput = InferInput<typeof User>;
  2. Create jsorm.config.ts

    Keep one config for runtime + CLI:

    // jsorm.config.ts
    import { defineConnectionSource, defineJsormConfig } from '@jsorm/core';
    import { pgAdapter } from '@jsorm/pg';
    const main = defineConnectionSource({
    adapter: pgAdapter({
    name: 'main',
    connectionString: process.env.DATABASE_URL!,
    pool: { min: 2, max: 10 },
    }),
    });
    export default defineJsormConfig({
    connectionSources: { main },
    defaults: {
    connectionSource: 'main',
    },
    });
  3. Create a runtime instance

    Use createJsorm() for Node runtime boundaries:

    import { createJsorm } from '@jsorm/core';
    const db = await createJsorm().init();

    If you ran pnpm exec jsorm init, you can also start from the generated db.ts helper.

  4. Insert data

    await db.insert(User, {
    name: 'Alice',
    email: 'alice@example.com',
    createdAt: new Date(),
    role: { connect: 1 },
    });
  5. Read typed data

    const users = await db.get(User, {
    select: {
    id: true,
    name: true,
    role: { name: true },
    },
    where: {
    active: true,
    role: { name: { eq: 'admin' } },
    },
    orderBy: [{ name: 'asc' }],
    });
    // users: Array<{ id: number; name: string; role: { name: string } }>
  6. Update and delete safely

    where is required on write operations.

    await db.update(User, {
    data: { active: false },
    where: { id: { eq: 10 } },
    });
    await db.delete(User, {
    where: { id: { eq: 10 } },
    });

Use the namespaced escape hatch only: db.raw.*.

const rows = await db.raw.execute(
'SELECT COUNT(*) AS total FROM users WHERE active = $1',
[true],
);
const compiled = await db.raw.compile(
'SELECT id, name FROM users WHERE active = $1',
[true],
);
const health = await db.healthCheck();
// { main: 'ok' }
process.on('SIGTERM', async () => {
await db.close();
process.exit(0);
});

For isolated tests, use createJsormTest():

import { createJsormTest } from '@jsorm/core';
const testDb = await createJsormTest().init();
  1. Use createJsorm() from @jsorm/core in Node runtime code, or from @jsorm/runtime in edge/fetch-only code.
  2. Use createJsormTest() for test isolation instead of sharing app runtime state.
  3. Keep where clauses explicit on all writes.
  4. Prefer JSON DSL as default; reserve db.raw.* for cases the DSL cannot express cleanly.
  5. Keep CLI, migrations, and deploy steps on @jsorm/node.