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 discovers entities, handlers, collectors, and seeds under fronds/. 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 '@frond/blog/entities/Post';

@fougere/nuxt reads the scan and registers a @frond/<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 '@frond/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' },

  // 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.

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.