
No ORM
No ORM in 3 steps
Section titled “No ORM in 3 steps”- Define your Postgres schema.
CREATE TABLE penguins ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, species TEXT NOT NULL, waddle_speed_kph NUMERIC NOT NULL, favourite_snack TEXT, -- Optional: not all penguins have refined palates. date_of_birth TIMESTAMP WITH TIME ZONE NOT NULL);
- Run
no-orm
against your database.
npm no-orm --config no-orm.config.ts
- Enjoy generated code in your Typescript server!
import { z } from "zod";import { type ListSqlToken, sql } from "slonik";
export const row = z.object({ id: z.number().brand<"public.penguins.id">(), name: z.string(), species: z.string(), waddle_speed_kph: z.number(), favourite_snack: z.string().nullable(), date_of_birth: z.date(),});
export type Row = z.infer<typeof row>;
export type Id = Row["id"];
export const tableFragment = sql.identifier(["public", "penguins"]);
export const columns = Object.keys(row.shape).map((col) => sql.identifier([col]),);
export const columnsFragment = sql.join(columns, sql.fragment`, `);
export function aliasColumns(alias: string): ListSqlToken { const aliasedColumns = Object.keys(row.shape).map((col) => sql.identifier([alias, col]), );
return sql.join(aliasedColumns, sql.fragment`, `);}
import { type CommonQueryMethods, sql } from "slonik";import { columnsFragment, type Id, row, type Row, tableFragment,} from "./table";
type BaseArgs = { connection: CommonQueryMethods };
export type Create = { name: string; species: string; waddle_speed_kph: number;};
export type CreateManyArgs = BaseArgs & { shapes: Create[];};
export async function createMany({ connection, shapes,}: CreateManyArgs): Promise<readonly Row[]> { const names = shapes.map((shape) => shape.name); const species = shapes.map((shape) => shape.species); const waddleSpeeds = shapes.map((shape) => shape.waddle_speed_kph);
const query = sql.type(row)` INSERT INTO ${tableFragment} ( name, species, waddle_speed_kph ) SELECT ${columnsFragment} FROM ${sql.unnest( [names, species, waddleSpeeds], ["text", "text", "numeric"], )} RETURNING ${columnsFragment}`;
return connection.any(query);}
export type CreateArgs = BaseArgs & { shape: Create;};
export async function create({ connection, shape }: CreateArgs): Promise<Row> { const result = await createMany({ connection, shapes: [shape] }); return result[0];}
export type GetManyArgs = BaseArgs & { ids: number[];};
export async function getMany({ connection, ids,}: GetManyArgs): Promise<readonly Row[]> { const query = sql.type(row)` SELECT ${columnsFragment} FROM ${tableFragment} WHERE id = ANY(${sql.array(ids, "INT")})`;
return connection.any(query);}
type GetArgs = BaseArgs & { id: Id;};
export async function get({ connection, id }: GetArgs): Promise<Row> { const result = await getMany({ connection, ids: [id] }); return result[0];}
type Update = Row;
export type UpdateManyArgs = BaseArgs & { newRows: Update[];};
export function updateMany({ connection, newRows,}: UpdateManyArgs): Promise<readonly Row[]> { const ids = newRows.map((row) => row.id); const names = newRows.map((row) => row.name); const species = newRows.map((row) => row.species); const waddleSpeeds = newRows.map((row) => row.waddle_speed_kph);
const query = sql.type(row)` UPDATE ${tableFragment} AS t SET name = u.name, species = u.species, waddle_speed_kph = u.waddle_speed_kph FROM ( SELECT * FROM ${sql.unnest( [ids, names, species, waddleSpeeds], ["id", "text", "text", "numeric"], )} ) AS u(id, name, species, waddle_speed_kph) WHERE t.id = u.id RETURNING ${columnsFragment}`;
return connection.any(query);}
type UpdateArgs = BaseArgs & { newRow: Update;};
export async function update({ connection, newRow }: UpdateArgs): Promise<Row> { const result = await updateMany({ connection, newRows: [newRow] }); return result[0];}
export type DeleteManyArgs = BaseArgs & { ids: number[];};
export async function deleteMany({ connection, ids,}: GetManyArgs): Promise<void> { const query = sql.type(row)` DELETE FROM ${columnsFragment} WHERE id = ANY(${sql.array(ids, "INT")})`;
await connection.query(query);}
type DeleteArgs = BaseArgs & { id: Id;};
// Gross that I need to add "One" here because delete is a reserved word.export async function deleteOne({ connection, id }: DeleteArgs): Promise<void> { await deleteMany({ connection, ids: [id] });}
Motivation
Section titled “Motivation”At No ORM, we believe you should be able to build a server without having to learn an ORM abstraction.
Abstractions work by hiding implementation details which makes using your database simpler, but also harder to understand and deeply configure. You gain simplicity but lose control.
Our opinion is that PostgreSQL is already a great abstraction. Adding another layer on top often just obscures what your database is doing and increasing the likelihood to make poor database design decisions.
We recognise however that ORMs do have their benefits however, so the motivation of No ORM
is to capture all of the best bits of an ORM while also preserving what makes PostgreSQL so great.
Philosophy
Section titled “Philosophy”We believe in a schema-first approach to server design.
With a traditional ORM, you start by defining your entities and the ORM will generate your schema.

With No ORM
, you start by defining your schema and we give you your entities!

Why schema-first?
Section titled “Why schema-first?”Most applications are CRUD operations combined in a way to solve a problem (not to be taken as a critique). Therefore, since less code is easier to maintain, we should try to minimise any additional code written outside of these operations.
This additional code can come in the form of critical business logic, but it can also come in the form of tech-debt masking poor database design.
A schema-first approach means that you can design your database without wading through ORM abstractions, helping you achieve your goal of reducing complexity and building maintainable software.
It’s not for everyone
Section titled “It’s not for everyone”You might love your ORM for various reasons.
Maybe you’re not familiar with SQL and prefer the abstraction. Maybe you believe your ORM provides you with useful features that raw Postgres doesn’t. Maybe your ORM is so coupled with the framework you are using that it’s impractical to use anything else.
That is completely understandable and if so, this tool probably isn’t for you!