Getting Ourselves Organized

File & Folder Structure for an Elm SPA

Getting Ourselves Organized

I’ve been asked by a number of folks: “What does the proper Elm file structure look like?” While the answer may be a bit nuanced as the answer is often dependent on your own project, I can show you what’s working for me after trying a bunch of different styles.

image::/assets/images/mandelbrot_set.jpg[""]

What I’ve found in trying different patterns is that a structure that is built around features, has some room for globals, but is mostly fractal in design allows for scalability, refactorability, & reason-about-ability.

Breaking down the problem

So let’s talk about a sample problem I’ve toyed around with—hitting the Tinder API. Without thinking about authentication & the actual requests, Tinder is essentially 3 things (which are coincidently the 3 views):

  • A user profile
  • A platform for viewing & passing judgment on other profiles
  • Messaging between users that have matched

A bad idea

$ tree .
Tinder
├── Apis …
├── Components …
├── I18n …
├── Models
│   ├── Model.elm
│   ├── Profile.elm
│   ├── Judgment.elm
│   └── Messaging.elm
├── Msgs
│   ├── Msg.elm
│   ├── Profile.elm
│   ├── Judgment.elm
│   └── Messaging.elm
├── Routes
│   ├── Route.elm
│   ├── Profile.elm
│   ├── Judgment.elm
│   └── Messaging.elm
├── Routing
│   ├── Routing.elm
│   ├── Profile.elm
│   ├── Judgment.elm
│   └── Messaging.elm
├── Types …
├── Updates
│   ├── Update.elm
│   ├── Profile.elm
│   ├── Judgment.elm
│   └── Messaging.elm
└── Views
    ├── Views.elm
    ├── Profile.elm
    ├── Judgment.elm
    └── Messaging.elm

This is the kind of structure that I knee-jerked towards when I first got jumped into project that finally took up more than a single file. You’ll see this kind of pattern often floating around other front-end frameworks you may know. But not building around features presents us with 3 problems:

  1. Some of my features are very small & don’t require a separate file for each of the frameworks ideas? (I might keep all of it in one file but separate the view into something else)
  2. What if I want to change frameworks & their model for the application no longer fits this paradigm?
  3. What if I have sub-features? (Nested routes, views, etc.)

Using a Sample Feature-Based Structure

$ tree .
Tinder
├── Apis …
├── Components …
├── I18n …
├── Judgment
│   ├── Model.elm
│   ├── Msg.elm
│   ├── Route.elm
│   ├── Routing.elm
│   ├── Update.elm
│   └── View.elm
├── Messaging
│   ├── Overview
│   │   ├── Overview.elm
│   │   └── View.elm
│   ├── Message
│   │   ├── Message.elm
│   │   └── View.elm
│   └── Messaging.elm
├── Profile
│   ├── Model.elm
│   ├── Msg.elm
│   ├── Route.elm
│   ├── Routing.elm
│   ├── Update.elm
│   └── View.elm
├── Types …
├── Model.elm
├── Msg.elm
├── Route.elm
├── Routing.elm
├── Update.elm
└── View.elm

I’m not saying I’d do it exactly this way, but it’s a solution. Here you can see how the root level & the feature levels mimic each other’s structure. What you then do at the root level’s piece is merely forward along just what each feature needs letting it have a self-contain-y feel to it. To explain an example, Update would contain an update function where given a Profile.Msg(..) type, we can then call Profile.Update.update with the appropriate Msg & Model.

In the Messaging folder you can see that there’s a subfolder structure in which in this example would hold all the pieces but the View.elm because maybe it’s not that complicated & it’s easier (and requires a ton less imports) to build all that functionality. At Messaging.Messaging we could have some basic forwarding logic to each sub piece just like root-level stuff, continuing this recursive design.

Global Folders

Certain parts of your application will have data thats shared between all of the features—this is why we have some global folders. For the few projects I’ve touched these folders include

Apis
This is where I bundle up all of the [.abbr]#REST# calls to abstract away the [.abbr]#URLs#, their building, & their types.
Components
Self-contained widget-y things like custom implementation of a drop-down menu, or a video player. They should be reusable & consumable at any level.
I18n
If you need translations, a folder for internationalization phrases wouldn’t be a bad idea.
Types
Usually there are types & type aliases you be dealing with all over the application. In the Tinder example a file for User.elm would be useful for describing a user & a Message.elm which would describe the structure of a message.

Takeaway

So, just like the Mandelbrot set is a recursive structure that contains more copies of itself, we too can build our project to look about the same on every level which not only scales better as our project becomes larger, but also is easier to think about the app since each child part mimics its parent.