What is Fougere

Fougere is a TypeScript framework built on one idea: you declare the domain, and everything else derives from it — down to the process it runs in.

Stated in the negative, it becomes checkable: the declaration names nothing outside itself. No table, no protocol, no host, no address appears in it. Two consequences follow, and they are ordinarily sold as two separate features:

The declaration does not name…so that thing is…its usual name
its table, its GraphQL type, its form, its judgederived from itsingle-schema
its host, its storage, its door, its addresschosen outside itthe gradient

One rule read in two directions — what a declaration produces, and what it may be surrounded by. The rest of this page is those two readings.

What is derived from it

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) — no Infer<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 type dependency injection matches in handler signatures (user?: User).

What is chosen outside it

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.

The four families the rule refuses to name, drawn — the gradient being the fourth one read as a movement rather than a list:

The Frond — what you wrote

entities · operations · its judge · its facts

it names none of the four

The host

Nuxt · Next · SvelteKit

TanStack · React Router · Express · none

The storage

SQLite · Postgres

MySQL · SQL Server

The door

in memory · JSON-RPC

REST · GraphQL

The place

same process · another process

another repo · another language

read as a movement, this one is the gradient

The Frond names none of the four families. Each is chosen outside it — and the last one, read as a movement rather than a list, is the gradient.

And it is checkable with diff: the five demos that serve this same blog under Next, TanStack Start, React Router, SvelteKit and Express share a fronds/ directory that is identical byte for byte, and three of those hosts need no Fougere package at all. So progressive here means only this — each step outward states its price, and none of them asks you to rewrite what you wrote.

Reading order

ConceptsPhilosophy · The Frond · The base

Server sideAn app you already have · Bring your schema · Getting started · The CLI · Entities · Views · Standard Schema · Handlers · Presenters · Collectors · Errors · Seeds · Facts · Storage · Repositories

Client sideQueries & commands · Forms · Session · invoke

TopologyThe gradient · Surfaces · Deployment · Hosts · Sources

Status. Fougere is in alpha: the @fougere/* packages are on npm under the alpha tag, and this documentation describes the API as it exists in the repository today. This site runs on it.

Built with Fougere — this site runs on the framework it documents.