(my) Best Practices for Aurelia - Application Structure

We get a lot of questions in the official Aurelia gitter regarding some basic best practices so I wanted to share a few quick thoughts and examples that are my own -

Application Structure

One of the first things that comes up when structuring an application with an intention of later using it in production is How should I structure my application to scale?

The truth is at first, it doesn't matter. Start simple by putting everything in the src folder until you have the need to break things out. Once yhou hit that point then check back here, read my ramblings, and let me know what you think in the comments.

Breaking our App into functional areas

First things first - we have a few basic common pieces of functionality that no matter what kind of application you are using you will need. I usually base these off of tried and true practices from Durandal.js. This involves breaking out into four main folders -

  1. Services
  2. Models
  3. Templates (Widgets)
  4. Configs (optional)
Services

Sample structure -

- /app
| -- /services
    | -- /dataservices
    | -- session.js
    | -- datacontext.js
| -- /models
| -- /templates
| -- /configs

Services are generally modules that are singletons and are meant to perform some operations or functions for your app that are re-used all over the place. A typical service that I use is session.js which is a module for handling session-state type information. I always have a currentUser for example.

I also put data-access modules here. Typically I use the pattern of having a datacontext or similar named module that is basically an unit of work that acts as an interface to the other data services.

I also put all of my actual data-access services in this folder in a sub-folder called something like data or dataservices. I find this generally helps a bit more when an app grows large and you want to have a separation of concerns. These modules are very specific to the model that they are providing functionality for.

Models

Sample structure -

- /app
| -- /services
| -- /models
    | -- base.js
    | -- user.js
| -- /templates
| -- /configs

Models are the bones of our skeleton. If you aren't using models in your JavaScript you are relying on magic. I prefer to put all of my models into categories inside of my models folder. In the example above the user.js module will probably have a User model, an Address model, and other user-related models.

The base model would probably have app-specific models. If I have a 3 column layout with widgets that show up in each column I'm probably going to use a model to represent my column -

export class Column{  
  constructor(name = '', widgets){
    this.name = name;
    this.widgets = data.widgets || [];
  }
}

Now I know anywhere in my app where I represent an instance of a column that I can push into the widgets property, even if it is empty.

If I start to find that I really have more complex needs for my models and serialization and such I can simply replace the class with a helper that uses something like breeze.js to create a user without having to find all the places where I used new User() and refactor it all.

Templates

Sample structure -

- /app
| -- /services
| -- /models
| -- /templates
    | -- dropdown.js
    | -- dropdown.html
| -- /configs

Note : I use template loosely here to represent a 'widget' if you will

In every app we have a need to use some shared controls and such. I love to put these into a templates folder that gives me the ability to put all functionality and markup of these widgets into a view / view-model pair. In the example above I may put bootstrap's dropdown markup into the .html file and all of the functionality driving how I want it to work in the view-model.

Sometimes I find that these templates are specific to the region of the app in which they are used so feel free to adapt this to your application as best fits.

If you are building an app that you foresee lot's of developers in your team using together I highly suggest spending the first sprint or so focused solely on getting these widget templates ironed out and agree on their usage and needs. It will save you an immeasurable amount of time going forward.

Configs

Sample structure -

- /app
| -- /services
| -- /models
| -- /templates
| -- /configs
    | -- app.js
    | -- services.js

The configuration files are optional at best. I use them because I know that eventually me or my team will need to share some logic and I'd rather be proactive about where it goes than try to clean it up later. A good example of what might go in the services.js here is end-point specific information -

export class Services {  
  basePath = 'http://github.com';
  loginAppDomain = '/users/login';
}

Now anywhere around my app that I need to construct a url I can reference the services.basePath once I import ./configs/services. This is especially helpful when you deploy to various environments such as Develop, QA, and Production. You can have configuration information here in one place -

if (document.location.origin === 'http://patrickwalters.net'){  
  basePath = 'api.patrickwalters.net';
} else if (document.location.origin === 'localhost') {
  basePath = 'localhost:2000';
}
export { basePath }  
Naming conventions

You notice above that I have the same name for files that reside in various folders. I do this for a reason and it is definitely a personal preference. I hate redundancy so app/src/models/base-models.js feels redundant and so I use conventions to name things. If I add a github model I add it as models/github.js and put any related data-access services into the services/data/github.js file. Now any developer working on my project can follow that convention easily.

What about views and view-models?

For all of my views and view models I choose to break them out into functional areas, usually that match my router. If my routes look like this -

/users
/bars
/beers

Then my app structure will end up like this -

- /app
| -- /services
| -- /models
| -- /templates
| -- /configs
| -- /users
   | -- index.js
   | -- index.html
| -- /bars
   | -- index.js
   | -- index.html
| -- /beers
   | -- index.js
   | -- index.html

I choose this convention because I think of the index as the base page. Any child routers will build onto that structure and continue to nest the directories. You may say

You: Hey Patrick but what about those really long path names that will be generated like 4 levels deep?!?! zomg nub...

to which I would reply -

Me: Well, generally you should only have dependencies that start from root or go down a single level so it's not so bad

What's missing?

You tell me in the comments below.