A lightweight, type-safe ORM for PostgreSQL with LINQ-style queries and automatic type inference
npm install linkgress-ormselect(), where(), orderBy(), and groupBy(). You also get magic SQL string interpolation for when you need raw SQL power without sacrificing type safety.
DbColumn, no decorators needed
DbContext pattern with method chaining
count(), sum(), max(), min() return proper types
.where().update() and .where().delete() with RETURNING support
pg and postgres npm packages
bash
npm install linkgress-orm postgres
`
See Installation Guide for detailed setup.
$3
`typescript
import { DbEntity, DbColumn } from 'linkgress-orm';
export class User extends DbEntity {
id!: DbColumn;
username!: DbColumn;
email!: DbColumn;
posts?: Post[]; // Navigation property
}
export class Post extends DbEntity {
id!: DbColumn;
title!: DbColumn;
userId!: DbColumn;
views!: DbColumn;
user?: User; // Navigation property
}
`
$3
`typescript
import { DbContext, DbEntityTable, DbModelConfig, integer, varchar } from 'linkgress-orm';
export class AppDatabase extends DbContext {
get users(): DbEntityTable {
return this.table(User);
}
get posts(): DbEntityTable {
return this.table(Post);
}
protected override setupModel(model: DbModelConfig): void {
model.entity(User, entity => {
entity.toTable('users');
entity.property(e => e.id).hasType(integer('id').primaryKey().generatedAlwaysAsIdentity({ name: 'users_id_seq' }));
entity.property(e => e.username).hasType(varchar('username', 100)).isRequired();
entity.property(e => e.email).hasType(varchar('email', 255)).isRequired();
entity.hasMany(e => e.posts, () => Post)
.withForeignKey(p => p.userId)
.withPrincipalKey(u => u.id);
});
model.entity(Post, entity => {
entity.toTable('posts');
entity.property(e => e.id).hasType(integer('id').primaryKey().generatedAlwaysAsIdentity({ name: 'posts_id_seq' }));
entity.property(e => e.title).hasType(varchar('title', 200)).isRequired();
entity.property(e => e.userId).hasType(integer('user_id')).isRequired();
entity.property(e => e.views).hasType(integer('views')).hasDefaultValue(0);
entity.hasOne(e => e.user, () => User)
.withForeignKey(p => p.userId)
.withPrincipalKey(u => u.id);
});
}
}
`
$3
`typescript
import { eq, gt } from 'linkgress-orm';
import { PostgresClient } from 'linkgress-orm';
// Create a database client with connection pooling
const client = new PostgresClient('postgres://user:pass@localhost/db');
// Create a DbContext instance - reuse this across your application!
const db = new AppDatabase(client);
// Create schema
await db.ensureCreated();
// Insert
await db.users.insert({ username: 'alice', email: 'alice@example.com' });
// Query with filters
const activeUsers = await db.users
.where(u => eq(u.username, 'alice'))
.toList();
// Nested collection query with aggregations
const usersWithStats = await db.users
.select(u => ({
username: u.username,
postCount: u.posts.count(), // Automatic type inference - no casting!
maxViews: u.posts.max(p => p.views), // Returns number | null
posts: u.posts
.select(p => ({ title: p.title, views: p.views }))
.where(p => gt(p.views, 10))
.toList('posts'),
}))
.toList();
// Fluent update with RETURNING
const updatedUsers = await db.users
.where(u => eq(u.username, 'alice'))
.update({ email: 'alice.new@example.com' })
.returning(u => ({ id: u.id, email: u.email }));
// Fluent delete
await db.users
.where(u => eq(u.username, 'old_user'))
.delete();
// Note: Only call dispose() when shutting down your application
// For long-running apps (servers), keep the db instance alive
// and dispose on process exit (see Connection Lifecycle docs)
`
Result is fully typed:
`typescript
Array<{
username: string;
postCount: number;
maxViews: number | null;
posts: Array<{ title: string; views: number }>;
}>
`
Documentation
$3
- Getting Started Guide - Complete walkthrough for beginners
- Installation - Setup and installation instructions
- Database Clients - Choose between pg and postgres`, connection pooling, and lifecycle management