Remult
One TypeScript entity · a secure REST API, a type-safe client, and an admin UI, all from the same class.
In a typical stack, you write the same data shape three times. A database table. Backend REST routes. And a typed client on the frontend. Then you keep all three in sync by hand, and every drift becomes a runtime bug. Remult flips that: you write the shape once.
Your entity is the single source of truth
- A secure REST API · full CRUD with paging, sorting, and filtering, zero routes.
- A type-safe frontend client · the same typed data access, over the wire.
- A backend query builder · Postgres, MySQL, MongoDB, SQLite, and more.
Remult is a fullstack framework for TypeScript, built on one idea. Your entity class is the single source of truth. From it you get a secure REST API. A type-safe client that gives your frontend the same typed access, over the wire. And a backend query builder, across Postgres, MySQL, MongoDB, and more. It is framework-agnostic, adopted one entity at a time.
Define it once
@Entity('products', {
allowApiCrud: true,
allowApiDelete: 'admin',
})
export class Product {
@Fields.id()
id!: string
@Fields.string()
name = ''
@Fields.number()
unitPrice = 0
}
One decorated class · an id, a name, a price.
It starts with one decorated class. Here is your product entity: an id, a name, and a price. The options are where the power sits. One flag turns on the full REST API; another restricts deletes to admins only. This one file is the single source of truth.
Serve it: the REST API, zero routes
import { remultApi } from 'remult/remult-express'
app.use(
remultApi({ entities: [Product] }),
)
Full CRUD for /api/products · paging,
sorting, filtering. You wrote no route handlers.
Now serve it. You pass your entity to Remult's request handler and mount it on your server. That single line is a complete REST API for products, with paging, sorting, and filtering. You wrote no route handlers at all.
The payoff: type-safe on the frontend
const productsRepo = repo(Product)
// insert: typed, no fetch, no route
await productsRepo.insert({
name: 'Tofu', unitPrice: 5,
})
// query: same entity, type-checked
const products = await productsRepo.find({
where: { unitPrice: { '>=': 5 } },
orderBy: { name: 'asc' },
})
Rename a field, and every wrong query stops compiling.
Here is the payoff. On the frontend, the same entity gives you fully typed access. Insert a product: no fetch call, no route. Query for everything priced at five or more, sorted by name. Checked by the compiler. Rename a field, and every wrong query stops compiling.
One more win: the admin UI
admin: true → /api/admin
Browse every table, see relations as a diagram, get realtime updates · all from the same entity.
One more win from that same entity. Turn on the admin option, and Remult serves a full admin panel at slash api slash admin. Browse and edit every table, see your relations as a diagram, and get realtime updates on connected clients. All from that one entity.
One entity, in sync by construction
- Secure REST API · full CRUD, zero routes.
- Type-safe client · same access, checked.
- Admin UI · free, from the same class.
- Incremental · your framework, your database.
npm init remult@latest · remult.dev
That is the arc. One entity becomes a secure REST API, a type-safe client, and an admin panel, all checked by the compiler and always in sync. It works with your framework and your database, and you add it one entity at a time. Start here: remult dot dev.


















