What is Fougere
Fougere is a TypeScript framework built around two ideas.
Single-schema. One entity class declares your data once — and judges its own input:
the same validate() runs in the browser and at the façade. That judge is itself a
projection, derived from the shape axis, but a normative one shipped with the class:
every other projection must agree with it, and it cannot drift on its own. SQLite tables,
GraphQL types, form contracts and API surfaces are projections of that declaration —
nothing is written twice.
import { entity, primary, text, created, oneOf, date, readOnly, optional } from '@fougere/schema';
export default class Post extends entity({
id: primary(),
title: text({ min: 1, max: 160 }),
body: optional(text()),
createdAt: created(),
status: readOnly(oneOf('draft', 'published', { default: 'draft' })),
publishedAt: readOnly(optional(date())),
}) {}
The schema — declared once
class Post extends entity({
id: primary(),
title: text({ min: 1 }),
status: readOnly(oneOf(
'draft', 'published')),
}) {}Post.validate(input)derived from the shape · ships with the class
API surface
post.list · post.publish
Database table
auto-DDL → SQLite
TypeScript type
function render(p: Post)
Form contract
useFormFor(Post)
GraphQL type
type Post { … }
Designation & DI
useQuery(Post, 'list')
One nucleus, six projections — change the declaration, every projection follows.
That single class is simultaneously:
- the TypeScript type of a row (
function render(p: Post)— noInfer<typeof …>), - the validator of client input (
Post.validate(input)), - the metadata every adapter reads (
Post.getFields()), - the designation pages use to call operations (
useQuery(Post, 'list')), - the nominal name dependency injection matches in handler signatures (
user: User | null).
The gradient. Business logic lives in Fronds — self-contained modules of entities, handlers, collectors and seeds. A Frond runs in-process today and in its own process tomorrow, behind JSON-RPC 2.0, with identical user code. The entire topology statement is one line of config:
// fougere.config.ts
remotes: { blog: 'http://127.0.0.1:4100' }
There is no RPC without travel: a call is a value (entity, operation, invocation); the
runner executes it directly in memory when the Frond is local and frames it onto the wire
when it is remote. Transports move the value — they never reshape it.
So the split costs the hop and the JSON that rides it, and nothing else: no serialization the local path avoids, no framework tax layered on top of the network.
Reading order
Concepts — Philosophy · The Frond
Server side — Getting started · Existing Nuxt app · The CLI · Entities · Views · Standard Schema · Handlers · Presenters · Collectors · Errors · Seeds · Facts
Client side — Queries & commands · Forms · Session · invoke
Topology — The gradient · Surfaces · Deployment
Status. Fougere is in alpha: the
@fougere/*packages are on npm under thealphatag, and this documentation describes the API as it exists in the repository today. This site runs on it.