A TypeScript API framework that lets clients compose any queries they need within boundaries you control.
Wrap your tables in a stable, public interface. You can refactor your "private" tables and columns without ever breaking clients.
// api.ts
export class User extends Models.User {
// Your public interface stays stable as your schema evolves
createdAt() {
// Before: accessing from JSONB metadata
// return this.metadata['->>']('createdAt').cast(Timestamptz);
// After: direct column access (schema refactored)
return this.created_at;
}
}
// route.ts
// Compiles to the single SQL query you'd write manually.
const user = await User.select()
.orderBy((u) => u.createdAt(), { desc: true })
.limit(1)
.one(tg);
Allowed operations are just methods on your interface, including relations and mutations. Everything fully composable and typed.
// api.ts
export class User extends Models.User {
todos() {
return Todo.select().where((t) => t.user_id.eq(this.id));
}
}
export class Todo extends Models.Todos {
update({ completed }: { completed: boolean }) {
return update(Todo)
.set((t) => ({ completed }))
.where((t) => t.id.eq(this.id));
}
}
// route.ts
const user = ...
// The only way to get a todo is through a user:
const todo = await user.todos()
.where((t) => t.id.eq(todoId))
.one(tg);
// The only way to update a todo is by getting it from a user:
await todo.update({ completed: true }).execute(tg);
Give clients a composable query builder with your unescapable data boundaries. Compose queries in the client with every Postgres feature (joins, window functions, CTEs, etc.) and function as primitives.
// api.ts
export class User extends Models.User {
// ...
}
export class Todo extends Models.Todos {
// ...
}
export class Api extends RpcTarget {
getUserFromToken(token: string) {
return User.select((u) => new User(u)).where((u) => u.token.eq(token));
}
}
// Clients receive composable query builders
// not flat results
// frontend.tsx
export function TodoList({ searchQuery }: { searchQuery: string }) {
const todos = useTypegresQuery((user) => user.todos()
// Arbitrarily compose your base query...
.select((t) => ({ id: t.id, title: t.title }))
// ...using any Postgres function such as `ilike`:
.where((t) => t.title.ilike(`%${searchQuery}%`))
.execute(tg)
);
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
);
}
Developer Preview: Typegres is experimental and not production-ready. The API is evolving rapidly. Try the playground and star the repo to follow along!
This project is evolving quickly! The code examples in this README are correct, but the playground contains the very latest, most ergonomic API I'm working on.
# Install Typegres
npm install typegres
// Import the typegres library
import { typegres, select } from "typegres";
// Import your schema definition
import db from "./schema";
const tg = typegres({
/* Your db connection options */
});
const activeUsers = await select(
(u) => ({
upper: u.name.upper(),
isAdult: u.age[">"](18),
}),
{
from: db.users,
where: (u) => u.isActive,
},
).execute(tg);
console.log(activeUsers);
// Output: [{ upper: 'ALICE', isAdult: true }, { upper: 'CHARLIE', isAdult: false }]
See the examples directory for complete working examples.
src/ - Main library source codesrc/gen/ - Auto-generated PostgreSQL types and functionssite/ - Documentation website and interactive playgroundRequirements:
nix package managerdirenv for automatic environment setupTo contribute, clone the repository (run nix develop if you don't have direnv set up) and run:
# Install dependencies
npm install
# Start custom PostgreSQL instance:
./start_postgres.sh
# Run the codegen script to generate types and functions
npm run codegen
# Run tests
npm test
# Type check the code
npm run typecheck
# Build the library
npm run build
MIT © Ryan Rasti