Skip to content

Instantly share code, notes, and snippets.

@thomas-darling
Last active June 13, 2024 10:41
Show Gist options
  • Save thomas-darling/951b9c9e681183adea6cd6c3be87ba74 to your computer and use it in GitHub Desktop.
Save thomas-darling/951b9c9e681183adea6cd6c3be87ba74 to your computer and use it in GitHub Desktop.
Conceptual overview of a project structure suitable for modular web apps.

Project structure for modular apps

The following represents a proven and scalable approach to structuring modular web apps. It is conceptually simple, relying on a few well defined structural patterns, some of which are repeated in a recursive fashion. A structure like this will often look much like the domain of the app, making it very easy to navigate, understand and reason about.

Naming conventions

All file and folder names must be lower case and the only word separator allowed is a single -. The only exceptions are tests, which must be named {name}.test.ts, and localized files, which must be named {name}.{locale}.{ext}.

In the following, - and + indicate expanded and collapsed folders, while ... denote additional unspecified items.

Structure of the package and src folder

.
├ - packages
│   ├ + cloud
│   ├ + desktop
│   └ + mobile
├ - src
│   ├ - app
│   │   ├ + components
│   │   ├ + modals
│   │   ├ + modules
│   │   ├ + resources
│   │   ├ + services
│   │   ├ + toasts
│   │   ├ + types
│   │   ├   app.html
│   │   ├   app.scss
│   │   └   app.ts
│   ├ - resources
│   │   ├ + fonts
│   │   ├ + icons
│   │   ├ + images
│   │   ├ + experiments
│   │   ├ + integrations
│   │   ├ - settings
│   │   │   ├   index.ts
│   │   │   └   ...
│   │   ├ + stubs
│   │   │   ├ + responses
│   │   │   └   index.ts
│   │   ├ - styles
│   │   │   ├ + framework
│   │   │   ├ + resources
│   │   │   ├ + settings
│   │   │   ├   index.scss
│   │   │   └   index.ts
│   │   ├ - themes
│   │   │   └ + ...
│   │   └ - translations
│   │       ├   translations.json
│   │       ├   translations.{locale}.json
│   │       └   ...
│   ├ - shared
│   │   ├ - framework
│   │   │   ├ + components
│   │   │   ├ + converters
│   │   │   ├ + services
│   │   │   ├ - styles
│   │   │   │   ├ + foundation
│   │   │   │   ├ + framework
│   │   │   │   ├ + resources
│   │   │   │   ├ + settings
│   │   │   │   ├   index.scss
│   │   │   │   └   index.ts
│   │   │   ├   index.scss
│   │   │   ├   index.ts
│   │   │   └   readme.md
│   │   ├ - infrastructure
│   │   │   ├ + ...
│   │   │   ├   index.ts
│   │   │   └   readme.md
│   │   ├ - localization
│   │   │   ├ + ...
│   │   │   ├   index.ts
│   │   │   └   readme.md
│   │   ├ - patches
│   │   │   ├ + ...
│   │   │   ├   index.ts
│   │   │   └   readme.md
│   │   ├ - types
│   │   │   ├ + ...
│   │   │   ├   index.ts
│   │   │   └   readme.md
│   │   └ - utilities
│   │       ├ + ...
│   │       ├   index.ts
│   │       └   readme.md
│   ├ - typings
│   │   ├ + extensions
│   │   ├ + fixes
│   │   ├ + globals
│   │   └ + modules
│   ├   env.ts
│   ├   index.ejs
│   ├   index.scss
│   └   index.ts
├ + tools
├   package.json
├   readme.md
├   stylelint.json
├   tsconfig.json
└   tslint.json

Contents of the ./packages folder:

This contains packages that act as hosts for the app. For example, the cloud package might implement a Node Express server, while the desktop package might implement an Electron app, and the mobile might implement a Cordova app. Note that you generally won't need those packages while developing the app itself, as development should happen using a simple development server.

Contents of the ./src/app folder:

This is a module folder, representing the root app module.

Contents of the ./src/shared folder:

Note that this could be broken out into separate packages, shared between multiple projects.

  • The framework folder, containing shared components and styles. This should be implemented such that it could theoretically be reused in other apps without changes. Note that you should never import anything from this folder, other than index.ts and index.scss.

  • The infrastructure folder, containing shared infrastructure code. This should be implemented such that it could theoretically be reused in other apps without changes. Note that you should never import anything from this folder, other than index.ts.

  • The patches folder, containing any monkey-patches needed to patch issues found in dependencies.

  • The types folder, containing shared types that are not specific to the app. This should be implemented such that it could theoretically be reused in other apps without changes. Note that you should never import anything from this folder, other than index.ts.

  • The utilities folder, containing shared utilities that are not specific to the app.

  • This should be implemented such that it could theoretically be reused in other apps without changes. Note that you should never import anything from this folder, other than index.ts.

Contents of the ./src/resources folder:

  • The experiments folder, containing the settings and implementations of any experiments being conducted. Each experiment must be implemented in its own folder.

  • The integration folder, containing any files needed to integrate with browsers, devices or services. An example of this would be the favicon.ico file, manifests and similar resources.

  • The locales folder, containing files that define the formatting settings for each locale. Only the one matching the current locale will be loaded by the app.

  • The settings folder, containing the settings for the app, infrastructure and framework. Note that feature settings should live in the individual modules and components. This is primarily intended for fundamental settings, such as API base URLs, supported markets and locales, etc.

  • The styles folder, containing app-specific styles used across the app. This is structured similar to the shared styles.

  • The themes folder, containing app-specific themes, each in its own folder, including any associated resources.

  • The stubs folder, containing stubs used during development, e.g. to fake responses from API endpoints that do not yet exist.

  • The translations folder, containing files that provide the translated content that is injected into view templates and string files during the localization process. Those files will be imported from a translation management system and should therefore not be edited manually. For more information, see gulp-translate.

Contents of the ./src/tools folder:

This contains any tools needed for development, such as build scripts.

Structure of a module folder

Note that for very large apps, modules could be broken out as separate packages, maintained by separate teams.

A module represents a coherent subset of the domain, and contains all the components, modals, pages, resources, services and types related to it.

Modules may reference each others components, modals and services as needed.

- {module-name}
  ├ + components
  ├ + modals
  ├ + pages
  ├ + resources
  ├ + services
  └ + types

Structure of a component folder

Note that you may use sub-folders under the components folder to group closely related component folders. Use this if you have e.g. multiple variations of some card component, and want to group them together.

A component represents a a custom element or attribute used within a view. It should be as self-contained as possible, and may contain sub-components, each structured in the same way.

Sub-components are components that are used internally in the template for the component, but which are not exposed to consumers. For example, while a page-header component might internally use a logo component, that is an implementation detail the consumer don't need to know about.

Some components may need to expose related components. For example, a tabs component might expose both a tabs and tab-pane component, both of which are needed to consume the component as a whole. In such cases, the files for the related components should be placed together with the files for the primary component.

- {component-name}
  ├ + components
  ├ + modals
  ├ + resources
  ├ + services
  ├   {component-name}.html
  ├   {component-name}.scss
  ├   {component-name}.ts
  ├   {optional-related-component-name}.html
  ├   {optional-related-component-name}.scss
  ├   {optional-related-component-name}.ts
  └   ...

Structure of a page folder

A page represents a page view with an associated route, usually presented in a <router-view>. It should be as self-contained as possible, and may contain sub-pages, each structured in the same way.

- {page-name}
  ├ + components
  ├ + modals
  ├ + pages
  ├ + resources
  ├ + services
  ├   {page-name}.html
  ├   {page-name}.scss
  ├   {page-name}.ts
  └   routes.ts

Structure of a modal folder

A modal represents a modal view, such as a dialog, panel or overlay. It should be as self-contained as possible, and may contain sub-modals, each structured in the same way.

- {page-name}
  ├ + components
  ├ + modals
  ├ + resources
  ├ + services
  ├   {modal-name}.html
  ├   {modal-name}.scss
  └   {modal-name}.ts

Structure of a service folder

Note that, although generally not recommended, you may use sub-folders under the services folder to group closely related services.

Services are injected into view models, allowing the view models to contain only interaction logic and temporary state scoped to that view.

Generally, an app will have two types of services:

  • Services that implements and manage a part of the domain model for the app.
  • Services that implements and manage state that is specific to some module or component.

Services should be carefully architected to separate concerns, and should be as self contained as possible, with each service exposing its public API through a single index.ts file. Each service will typically define the domain models it manages, with the exception of very general types, which may reside in one of the types folders.

Prefer creating more specialized services, rather than creating a few services that take on too many responsibilities.

Structure of a resources folder

A resources folder may exist at the root level, module level and component level, as well as under services. Resources represent assets, such as images, icons, videos and fonts, as well as things that could be said to be consumed by the module or component, such as settings, or localizable things, such as .json files containing strings for use in models and services.

- resources
  ├ + fonts
  ├ + icons
  ├ + images
  ├ + settings
  ├ + strings
  ├ + videos
  └ + ...

Note that the resources folder at the root level has different content.

Structure of an experimentfolder

An experiment is any kind of test we wish to perform, e.g. to evaluate the user and business impact of a new design for some component. Experiments have two parts:

  • An experiment folder, which contains the implementation of any framework configuration, components, or services needed for the experiment.
  • The code that imports and integrates the experiment in the affected parts of the app.

Conceptually, the idea is, that essentially all code related to the experiment lives in this folder, and only if the experiment proves to be a success, will time be allocated to implement it properly in the app itself. This allows experiments to be created with less concern for long term maintainability, and more of a copy-paste-modify approach, as they won't pollute the code base for the app itself - at least not beyond a few simple condition to use e.g. an alternative component implementation provided by the experiment. And when an experiment folder is deleted, build errors will reveal all the places in which it was integrated, making cleanup easier.

An experiment folder must be structured exactly like this, and the index file must export a configure function, so they can be loaded and configured as features in Aurelia:

- {experiment-name}
  ├ + components
  ├ + resources
  ├ + services
  ├   index.ts
  └   readme.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment