Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save thomas-darling/718f4ca6e7f0ec688b488460e0d0030c to your computer and use it in GitHub Desktop.

Select an option

Save thomas-darling/718f4ca6e7f0ec688b488460e0d0030c to your computer and use it in GitHub Desktop.
Conceptual overview of a project structure suitable for hierarchical web apps.

Project structure for hierarchical apps

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

When deciding where to place something, always strive to place it as close to where it is used as possible, and then move it up in the structure only if it later becomes needed elsewhere. And if that does happen, always ask yourself whether you really need all of this, or only part of it. If the answer is the latter, you might want to consider refactoring, so only the relevant parts move up the structure, while the rest remains where it belongs, close to where it is needed.

A good test for whether the structure is in a good state, is to ask yourself "what would happen if I delete this module or component? What else would I need to clean up?". Ideally, the answer should be only a few references or route definitions. If you find any other files or folders need to be deleted, those should probably have been inside the module or component.

A side effect of this is, that a few components and services will tend to work their way up, towards the root of the structure, as they become needed in more and more places. And while this does separate them slightly from where they are used, that's fine - it's just a consequence of their shared nature, so don't stress about it. And of course, if you happen to have a large, shared component, that is not tightly coupled to the domain of the app, such as e.g. a complex markdown editor, you could always consider splitting that out into an entirely separate package.

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 page view with an associated route, usually presented in a <router-view>. It should be as self-contained as possible, and may contain sub-modules, each structured in the same way.

- {module-name}
  β”œ + components
  β”œ + modals
  β”œ + modules
  β”œ + resources
  β”œ + services
  β”œ + types
  β”œ   {module-name}.html
  β”œ   {module-name}.scss
  β”œ   {module-name}.ts
  β””   routes.ts

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 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
@adriatic
Copy link

adriatic commented Aug 6, 2019

This looks like a well-thought application template, so I would like to know what is your success in proposing to make it officially supported for Aurelia 2. I believe that I navigated to this page following a trail from Aurelia 2 documentation, but cannot recall the full context.

@avrahamcool
Copy link

this is a very helpful resource.
thank you very much.

@thomas-darling
Copy link
Author

Thanks πŸ™‚

Just in case you haven't seen it - I also have another variant of this, that takes a different approach:
https://gist.github.com/thomas-darling/951b9c9e681183adea6cd6c3be87ba74

They both work really well, but some apps just naturally fit one better than the other.
(don't get confused about the term module - it has different meaning in the two structures)

@avrahamcool
Copy link

can I copy part of this?
I want to create a coding guideline for the developers in my team.
and I want to base on this + little tweaks.

@thomas-darling
Copy link
Author

Sure, that's why I published it πŸ™‚

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment