App structure for big Vue projects
Tags: webdev, javascript, frontend, vue
Published: 2024-02-09 09:29:18

Choosing the right folder structure for a middle-size or big vue application might be intimidating and it's even more so when there are not many suggestions about the topic.

From 2017 to the moment of writing this post, I've been working on Vue applications for years from medium to large, established apps migrating to Vue to projects at the start of the process. From Nuxt, Laravel, and Vue SPA; here I will try my best to describe what I think has been the more intuitive easy to work and maintain folder structure.

## The alternatives
First I want to talk about the alternatives considered. 

### Atomic Design

This is a very complete structure to define your building blocks according to their complexity, size, and how they interact with each other taking inspiration from one of the most well-structured systems the building blocks that compose our body

It is organized as follows:

- Atoms
- Molecules
- Organisms

Those will help us to aisle functionality, making portable and reusable components even easier to unit test.

Then we used to group those organisms in **templates** to give them context and form in a layout and pages that are the single entry point of the content we show to our users.

- Atoms
- Molecules
- Organisms
- Templates
- Pages

```
|_atoms/
   |__Input.vue
   |__Button.vue
   |__Label.vue
|_molecules
   |__FieldGroup.vue
|__organisms
   |__ContactForm
```

### Benefits
- **Hint of complexity**
You get a hint about the complexity of your components ahead. just by seeing where the component is placed: atoms are an indicator that the complexity is low and if are placed in organisms it is more complex.

- **Reusability**
As you are working with building blocks they are like Legos that you can move around atoms can be part of many molecules and organisms


- **Testability**
As components tend to be smaller and only do in isolation 

- **Perfect match with design systems**
If you are starting an application from 0 and need to build a consistent design system this is almost a no-brainer 

### Cons
-  When applied directly in an application structure you'll feel your components are all over the place.
- If an atom is just used by one molecule or single organism you can't group them by context
- Business logic 

In my experience, we can get the most from Atomic Design when we are building a component library/Design System that will be the foundation of our system: Buttons, Input, Input groups, Form Wizards, SearchBars, Selectors, etc.

## The Chosen One

When you are building an application you need as much context as possible in your project in the components that handle the business logic. For example, if I have a budget 

```
|_Budget/
   |__Budget.vue
   |__BudgetCategory.vue
   |__BudgetCategoryItem.vue
```

The dev experience could improved greatly when building a new feature or removing or refactoring a component because it has much more context of where is used and its props (BudgetCategoryItem could have a `BudgetCategory` prop for example, and not a generic `row` prop) en what domain dominio y and which is its closest parent component.

## Proposed structure:

```
|__ assets
|__ components/
|__ locales/
|__ plugins
    |__ i18n/index.ts
    |__ auth0/index.ts
    |__ loger/index.ts
    |__ axios/index.ts
|__ config/
|   |__ index.ts
|__ domains/
    |__ [domain]
       |__ api/  
       |__ models/
       |__ enums/
       |__ components/
           |__ RequestModal.vue
       |__ composables/
       |__ tests/unitTest.ts 
|__ pages
|   |__ auth/
|       |__ Partials/
|           |__ AuthLayout.vue     
|	    |__ AuthSignIn.vue
|	    |__ AuthSignUp.vue
|	    |__ AuthRecover.vue
|	    |__ AuthReset.vue
|   |__ [domain]
|        |__ Partials/
|            |__ SubpageLayout.vue
|            |__ [ComponentUsedOnce].vue
|        |__ subpageList.vue
|        |__ subpageEdit.vue
|        |__ subpagesCreate.vue
|__ router/index.ts     
|__ store/
     |__ Modules/
     |__ index.ts 
|__ utils/   
|__ App.vue
|__ main.ts
 
```

### assets

This directory can contain images, SVG, CSS or SCSS for the application.

### components

This component holds all the shared components of the application and they can be used in any part of the system.

### config

Access to env variables with `import.env.VITE_VARIABLE_NAME` across our app loads too much dependency on the bundler to centralize all this in a single place and give us some good ts support and add default values.  

In one of my projects, it looks like:
```
``// config/index.ts 
interface AppConfig {
    FIREBASE_API_KEY: string;
    FIREBASE_PROJECT_ID: string;
    FIREBASE_APP_ID: string;
    FIREBASE_SENDER_ID: string;
    PUSH_PK: string;
    MEASUREMENT_ID: string;
    GOOGLE_APP_KEY: string;
    GOOGLE_APP_CLIENT: string;
    FIREBASE_VAPID_KEY: string;
    IS_DEMO: boolean;
}

const isDemo = import.meta.env?.VITE_APP_DEMO

export const config: AppConfig = {
    FIREBASE_API_KEY: import.meta.env.VITE_FIREBASE_APP_KEY,
    FIREBASE_PROJECT_ID: import.meta.env.VITE_FIREBASE_PROJECT_ID,
    FIREBASE_APP_ID: import.meta.env.VITE_FIREBASE_APP_ID,
    FIREBASE_SENDER_ID: import.meta.env.VITE_FIREBASE_SENDER_ID,
    PUSH_PK: import.meta.env.VITE_PUSH_PK,
    MEASUREMENT_ID: import.meta.env.VITE_MEASUREMENT_ID,
    GOOGLE_APP_KEY: import.meta.env.VITE_GOOGLE_APP_KEY,
    GOOGLE_APP_CLIENT: import.meta.env.VITE_GOOGLE_CLIENT_ID,
    FIREBASE_VAPID_KEY: import.meta.env.VITE_FIREBASE_VAPID_KEY,
    IS_DEMO: Boolean(isDemo) && isDemo !== 'false'
}
```


###   domains

Contains the business logic of the application grouped by domain (remember DDD?) and each domain will have the sections: api, models, components, composables, unit tests

_**Subfolders**_**:**

- **api**: contains all the API calls of the application as endpoints are in a central place we change once if it used in different parts of the app
    
- **models**: Contains all the interfaces and types of the domain

- **components**: Contains all the domain-related components
    
- **composables**: Our domain-related vue composables 
    
- **tests**: Our unit tests
    

### Pages

These components would be used only by the router and would make the main call to the endpoint, permissions (ACL), and also group different domain controllers or partials.

_**Subfolders**_

- **Partials**: Partials are the only directory allowed on pages. They are only used by their respective pages `BudgetSectionTemplate.vue` think about them as templates or layouts.  

### Plugins

Third-party services that you can change like eg. Auth0, Axios, i18n

## Trade-offs

**Pros:**

- Provide context about business logic/business domain in the application. It would benefit onboarding new devs to the code.
    
- As the code is grouped by domains you can extract functionality easily to make a library if it's required for another project.
    

**Cons**

- Nested folders
    
- Elements could have more than one domain
    

## Solving conflict points:

1. **How to avoid over-nested components?** Sometimes we tend to group subcomponents by their technical connotation eg.  
      
```
Components 
   |__ Notifications 
      |__ NotificationCard 
      |__ NotificationTypes/ 
         |__ TaskApproved 
         |__ TaskRejected
```

An unnecessary nesting could be avoided by naming the component with the domain in front or creating another domain.

Eg.1
```
Components 
   |__ Notifications 
   |__ NotificationCard 
   |__ NotificationTypeTaskApproved 
   |__ NotificationTypeTaskRejected
```


Eg. 2

```
Components 
   |__ Notifications 
      |__ NotificationCard 
      |__ // ...more components 
   |__ NotificationTypes 
      |_ NotificationTypeTaskApproved 
      |_ NotificationTypeTaskRejected
```

2. **How to deal with a component that seems to have multiple domains?**  

    Sometimes a domain may cross with another for example widgets that are shown in the dashboard, should be put in a dashboard domain or its domain (Projects/ Tasks/ Tracks).  
      
    In this case we should analize the composition of the component and the requirements In this case the widgets are only used in the dashboard and if we go to its composition and props like `title` `graphType`and `data` all are the same in structure. Then we can conclude that each widget can be safely placed in the `Dashboard` domain.