What is Fougere
You write your data and what your app does with it. Fougere writes the rest: the table, the validation, the API, the forms, the typed client, the tests. And any part of your app can later run in its own process without a line of your code changing.
What you write
A post, in a blog:
// fronds/blog/entities/Post.ts
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' })),
}) {}
And what the blog does with it:
// fronds/blog/handlers/PostHandler.ts
export default class PostHandler extends Crud(Post) {
constructor(private posts: Repository<Post>) {
super(posts);
}
async publish(id: Post['id'], user?: User): Promise<Post> {
const post = await requireOwn(this.posts, id, user);
if (post.status === 'published') {
throw new FougereError({ code: ErrorCode.CONFLICT, message: 'Already published' });
}
return this.posts.update(id, { status: 'published' });
}
}
fronds/blog/ is a frond: one domain of your app, with its data and what it does.
What you get
Nothing below is written by you:
- the type —
Postis the TypeScript type of a row, noInfer<…>; - the validation — the same rules in the browser and on the server,
titlerefused past 160 characters in both; - the table — and the migration when a field changes (Storage);
- the API —
POST /api/blog/posts/:id/publish, a GraphQL mutation, a JSON-RPC call (Surfaces); - the page's client —
useCommand(post, 'publish'), typed down to the errors it can answer:CONFLICThere (Queries & commands); - the form —
useFormFor(Post)knowsstatusis not the user's to write (Forms); - the tests — one line,
checkAll(await testApp()), tries every refusal the fields imply (Testing); - a command line — the same operations as a terminal tool, flags read off the fields (demo);
- a refusal at startup — when something cannot hold, the app does not start, and says which file to fix.
Where it runs
Today the blog runs inside your Nuxt app. Tomorrow it runs on its own server:
// fougere.config.ts
fronds: { blog: 'http://127.0.0.1:4100' }
That line is the whole change. The page still calls publish, the handler is the same
file; the call now crosses the network, and costs that trip and nothing else
(The gradient).
Why it holds
Your code never says how it runs: no SQL, no route, no HTTP, no host. It says what your data is and what your app does. Everything above is read from that, so nothing can drift from it, and one field changed is one place to read in a diff.
The longer version: Philosophy.
Reading order
Concepts — Philosophy · The Frond · The base
Server side — An app you already have · Bring your schema · Getting started · The CLI · Entities · Views · Standard Schema · Handlers · Presenters · Collectors · Errors · Seeds · Facts · Storage · Repositories
Client side — Queries & commands · Forms · Session · invoke
Topology — The gradient · Surfaces · Deployment · Hosts · Sources
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.