Getting started

npm create fougere shop --frond blog --app nuxt
cd shop && pnpm install && pnpm dev

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

Application structure

A Fougere app is a Nuxt app plus a fronds/ directory and one config file:

my-app/
  fougere.config.ts       ← persistence, auth, topology
  nuxt.config.ts          ← modules: ['@fougere/nuxt']
  fronds/
    blog/
      entities/Post.ts
      handlers/PostHandler.ts
      collectors/CurrentUserCollector.ts
      seeds/Post.seed.ts
    user/                 ← a second domain
      entities/User.ts
  app/
    pages/                ← ordinary Nuxt pages, talking through the primitives

The scanner reads seven convention directories under fronds/entities/, handlers/, presenters/, collectors/, seeds/, and services/ + repositories/, which are two spellings of one provider list. File location and class name are used for registration; no additional wiring file is needed.

Each directory directly under fronds/ defines a Frond. A page imports an entity using that directory name:

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

@fougere/nuxt reads the scan and registers a @fronds/<name> alias per Frond, so that import works for blog and user alike — nothing to add, no package.json, no pnpm-workspace.yaml entry. Renaming a Frond, or importing one from outside a Nuxt app, is where a Frond's package.json earns its keep: see Frond — naming and importing.

fougere new writes the same directories one level out — the app under apps/<name>/, the Fronds shared at the workspace root, fougere: { root: '../..' } in nuxt.config.ts — so that several apps consume the same domains. One app, one directory: the shape above.

One domain, no fronds/ at all

The project root can use the same convention as a directory under fronds/. A single-domain app can therefore place its entities and handlers directly at the root:

my-shop/
  fougere.config.ts
  nuxt.config.ts
  entities/Product.ts       ← the root IS the Frond, named after the directory
  handlers/ProductHandler.ts
  app/pages/

fougere new my-shop --flat --frond blog writes exactly that. import Product from '@fronds/my-shop/entities/Product' — same alias rule, no fronds/ segment, and the word never comes up until there are two domains to tell apart. The root needs at least an entities/ to count; a lone services/ is an ordinary directory name.

When the second domain arrives it goes to fronds/billing/ and the root Frond stays where it is — nothing moves, no import is rewritten. fronds/ is not what defines a Frond, it is where the others live.

fougere.config.ts — reference

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

export default defineFougere({
  // Persistence. Three forms:
  //   'sqlite'                          → in-memory on the Nuxt side (reseeded each reload)
  //   { dialect: 'sqlite', path: '…' }  → file-backed (survives reloads and deploys)
  //   false                             → no db
  db: { dialect: 'sqlite', path: '.data/app.db' },

  // Where rows live, when they do not all live in `db`. An entity this does not
  // name stays in the default source, so one database declares nothing at all.
  // sources: { archive: { path: '/mnt/legacy/catalog.db', entities: ['Book'] } },

  // 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.
  // remotes: { blog: 'http://127.0.0.1:4100' },

  // Auth (optional) — better-auth behind a thin translation layer.
  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. At boot, SQLite auto-DDL creates missing tables and adds missing columns. Renames, removals and type changes require an explicit migration. Seeds run after that schema sync and call operations with their usual validation.

An app with several databases declares them under sources:, and a Frond that must query across them declares reads: — see Sources.

Run

pnpm install
pnpm dev          # scan → sync additive schema → seed → serve on :3000

Check the boot log for the number of discovered Fronds:

INF [boot:app] scanned 2 frond(s) in 467ms
INF [boot] ready in 483ms — 2 frond(s) + auth (/auth)

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

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