Structuring your app


There are a few good articles by Ashley Grant and Patrick Walters and Tiago Morais on structuring an Aurelia app but nothing I can find on the subject that is that recent. I have a reasonable sized app that’s over a year old that I’ve restructured a few times and am considering doing it again as I’m not happy with it.

root directory

Setup specific folders

|-- .github (Github templates)
|-- .vscode (VSCode only)
|-- aurelia_project (CLI only)
|-- custom_typings (Typescript only)

Ignored folders

|-- node_modules
|-- scripts
|-- assets (fonts, images, icons)
|-- doc (Changelog and other useful docs)
|-- locales (i18n translation files)
|-- src (the source code)
|-- test (just test setup)

My test folder is pretty empty as I prefer to have a __test__ folder in the same directory as each component.

Current source code structure

I’m not happy with the current structure as it doesn’t reflect the app. I can’t find out where I heard/read but it was something like your folder source should scream what your app does not how it’s built. The current structure does the latter.

|-- config (plugin config)
|-- controllers (will probably be dropped)
|-- features (encapsulated features)
|-- interfaces (typescript interfaces)
|-- models (models used by the views)
|-- private (the routes of the app)
|   |-- page 1
|   |-- page 2
|       |-- page 2a
|       |-- page 2b
|       |-- ...
|   |-- app-shell.css
|   |-- app-shell.html
|   |-- app-shell.ts
|   |- ...
|-- resources
|   |-- attributes
|   |-- dialogs
|   |-- elements
|   |-- forms (reusable forms)
|   |-- utilities
|   |-- value-converters
|   |-- events.ts (constants)
|   |-- index.ts
|   |-- key-codes.ts (more constants)
|-- services
|   |-- api
|       |-- service1.ts
|       |-- service2.ts
|       |-- ...
|   |-- log.ts
|-- styles
|   |-- base.css
|   |-- spacing.css
|   |-- ...
|-- environment.ts
|-- main.ts
|-- vars.css (css custom properties used through out the app)

It has a few problems

  • Too many top level folders
  • Folders with hardly anything in
  • Doesn’t show the features of the app

Version 2

Considering this instead to help focus on the features.

|-- app-shell
|   |-- app-shell.css
|   |-- app-shell.html
|   |-- app-shell.ts
|-- page 1
|-- page 2
|   |-- page 2a
|   |-- page 2b
|   |-- ...
|-- ...
|-- resources
|   |-- config (plugin config)
|   |-- controllers (will probably be dropped)
|   |-- features (encapsulated features)
|   |-- interfaces (typescript interfaces)
|   |-- models (models used by the views)
|   |-- attributes
|   |-- dialogs
|   |-- elements
|   |-- forms (resuable forms)
|   |-- services
|       |-- api
|           |-- service1.ts
|           |-- service2.ts
|           |-- ...
|       |-- log.ts
|   |-- utilities
|       |-- events.ts (constants)
|       |-- key-codes.ts (more constants)
|   |-- value-converters
|   |-- index.ts
|-- styles
|   |-- base.css
|   |-- spacing.css
|   |-- vars.css (css custom properties used through out the app)
|   |-- ...
|-- environment.ts
|-- main.ts

This flattens the private folder and puts the page/screens down into the src folder and everything else into resources (maybe lib would be better?). The resources folder is a little busier and probably still in need of tidying up.

For now there is a very little in the page folders other than the view and view-model pair but this might make it easier to restructure.

Bonus tip

You can set your paths in typescript so you don’t have to worry about relative paths. So your imports could be something like import { AccountService } from 'services/account-service' rather than import { AccountService } from '../../../../resources/services/account-service'

What do you think? What does yours look like? Is there anything you’re not happy with?


We’ve got a large Electron app which has been added to for over a year and we’re still going.

We went for something like version 2 where all the features have their own base directory. The idea being that all the features should be usable independently. This means the separate features often have their own models, services and elements which don’t spill into the other features.

Lately I’ve been looking to refactor into a mono-repository. This would help keep the features better segregated and pushes developers into creating a tree of dependencies rather than a spaghetti mess. This will also mean that if and when we want to ship our app in the browser or cordova, it will be easier to only include the components and features which are compatible.


Could you share relevant structure and build setup for electron?


Here is most of the build and Electron stuff.

We started with an aurelia jspm skeleton nearly two years ago. Over time it was converted to TypeScript and Gulp was replaced with npm scripts plus a few node scripts where that didn’t suffice.

  "scripts": {
    "postinstall": "electron-builder install-app-deps && jspm install -y",
    "start": "electron . --dev",
    "lint:ts": "tslint --config ./tslint.json --project tsconfig.json --force --fix",
    "lint:html": "node ./scripts/aurelia-lint.js",
    "lint": "npm-run-all lint:*",
    "clean": "del-cli ./dist/**",
    "build:ts": "tsc  -p ./tsconfig.json --pretty",
    "build:html": "html-minifier --input-dir ./src --output-dir ./dist --file-ext html --collapse-whitespace --conservative-collapse --remove-comments",
    "build:scss": "node-sass ./src -o ./dist -q --output-style compressed",
    "build": "run-p clean unbundle && run-p build:*",
    "watch:ts": "tsc --pretty --watch",
    "watch:html": "chokidar \"./src/**/*.html\" -c \"yarn run build:html\" --initial",
    "watch:scss": "chokidar \"./src/**/*.scss\" -c \"yarn run build:scss\" --initial",
    "watch": "run-p clean unbundle && run-p watch:*",
    "bundle": "yarn run build && node ./scripts/bundler.js --bundle",
    "unbundle": "node ./scripts/bundler.js --unbundle",
    "release": "yarn run bundle && build --config ./build/config.yml",
    "test": "xvfb-maybe karma start --single-run",
    "e2e": "cross-env TS_NODE_PROJECT=./e2e/tsconfig.json xvfb-maybe ava-ts ./e2e/src --serial --verbose"

The Electron builds and tests run in CI on Windows, Mac and Linux only requiring yarn && yarn release using electron-builder. We include a github token in an environment variable on CI and electron-builder automatically puts the artifacts into Github releases on our private repository. It even takes care of building the half dozen native modules we use on each platform and in the required architectures.

electron-builder excludes almost everything by default so we can just define the things we want packaged in the app.

productName: App Name
  # this contains icons, licence agreement, etc
  buildResources: "./build"
  output: "./releases"
# include required root files
- "index.js"
- "index.html"
- "config.js"
# include all of static
- "static/**/*"
# Nothing in jspm_packages get packaged unless we define it here
- jspm_packages/system.js
# This doesn't get bundled so we have to include it
- jspm_packages/npm/pdfjs-dist@*/build/pdf.worker.js
# exclude dist apart from the bundles
- "!dist${/*}"
- dist/bundle-*.js
# electron-builder includes a pruned copy of node_modules 
# exclude all the build artifacts while ensuring we have the native module binary
- "!**/node_modules/*/build${/*}"
- "**/node_modules/*/build/**/*.node"
# exclude some other native artifacts
- "!**/node_modules/**/{*.cc,*.c,*.h,*.obj,*.pdb,*.sym,*.dsym}"
# Copy the correct native hardware drivers for the platform
- from: build/${os}/${arch}
  to: resources
  - "**/*"

After a release we manually push sourcemaps to so we get TypeScript code lines in our error reports and they get grouped nicely. The html-minifier options of --collapse-whitespace --conservative-collapse --remove-comments are specifically so our bundles are identical between builds on all 3 platforms. Without this we found the bundles and sourcemaps were slightly different which made the decoding and grouping break.

I’ve also recently helped work on sentry-electron which as well as capturing JavaScript exceptions also captures native crash minidumps, decodes the stacktraces and displays them with breadcrumbs telling you what the user was doing before the crash. We’re doing a lot of communication with native drivers so to have native error reporting has been great if only so I can prove to my customer that it’s their native code taking down the process!


We went for the features approach where different zones (admin, sales, customer) but also validation have their own feature folder.
The book Learning Aurelia has a nice chapter about this topic “6 Design Concerns - Organizing and Decoupling”. It describes the pros and cons of multiple variants.