Application Conventions
Guide 91
How the demo applications under apps/ are named and laid out. The rules keep
the ladder readable, keep package names stable across renumbering, and make a
file’s role obvious before it is opened. They apply to new apps and to files
touched while changing an existing app; the migration backlog
lists the older shapes that are deliberately left for follow-up work.
Audience: Framework contributors · Prerequisites: Workspace bootstrapped; guide 37 · Reference app: apps/05-ndigo-api
What you’ll learn
Section titled “What you’ll learn”- Name directories, packages, and apps.
- Pair
-weband-apiapplications. - Apply role-based file suffixes and folder layout.
- Keep the demonstration ladder readable.
- Work through the deferred normalization backlog.
1. Directory and package names
Section titled “1. Directory and package names”Every application lives at:
apps/NN-ndigo-<slug>NNis a two-digit reading-order number (01–99). It is presentation only — it never appears in a package name, import specifier, or nx target.<slug>is kebab-case and names the application, not its technology:todo-web,todo-api,httpbin,petclinic.- The package name is
@ndigo/<slug>— the directory’sndigo-prefix and number are dropped. apps/05-ndigo-api is@ndigo/api; apps/04-ndigo-todo-api is@ndigo/todo-api. - The package is always
private: true; apps are not published.
New apps append at the next free number. ndigo new <slug> --kind node|browser
allocates both the number and the dev port );
add the row to apps/README.md by hand.
Renumbering or renaming an app
Section titled “Renumbering or renaming an app”The number is disposable and the slug is stable, so a reorder is a mechanical sweep:
git mv apps/<old> apps/<new>(directory order is what changes).- Update
namein the app’spackage.jsononly if the slug changed. - Run
pnpm installto regenerate the workspace importers inpnpm-lock.yaml. - Sweep live references:
apps/README.md, the rootREADME.md, CI workflow paths/filters,scripts/apps-smoke.mjs,scripts/check-migration-schema.mjs(for the API’sdb/), package README links, currentdocs/guides/**, and source comments that cite an app path. - Validate with
pnpm typecheck, the affected app tests,pnpm lint,pnpm manifest:check,pnpm docs:links, andpnpm format. - Refresh the graph with
graft build.
Do not rewrite historical records: docs/plans/, docs/decisions/,
docs/audits/, dated migration guides, and CHANGELOG.md describe the tree as
it was when they were written.
2. Paired front-end and back-end apps
Section titled “2. Paired front-end and back-end apps”When a browser app and a server app are two halves of one product, use role suffixes and put the pair next to each other:
| Role | Suffix | Examples |
|---|---|---|
| Browser front end | -web |
03-ndigo-todo-web, 10-ndigo-conduit-web |
| HTTP/API back end | -api |
04-ndigo-todo-api, 11-ndigo-conduit-api |
The web half comes first in the ladder. Back ends with no paired front end keep
their domain name (05-ndigo-api, 09-ndigo-httpbin). Do not use
-backend, -server, or -client as product suffixes; reserve client/ for
the browser build directory inside an app that serves its own client.
The current pairs are:
| Web | API |
|---|---|
03-ndigo-todo-web |
04-ndigo-todo-api |
10-ndigo-conduit-web |
11-ndigo-conduit-api |
3. Source layout
Section titled “3. Source layout”A small app may keep a flat src/ — 01-ndigo-hello and 02-ndigo-hello-node
are intentionally this small. Once a role has more than one file, group it:
src/├── app/ # composition roots, runtime, tokens, bootstrap helpers├── controllers/ # HTTP / socket entry points├── services/ # domain and application services├── repositories/ # persistence adapters over @ndigo/orm or a driver├── entities/ # ORM entities (*.entity.ts)├── dtos/ # DTOs (*.dto.ts)├── interceptors/ # pipeline interceptors├── views/ # view classes and server-rendered views├── components/ # Web Components / islands└── index.ts # process or browser entry pointUse only the folders the app needs. Keep the entry point at src/index.ts; a
browser app that also ships a server-owned client uses client/ for the
browser bundle entry and src/ for the server.
File names
Section titled “File names”Files are kebab-case and end with the role they play:
| Suffix | Holds |
|---|---|
*-controller.ts |
HTTP/socket handlers (todo-controller.ts) |
*-service.ts |
Domain/application operations (todo-service.ts) |
*-repository.ts |
Persistence adapter (note-repository.ts) |
*-store.ts |
A minimal in-memory collection; prefer -repository once it wraps a real data source |
*-configuration.ts |
DI composition/config root (todo-configuration.ts) |
*-interceptor.ts |
One interceptor class |
*-http-interceptors.ts |
A wiring module that installs several interceptors |
*-resolver.ts |
View/argument resolver (status-view-resolver.ts) |
*-view.ts |
A view or Web Component that renders (report-view.ts) |
*-handler.ts |
Processes a request/event |
*-strategy.ts |
Encapsulates an interchangeable algorithm |
*-factory.ts |
Creates instances |
*-registry.ts |
Stores registered objects |
*-adapter.ts |
Adapts an external interface |
*.entity.ts |
ORM entity (todo.entity.ts) |
*.dto.ts |
Data-transfer object (todo.dto.ts) |
Notes:
- The
*.entity.ts/*.dto.tsdot suffixes are the established framework convention; keep the dot, not a hyphen. - A module that would otherwise be called
interceptors.tstakes a domain prefix —todo-http-interceptors.ts— so its purpose survives a folder move. - Avoid generic basenames such as
entities.ts,models.ts,store.ts,views.ts,services.ts,config.ts, orutils.ts. Split them by domain or give them a domain prefix._util.tsremains the package-internal escape hatch described in the STYLE_GUIDE; it is not an application catch-all. - Name the primary export after the file:
todo-controller.tsexportsTodoController,todo-configuration.tsexportsTodoConfiguration. Acronyms follow the STYLE_GUIDE casing rules (HTTPController, buthttpTransformerin identifiers).
- Unit and integration specs live in
test/and mirror the source subject:src/todo-controller.ts→test/todo-controller.spec.ts. - Shared in-process harnesses live in
test/harness.ts; fixtures that are not specs use a descriptive name, not_-prefixed package internals. - End-to-end specs live in
e2e/*.spec.tsand are driven by Playwright. - One spec file per subject; a file that needs the whole app composition is an
integration spec (
api.integration.spec.ts,conduit-api.spec.ts).
4. Package manifest and README
Section titled “4. Package manifest and README”Every app carries:
name=@ndigo/<slug>,private: true,type: "module".dev(Vite) orstart(Node/tsx);buildorbuild:clientwhere the app has a build step;test;test:e2efor Playwright lanes.- A README with the standard sections: tagline, Run, Demonstrates (or equivalent), Development, See Also, Contributing, License.
- A row in apps/README.md with the directory, package, runtime, dev port, and one-line purpose.
5. Applying the convention
Section titled “5. Applying the convention”Prefer normalizing files you already have to touch rather than opening a
standalone refactor. A rename is safe when it is mechanical and covered by the
app’s test lane; run that lane and pnpm typecheck before and after.
Migration backlog
Section titled “Migration backlog”The directory and package renames are complete. These file-level shapes remain and are deferred so the ladder stays reviewable:
| App | Current shape | Target shape |
|---|---|---|
11-ndigo-conduit-api |
Flat src/: entities.ts (User, Article, Comment, Favorite, Follow), schemas.ts, store.ts, views.ts, auth.ts, errors.ts, presenter.ts |
entities/ with user.entity.ts, article.entity.ts, comment.entity.ts, favorite.entity.ts, follow.entity.ts; conduit-store.ts; views/article-view.ts + comment-view.ts |
12-ndigo-petclinic |
Flat src/: entities.ts (Owner, Pet, PetType, Visit, Vet, Specialty, VetSpecialty), forms.ts, messages.ts, responses.ts, request-values.ts |
entities/ per entity, forms/, messages/; keep the domain-prefixed root files |
10-ndigo-conduit-web |
Role folders already in place; the last generic names are views/models.ts and app/conduit-services.ts |
views/models.ts → per-resource *.dto.ts; conduit-services.ts is already domain-prefixed and may stay |
Each row is a candidate for the next touch of that app, not a licence for a repo-wide rename. When a row is complete, delete it from this table.
See also
Section titled “See also”- apps/README.md — the numbered ladder, ports, and per-app index
- STYLE_GUIDE.md — file, symbol, and role-suffix rules in the framework packages
- guide 37 — The
ndigoCLI —ndigo newallocates the next app number and port