Getting started

Create an app:

npm create fougere

The project is written for the package manager that ran the command — see the CLI.

Alpha. The @fougere/* packages are on npm under the alpha tag: the version is the whole promise, and the surface can still move. This site (site/) and demos/nuxt-blog are complete references. Already have a Nuxt app? Add Fougere to it.

demos/nuxt-blog43 files
Local only
fronds/blog/entities/Author.ts
import { entity, primary, text } from '@fougere/schema';

export default class Author extends entity({
  id: primary(),
  name: text({ min: 1, max: 100 }),
  email: text({ min: 3 }),
}) {}

What an app looks like

You get a Nuxt app, a fronds/ directory and one config file:

my-app/
  fougere.config.ts       ← database, auth, where each part runs
  fronds/
    blog/                 ← one domain of your app
      entities/Post.ts
      handlers/PostHandler.ts
      collectors/CurrentUserCollector.ts
      seeds/Post.seed.ts
    user/                 ← another one
      entities/User.ts
  apps/
    nuxt/
      nuxt.config.ts      ← modules: ['@fougere/nuxt']
      app/pages/          ← ordinary Nuxt pages

Each directory under fronds/ is a frond: one domain of your app, with its data and what it does. Inside it, the folder a file sits in says what it is: an entity in entities/, a handler in handlers/, a service in services/, and so on. You never register anything: Fougere finds each class by where it lives and what it is called.

A page imports from a frond by its name:

import Post from '@fronds/blog/entities/Post.js';

The @fronds/blog alias exists on its own, for every frond. You add no package.json and no workspace entry. A frond needs its own package.json only to be renamed or imported from outside a Nuxt app: see Frond — naming and importing.

fougere new puts the app under apps/<name>/ and the fronds at the workspace root, so several apps can share the same domains. The app finds them on its own.

fougere.config.ts — reference

import '@fougere/adapter-sql';
import { defineFougere } from '@fougere/core';
import { betterAuth } from '@fougere/auth-better';
import User from './fronds/user/entities/User';

export default defineFougere({
  // Where rows live. The entry that lists no entities holds every entity the others do not.
  //   { type: 'sqlite', path: '…' } → a SQLite file
  //   { type: 'pg', url: '…' }      → Postgres, once `pg` is installed (mysql, mssql alike)
  //   no `sources:` at all          → rows in memory, on every host, and the boot says so
  // The import above is what makes these types available — nothing is imported for you.
  sources: {
    main: { type: 'sqlite', path: '.data/app.db' },
    // archive: { type: 'sqlite', path: '/mnt/legacy/catalog.db', entities: ['Book'] },
  },

  fronds: {
    // Topology. A Frond listed here is STILL scanned (its entities keep feeding
    // metadata to forms and validation) but is not hosted locally: calls travel
    // as JSON-RPC to the process that hosts it. Comment the line → in-process.
    // blog: 'http://127.0.0.1:4100',
    // Auth (optional) — better-auth behind a thin translation layer. It is a frond like any
    // other: an entry of `fronds:`, left out by `only:` wherever the engine should not start.
    auth: betterAuth({
      user: User,                             // your entity extending AuthUser
      secret: process.env.AUTH_SECRET!,       // 32+ chars
      baseUrl: process.env.SITE_URL ?? 'http://localhost:3000',
      basePath: '/auth',                      // mounts /auth/** (sign-in, sign-up, sign-out…)
      trustedOrigins: ['http://localhost:3000'],
      sessionTtl: 30 * 24 * 60 * 60 * 1000,
      providers: {
        credential: { minPasswordLength: 8, autoSignIn: true },
        // google: { clientId: …, clientSecret: … },
      },
    }),
  },
});

Tables are derived from your entities, and written by fougere migrate — never by the boot. On your database, --latest follows the entities as they are; every other database receives only what fougere freeze recorded. A boot reads the database against the entities and refuses when it is behind, naming each missing table and column. A rename is declared on the entity (previous:), see How a schema moves. Seeds run once the schema check passes and write their rows by the storage, which still judges them.

A second database is a second entry of sources:, listing the entities it holds, and a Frond that must query across them declares reads: — see Sources.

Run

npm install
npm run migrate   # fougere migrate --latest --apply — create what the entities declare
npm run dev       # scan → write the statement → check the schema → seed → serve on :3000

The last lines fougere new prints are already written for your package manager.

The first page says which host serves the app and links to the three guides: business logic, data, topology.

Check the boot log for the number of discovered Fronds:

INF [boot] ready in 483ms — 3 frond(s)

Next: Entities — the field vocabulary and the four axes.

Built with Fougere and Nuxt, hosted on GitHub Pages.