[!NOTE]
Every example below is complete. If a snippet does not run as written, that
is a bug in these docs — the SQL shown alongside each example is what prorm
actually generates, recorded on SQLite.
my-app/
package.json
index.ts # everything on this page, in ordernpm install ts-prorm-orm better-sqlite3pnpm add ts-prorm-orm better-sqlite3yarn add ts-prorm-orm better-sqlite3bun add ts-prorm-orm better-sqlite3ts-prorm-orm ships its own TypeScript types, so@types package to add — and it ships no database drivers, sopg,mysql2 or another driver to target a different engine — see[!TIP]
None of this needs a build step if you are working in a browser. One
script tag fromhttps://prod-orm.io/cdn/prorm.min.jsgives you the same
models and queries against SQLite in the page — see
In the browser.
import { Prorm } from 'ts-prorm-orm';
const db = new Prorm({
dialect: 'sqlite',
storage: ':memory:', // a file path keeps the data; ':memory:' does not
});
await db.connect();storage: ':memory:' gives you a throwaway database that disappears when theimport { Prorm } from 'ts-prorm-orm';
// npm install pg
const db = new Prorm({
dialect: 'postgres',
host: 'localhost',
port: 5432,
database: 'app',
username: 'app',
password: process.env.PGPASSWORD,
});import { Prorm } from 'ts-prorm-orm';
// npm install mysql2
const db = new Prorm({
dialect: 'mysql',
host: 'localhost',
port: 3306,
database: 'app',
username: 'app',
password: process.env.MYSQL_PASSWORD,
});import { Prorm } from 'ts-prorm-orm';
// npm install better-sqlite3
const db = new Prorm({ dialect: 'sqlite', storage: './app.db' });import { Prorm } from 'ts-prorm-orm';
// npm install mssql
const db = new Prorm({
dialect: 'mssql',
host: 'localhost',
port: 1433,
database: 'app',
username: 'sa',
password: process.env.MSSQL_PASSWORD,
});import { DataTypes } from 'ts-prorm-orm';
const User = db.define('User', {
id: { type: DataTypes.INTEGER(), primaryKey: true, autoIncrement: true },
name: { type: DataTypes.STRING(100), allowNull: false },
email: { type: DataTypes.STRING(255), allowNull: false, unique: true },
age: { type: DataTypes.INTEGER() },
});User (singular); the table is users (pluralised) unlesstableName. Types are covered in Data types.await db.sync();createdAt and updatedAt appear because timestamps are on by default; pass{ timestamps: false } to the model to drop them.[!NOTE]
On an open connection,define()already creates the table in the
background;sync()waits for that and creates anything still missing. That
is why, in a real app, you define every model (and its associations) first
and callconnect()andsync()once at startup — see step 8.
[!WARNING] sync({ force: true })drops the table first. It is a convenience for
development and tests, not a migration tool. For anything whose data you care
about, use migrations.
const ada = await User.create({
name: 'Ada Lovelace',
email: 'ada@example.com',
age: 36,
});
console.log(ada.id); // 1DEFAULT CURRENT_TIMESTAMPimport { Op } from 'ts-prorm-orm';
// everyone
const all = await User.findAll();
// one, by primary key
const one = await User.findByPk(1);
// filtered, ordered, limited
const adults = await User.findAll({
where: { age: { [Op.gte]: 18 } },
order: [['name', 'ASC']],
limit: 10,
});SELECT TOP(10) * FROM [users] WHERE [age] >= 18 ORDER BY [name] ASC;.Op holds the comparison operators. A bare value means equality; anythingIN, LIKE, null checks — is an operator object. See[!IMPORTANT] ordertakes an array of[column, direction]pairs. A bare string likeorder: 'name DESC'is pasted into the SQL as it is —SELECT * FROM "users" ORDER BY name DESC— unquoted and unchecked. It works
for a literal you wrote, and is an injection hole for anything that came from
a request. Use the array form.
// on an instance you already hold
ada.age = 37;
await ada.save();
// or in one statement, without loading anything
await User.update({ age: 37 }, { where: { email: 'ada@example.com' } });
await User.destroy({ where: { id: 1 } });save() writes the whole row back; the static update() writes only theconst Post = db.define('Post', {
id: { type: DataTypes.INTEGER(), primaryKey: true, autoIncrement: true },
title: { type: DataTypes.STRING(200), allowNull: false },
userId: { type: DataTypes.INTEGER() }, // the foreign key column
});
User.hasMany(Post); // a user has many posts
Post.belongsTo(User); // and each post belongs to one user
await db.sync();
const post = await Post.create({ title: 'On computing', userId: ada.id });
// fetch users with their posts: one query for users, one for all their posts
const withPosts = await User.findAll({ include: [Post] });
withPosts[0].posts; // [{ id: 1, title: 'On computing', userId: 1, … }]hasMany puts the foreign key on the target (posts.userId);belongsTo declares the same relationship from the other end so you can[!WARNING]
Declare theuserIdcolumn yourself. BecausePostis defined on an open
connection, its table is created at once — beforehasManyruns — so the
association cannot add the column, and without it theINSERTfails withtable posts has no column named userId. For the same reason there is noREFERENCESclause here. In an app, define every model and association
beforeconnect(), andsync()creates the tables with their foreign keys.
import { Prorm, DataTypes, Op } from 'ts-prorm-orm';
const db = new Prorm({ dialect: 'sqlite', storage: ':memory:' });
await db.connect();
const User = db.define('User', {
id: { type: DataTypes.INTEGER(), primaryKey: true, autoIncrement: true },
name: { type: DataTypes.STRING(100), allowNull: false },
email: { type: DataTypes.STRING(255), allowNull: false, unique: true },
age: { type: DataTypes.INTEGER() },
});
await db.sync({ force: true });
await User.bulkCreate([
{ name: 'Ada Lovelace', email: 'ada@example.com', age: 36 },
{ name: 'Alan Turing', email: 'alan@example.com', age: 41 },
]);
const adults = await User.findAll({
where: { age: { [Op.gte]: 40 } },
order: [['name', 'ASC']],
});
console.log(adults.map((u) => u.name)); // [ 'Alan Turing' ]
await db.close();FindOptions becomes SQL.