npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@domain-first/project-structure

v2.17.1

Published

Build and manage your Domain-First project structure with CLI

Downloads

7,146

Readme

@domain-first/project-structure

Spend less time setting up files. Spend more time building your domain.

An interactive CLI for growing TypeScript applications around business domains. Scaffold bounded contexts, aggregates, use cases, commands, queries, adapters, REST endpoints, and React modules with a consistent structure.

Choose what you need, answer a few prompts, and get the source files, optional test skeletons, and wiring to start implementing the feature.

npm install --save-dev @domain-first/project-structure
npx df-ps

Quick start · Project structure · Generators · Configuration · Example workflow

Why use it?

Adding an aggregate often means adding a repository contract, infrastructure implementations, tests, and dependency wiring too. Repeating that setup by hand takes time and lets conventions drift.

df-ps turns those recurring decisions into a guided workflow:

  • Keep the team consistent. Give each kind of component a predictable home, filename, and class name.
  • Grow feature by feature. Add a bounded context or a single component as the application evolves.
  • Keep business code organized. Group code by bounded context, with domain, application, infrastructure, and presentation layers inside it.
  • Prepare for multiple implementations. Generate primary and test implementations of repositories, execution commands, queries, and application ports.
  • Use the integrations you need. Enable templates for the @domain-first packages in your project and optional React scaffolding.

The generated files belong to your project. Fill in the business rules, schemas, dependencies, and integration code as you would with any other source file.

Quick start

Run the CLI inside a project that has a package.json. For a new project, create one with npm init -y first. Use Node.js 22.13+ within the Node 22 release line, or Node.js 24+.

1. Install and initialize

npm install --save-dev @domain-first/project-structure
npx df-ps

You can also run it without adding it as a development dependency:

npm exec --package=@domain-first/project-structure -- df-ps

On the first run, the CLI asks for:

| Setting | What to enter | | ---------------------------------- | ---------------------------------------------------------------------------------------------------- | | Root folder | The source directory, such as src or src/server. Default: src. | | @domain-first packages | The integrations to use in generated templates. | | Default persistence implementation | A label such as prisma, used as the suggested implementation for execution commands and queries. | | React | Whether to show the React module menu. | | Unit tests | Whether to generate tests, and which module to import test helpers from. Suggested module: vitest. |

Setup writes domain-first.project-structure.config.json beside your package.json and creates shared helpers for the selected wire and errors integrations. That first run then exits.

The CLI uses the nearest package.json found by walking up from the current directory. In a monorepo, run it inside the package you want to scaffold.

2. Create a bounded context

Run npx df-ps again, choose Scaffold new bounded context, and enter a name such as sales.

3. Add a component

Run npx df-ps, choose Bounded Context: sales, select a layer, and choose a generator. For example:

Bounded Context: sales → Domain → New Aggregate

Each invocation performs one scaffolding operation. Run the command again to add the next component. Folders are created as needed.

Project structure

A bounded context groups a business area and its implementation. Within it, each layer has a specific role:

| Location | Purpose | | ----------------- | ----------------------------------------------------------------------------------------------------- | | domain/ | Aggregates, repository contracts, domain services, and business errors. | | application/ | Use cases, commands, queries, ports, and application services. | | infrastructure/ | Concrete repositories, adapters, execution handlers, and infrastructure services. | | presentation/ | REST endpoints and presentation services. | | wiring/ | Factories that construct components and select implementations, when @domain-first/wire is enabled. |

The Shared Layer menu provides cross-context errors, entities and value objects, application ports, and domain, application, infrastructure, and presentation services under <rootFolder>/shared.

A project using handler and wiring integrations can grow into this structure:

src/
├── bounded-contexts/
│   └── sales/
│       ├── domain/
│       │   ├── aggregates/order/
│       │   ├── errors/
│       │   └── services/
│       ├── application/
│       │   ├── commands/          # execution/ and orchestration/
│       │   ├── queries/           # execution/ and orchestration/
│       │   ├── ports/
│       │   ├── services/
│       │   └── use-cases/
│       ├── infrastructure/
│       │   ├── application-adapters/
│       │   ├── commands/
│       │   ├── queries/
│       │   ├── repositories/
│       │   └── services/
│       ├── presentation/
│       │   ├── rest/
│       │   └── services/
│       └── wiring/
├── shared/
└── presentation/
    ├── rest/
    └── react/
        ├── pages/
        ├── widgets/
        ├── ui-kit/
        └── hooks/

This is an example of the accumulated output; creating a context alone creates its directory and, when enabled, its error namespace.

Generators

Choose a bounded context, then a layer:

| Layer / menu option | Generated output | | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Domain → New Aggregate | Aggregate root and barrel export; optional repository contract, primary and test implementations, tests, and repository wiring. | | Domain → New Entity / New Value Object | Entity or value object class with tests, in the entities/ or value-objects/ folder of a chosen aggregate (exported from its barrel) or of the context's domain/. Also in the Shared Layer, under shared/domain/. | | Domain → Errors | Errors in the context namespace, nested namespaces, and exports. Uses @domain-first/errors. | | Domain → New Service | Domain service class, with optional tests and wiring. | | Application → New Use Case | Use case class; handler schemas and handle when enabled; optional tests and wiring. | | Application → New Command / New Query | Execution or orchestration templates, with optional tests and wiring. | | Application → New Port | Abstract port and an adapter implementation, with an optional test adapter, tests, and wiring. | | Application → New Service | Application service class, with optional tests and wiring. | | Infrastructure → New Service | Infrastructure service class, with optional tests and wiring. | | Presentation → New Service | Presentation service class, with optional tests and wiring. | | Presentation → New Handlers-REST Endpoint | Endpoint class for a controller, method, and relative path; wiring and handler exports when enabled. Requires @domain-first/handlers-rest to appear in the menu. |

Commands and queries

Both generators offer two modes:

  • Execution (with infrastructure implementation): creates an abstract application contract and a concrete implementation under infrastructure/commands/<implementation> or infrastructure/queries/<implementation>. You can add a separate implementation for tests.
  • Orchestrator (no infrastructure implementation): creates an application class under application/commands/orchestration or application/queries/orchestration, ready for coordinating other operations.

With @domain-first/handlers, templates include input/output schemas and a typed handler contract or defineHandler implementation. Execution contracts go in the corresponding execution/ directory; without that integration, they go directly in application/commands or application/queries.

Wiring and tests

With @domain-first/wire, generators create wireClass factories. Repositories, execution commands, queries, and ports use the shared envBranchedWire helper to choose an implementation based on NODE_ENV:

| Environment | Implementation | | ------------- | ---------------------------------------------------------------------------- | | test | The test implementation, if requested; otherwise the primary implementation. | | development | The primary implementation. Also used when NODE_ENV is unset. | | production | The primary implementation. |

Implementation names such as prisma, api, in-memory, and mock determine names and locations for generated classes. Add the database queries, API calls, or in-memory behavior yourself. As the last step, generators that create a wiring factory offer to pick its dependencies from wired modules of the same bounded context and the Shared Layer. Each selected module is added to the factory's dependency list and to the constructor, for example private readonly userRepository: UserRepository. Environment-branched factories (repositories, execution commands and queries, ports) wire each implementation in isolation: you pick dependencies separately for each one, for example for PrismaUserRepository and then for InMemoryUserRepository. Each list starts with a Suggested group when infrastructure modules match the implementation name: prisma-infrastructure-service for a prisma implementation, or in-memory-db-infrastructure-service for an in-memory one. To add dependencies to an existing module later, select Wire dependency in the bounded context or Shared Layer menu. Environment-branched modules are listed there once per implementation, such as UserRepository (prisma) and UserRepository (in-memory), and only the chosen one is changed.

The list depends on the layer of the class being built:

| Layer | Can depend on | | -------------- | ------------------------- | | Domain | Domain | | Application | Domain, application | | Infrastructure | Domain, infrastructure | | Presentation | Application, presentation |

Some kinds of modules have narrower rules. From the application layer, application services can use only ports. Use cases can't use other use cases, and repository implementations can't use other repositories. Execution commands and queries can't use other commands or queries. From the application layer, orchestration commands and queries can use only execution commands and queries. Repository, execution command, execution query, and port implementations belong to infrastructure: they implement application modules but can't take them as dependencies. REST endpoints are not offered as dependencies. Their dependencies are chosen when they are generated (see REST endpoints) and are not available in Wire dependency.

When testingLibrary is configured, supported generators always add .spec.ts files. These start with construction or wiring checks; extend them with tests for your business behavior. Repository, command, query, and port test templates reference wiring factories, so use the wiring integration or adapt those tests to your own construction setup.

REST endpoints

Select an existing controller or create one, choose get, post, patch, delete, or put, and enter a relative path. For example, context sales, controller orders, path recent, and method get produce:

src/bounded-contexts/sales/presentation/rest/orders/recent/GET.ts

The route metadata uses /sales/orders/recent. With @domain-first/wire, the generator then asks for the endpoint generator, picked from presentation services of the bounded context and the Shared Layer (services with endpoint in the name are suggested first), and for the handler, picked from queries, commands, and use cases of the bounded context. The endpoint's constructor gets the chosen types, and its wiring factory gets their factories. In get handle(), call the generating method of the chosen service yourself, for example this.endpointGenerator.generate(...) for readonly generate = createEndpoint(...)(adapter). Without wiring, or when there is nothing to pick, the endpoint keeps the EndpointGenerator type and a never handler placeholder for you to replace.

With wiring enabled, the generator also adds endpoint factories and exports collected as restHandlers in <rootFolder>/presentation/rest/index.ts. Connect these handlers to your HTTP server.

React modules

Enable useReact and choose Scaffold React module from the main menu. Modules are created under <rootFolder>/presentation/react.

| Generator | Output | | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | New page | pages/<name>-page.tsx with a typed props object and a component. | | New widget → monolithic | widgets/<name>/<name>-widget.tsx with props and rendering in one file, re-exported from index.ts. | | New widget → with separated ui | <name>-widget.tsx, types.ts, ui/<name>-widget-ui.tsx, hooks/use-<name>-ui-model.ts, and index.ts; with storybookFramework, also ui/<name>-widget-ui.stories.tsx. | | New widget → headless | <name>-widget.tsx, types, a UI-model hook, and index.ts; the rendering component is supplied through the UI prop. | | New UI-kit component | ui-kit/<name>/<name>.tsx with a typed props object and a component, re-exported from index.ts; with storybookFramework, also <name>.stories.tsx. | | New shared hook | hooks/use-<name>.ts with an app-wide hook, such as use-debounce; a leading use- in the name is not repeated. |

Page and widget names can include folders split by /. For example, a page named admin/panel/users is created at pages/admin/panel/admin-panel-users-page.tsx as AdminPanelUsersPage, and a widget with the same name at widgets/admin/panel/users/admin-panel-users-widget.tsx as AdminPanelUsersWidget with the story title Widgets/Admin/Panel/Users. The widget folder uses only the last segment; file names and code names use the whole path.

To move or rename a page or a widget, choose Move or rename page or Move or rename widget, pick it from the list and enter a new name with folders. A widget moves with its whole folder (assets included), and a page with the files next to it that share its name, such as <name>-page.module.css. File names, <Name>Page, <Name>Widget… and use<Name>UIModel follow the new name, and so does a generated story title. Imports of the module are updated across the project, both relative ones and tsconfig.json paths aliases such as @/; files importing it through barrels (export * from / export { … } from) get the renamed classes too, and relative imports inside the module are adjusted to its new location. The CLI lists the files to move and to update and asks before writing; emptied folders are removed. Entering the same name renames files made before file names used the whole path, for example pages/admin/users-page.tsx to pages/admin/admin-users-page.tsx.

Project integrations

Choose Add integration → Storybook (React + Vite) to configure Storybook. The integration creates main.ts and preview.ts under <rootFolder>/presentation/react/.storybook, enables React scaffolding and sets storybookFramework to @storybook/react-vite for future generated stories. It uses relative story paths and configures the Vite alias @ to the absolute project root (the directory containing package.json), computed at runtime from the config location. Add project CSS to preview.ts with relative imports or @/ imports. The setup includes @vitejs/plugin-react.

It adds these package scripts, with paths adjusted to your rootFolder:

{
    "storybook": "storybook dev -p 6006 -c \"src/presentation/react/.storybook\"",
    "build-storybook": "storybook build -c \"src/presentation/react/.storybook\" -o \"src/presentation/react/storybook-static\""
}

Existing unrelated scripts and package fields are preserved. If a script name already has a different command, choose to keep it, replace it, or use another name. Identical generated configs and scripts are skipped on subsequent runs; changed config files use the usual overwrite confirmation. Cancelling before writing leaves all files unchanged.

After writing, the CLI prints only npm commands for missing dependencies. It does not install packages or execute scripts. Install commands omit version constraints for Storybook, its React + Vite framework, Vite and the React plugin; existing dependency versions are preserved and must be compatible with those packages. Generate a UI-kit component or a widget with separated UI to add your first story. The static build goes to <rootFolder>/presentation/react/storybook-static; the integration adds this directory to the project .gitignore without duplicating the entry on subsequent runs.

Missing domain-first packages

Choose Add integration → Missing domain-first packages to compare domainFirstPackages in the configuration with dependencies, devDependencies and optionalDependencies in the project's package.json. The CLI prints one npm i command for missing packages, without version constraints. If none are missing, it prints nothing. This checks declared dependencies, not the contents of node_modules, and does not modify files or install packages.

Prisma 8 (PostgreSQL)

Choose Add integration → Prisma 8 (PostgreSQL, RC). The preset creates <rootFolder>/shared/infrastructure/prisma/prisma.config.ts and a starter contract.prisma. Existing contracts are preserved, including your models.

You can optionally create PrismaInfrastructureService in shared/infrastructure/services; it exposes orm, uses the Temporal polyfill, and reuses the client across development hot reloads. When @domain-first/wire is enabled, the integration also creates its wiring factory. A separate prompt lets you make prisma the default persistence implementation.

The integration adds three scripts, all pointing to the generated config:

  • prisma:emit: emit contract.json and contract.d.ts.
  • prisma:migration:plan: emit the contract, then plan a migration.
  • prisma:migration:apply: apply migrations and advance the db ref.

Script conflicts use the same keep/replace/rename flow as Storybook. File changes are staged until overwrite confirmation; no packages are installed and no commands connect to the database during scaffolding.

The printed install commands omit version constraints for prisma, @prisma/cli-engine, @prisma/orm-postgres, temporal-polyfill, and dotenv. Already declared dependencies are kept. The CLI also reminds you to add !<rootFolder>/shared/infrastructure/prisma to your linter ignores. This is a PostgreSQL preset for Prisma 8 RC, not a Prisma 7 migration tool.

After installing dependencies, configure DATABASE_URL (the CLI loads .env; your application must load its runtime environment), add your models to the contract and run the emit script before compiling or importing the service. Use Node.js 22.18+, TypeScript 5.9+, ESM and JSON import support. Review planned migrations before running the apply script. Generated contract files and migration history are managed by Prisma, not by this scaffolder.

Vitest

Choose Add integration → Vitest. The integration creates vitest.config.ts in the project root (the directory containing package.json) with resolve.tsconfigPaths and V8 coverage for <rootFolder>/bounded-contexts and <rootFolder>/shared. Specs, infrastructure, REST presentation and wiring are excluded from coverage. When @domain-first/handlers-next is declared, the config also inlines it so Vitest can resolve its next/headers import.

It sets testingLibrary to vitest, so generators scaffold unit tests; if another testing library is configured, the CLI asks before replacing it. It also adds /coverage/ to the project .gitignore and these package scripts:

{
    "test": "vitest run --reporter=dot --silent=passed-only",
    "test:coverage": "vitest run --coverage --reporter=dot --silent=passed-only"
}

Script conflicts and overwrite confirmation work as in the other integrations. The printed install command omits version constraints for vitest and @vitest/coverage-v8; already declared dependencies are kept. resolve.tsconfigPaths requires Vite 8+. The config excludes specs/e2e, which belongs to Playwright.

Playwright

Choose Add integration → Playwright. The integration creates playwright.config.ts in the project root with testDir: './specs/e2e', an HTML reporter, traces on retry, screenshots and videos on failure, and projects for Chromium, Firefox, WebKit, Mobile Chrome and Mobile Safari, all running on Playwright's own browser builds. The web server runs npm run dev at http://localhost:3000 and is reused outside CI; adjust baseURL and webServer.url for another port.

A mock example goes to specs/e2e/home: home.e2e.spec.ts and a README.MD with its user stories. Existing example files are never overwritten. The integration adds a test:e2e script (playwright test) and ignores /test-results/, /playwright-report/, /blob-report/, /playwright/.cache/ and /playwright/.auth/ in .gitignore.

After writing, the CLI prints npm install --save-dev @playwright/test if the package is missing and npx playwright install --with-deps for the browsers and their system dependencies. Then run npm run test:e2e.

OpenAPI

Choose Add integration → OpenAPI. The item is listed only when domainFirstPackages includes @domain-first/handlers-rest. The integration asks for the route prefix your HTTP server serves restHandlers under (/api by default) and for the document path (public/openapi.json when next is declared, otherwise openapi.json). It creates scripts/generate-openapi.ts in the project root, which passes restHandlers from <rootFolder>/presentation/rest to generateOpenAPI from @domain-first/handlers-rest. The document title comes from the package name. It adds this package script:

{
    "openapi": "tsx --env-file-if-exists=.env scripts/generate-openapi.ts"
}

.env is loaded because wiring imported through restHandlers may read the environment. Script conflicts and overwrite confirmation work as in the other integrations. After writing, the CLI prints npm install --save-dev tsx when tsx is missing. The script needs @domain-first/handlers-rest 7.0.2+, where OpenAPI paths start with / without a leading empty prefix segment; when the package is missing or older (the installed version is checked first, then the declared range), the CLI prints npm install @domain-first/handlers-rest@^7.0.2. restHandlers are created with the first REST endpoint generated with @domain-first/wire; until then, the CLI reminds you about it. Run npm run openapi after adding or changing endpoints, then regenerate your API client, if you have one.

Orval (React Query client)

Choose Add integration → Orval (React Query client). The item is listed only when domainFirstPackages includes @domain-first/handlers-rest and useReact is enabled. The integration asks for the OpenAPI document path; the default is read from scripts/generate-openapi.ts created by the OpenAPI integration. It creates:

  • <rootFolder>/presentation/react/data-layer/orval.config.ts: TanStack Query v5 hooks on fetch, split by tags into data-layer/orval, with schemas in its model folder. Orval resolves paths relative to the config, so they point to the OpenAPI document and the data layer from there. When next is declared, request URLs start with NEXT_PUBLIC_API_BASE_URL.
  • data-layer/api-client/mutator.ts: customInstance, which every generated request goes through. It calls fetch and throws ApiError with the status and the parsed body on non-2xx responses; the hooks type that body per endpoint. Add auth headers, token refresh and other shared request logic here.
  • data-layer/query-provider.tsx: QueryProvider with one QueryClient per browser session. Wrap your app in it.

The mutator and the provider are starting points and are never overwritten.

The generated data-layer/orval/ folder is added to .gitignore, and this package script is added, with the path adjusted to your rootFolder:

{
    "client:orval": "orval --config \"src/presentation/react/data-layer/orval.config.ts\""
}

After writing, the CLI prints npm install @tanstack/react-query and npm install --save-dev orval for missing packages. Run npm run openapi, then npm run client:orval, and import hooks and types from data-layer/orval.

Configuration

The project configuration is stored in domain-first.project-structure.config.json. Commit it to share generation settings with your team. For example:

{
    "rootFolder": "src",
    "domainFirstPackages": [
        "@domain-first/types",
        "@domain-first/errors",
        "@domain-first/handlers",
        "@domain-first/wire",
        "@domain-first/handlers-rest"
    ],
    "defaultPersistenceLayerImplementation": "prisma",
    "useReact": true,
    "storybookFramework": "@storybook/react-vite",
    "testingLibrary": "vitest"
}

| Field | Effect | | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | rootFolder | Base path for generated source, relative to the project root. | | domainFirstPackages | Explicit list of integrations used by templates. An empty array selects basic class templates where supported. | | defaultPersistenceLayerImplementation | Suggested implementation label for execution commands and queries. | | useReact | Shows or hides the React module menu. | | storybookFramework | Storybook framework package imported by generated stories (Meta, StoryObj), e.g. @storybook/react-vite. Omit this field to skip stories for UI-kit components and widget UI. Asked during setup only when React is enabled. | | testingLibrary | Module imported by generated tests. Omit this field to disable test generation. Templates expect helpers such as describe, test, expect, and beforeEach. |

Package integrations

| Package | What it enables | | ----------------------------- | ------------------------------------------------------------------------------------------------------------------ | | @domain-first/types | Aggregate roots extending domainType(). | | @domain-first/errors | Context and shared error namespaces initialized during setup/context creation. Error generation uses this package. | | @domain-first/handlers | Handler contracts, defineHandler, and input/output schema placeholders for use cases, commands, and queries. | | @domain-first/wire | Construction factories and environment-based implementation selection. | | @domain-first/handlers-rest | The REST endpoint generator using EndpointGenerator. |

Select the packages you intend to use and install them in your application separately. The CLI reads this list from configuration; it does not detect or install dependencies. React and your testing library also need their own project setup.

You can edit the configuration for future runs. Existing files are left as they are when you edit settings; enabling wire or errors later also requires adding the shared helpers that initial setup would create, plus context error namespaces where needed.

Example workflow

To start an order feature, enable @domain-first/wire during initialization and run npx df-ps for each step:

  1. Choose Scaffold new bounded context and name it sales.
  2. Choose Bounded Context: sales → Domain → New Aggregate, name it order, and add a repository with prisma and an in-memory test implementation.
  3. Choose Application → New Use Case within sales and name it place-order.
  4. Implement the aggregate's rules, repository operations, and use case; connect their dependencies in the generated wiring files.

The key files from these selections are:

src/bounded-contexts/sales/
├── domain/aggregates/order/
│   ├── index.ts
│   ├── order.aggregate-root.ts
│   └── order-repository.ts
├── application/use-cases/
│   └── place-order-use-case.ts
├── infrastructure/repositories/
│   ├── prisma/prisma-order-repository.ts
│   └── in-memory/in-memory-order-repository.ts
└── wiring/
    ├── repositories/wire-order-repository.ts
    └── use-cases/wire-place-order-use-case.ts

Use lowercase kebab-case for entered names, such as place-order and payment-gateway. Names are used in paths, and class and variable names are derived from their hyphen-separated words: place-order becomes PlaceOrderUseCase for a use case.

Nothing is written until a run finishes. If the run would replace existing files, the CLI lists them and asks Overwrite them? (default: No). Answering No, or interrupting the CLI at any prompt, leaves the project unchanged. Lines added to existing barrels, error files, and wiring indexes are not overwrites and are never duplicated. Use a new name for a new component, and review the generated changes before continuing implementation.

License

MIT