📝: Some folders may be missing from the project structure as there the template does not include all the features. The structure is meant to be a guide and not a strict rule.
Most of the code lives in the src
folder and looks like this:
├── src/
│ ├── assets/ # assets folder can contain all the static files such as images, fonts, etc.
│ ├── components/ # shared components used across the entire application
│ ├── config/ # all the global configuration, env variables etc. get exported from here and used in the app
│ ├── constants/ # shared constants used across the entire application
│ ├── features/ # feature based modules
│ ├── hooks/ # shared hooks used across the entire application
│ ├── lib/ # re-exporting different libraries preconfigured for the application
│ ├── providers/ # all of the application providers
│ ├── routes/ # routes configuration
│ ├── stores/ # global state stores
│ ├── test/ # test utilities and mock server
│ ├── types/ # base types used across the application
│ ├── utils/ # shared utility functions
In order to scale the application in the easiest and most maintainable way, keep most of the code inside the features
folder, which should contain different feature-based modules. Every feature
folder should contain domain specific code for a given feature. This will allow you to keep functionalities scoped to a feature and not mix its declarations with shared modules. This is much easier to maintain than a flat folder structure with many files.
A feature could have the following structure:
├── src/features/awesome-feature/
│ ├── api/ # exported API request declarations and api hooks related to a specific feature
│ ├── assets/ # assets folder can contain all the static files for a specific feature
│ ├── components/ # components scoped to a specific feature
│ ├── hooks/ # hooks scoped to a specific feature
│ ├── routes/ # route components for a specific feature pages
│ ├── stores/ # state stores for a specific feature
│ ├── types/ # typescript types for TS specific feature domain
│ ├── utils/ # utility functions for a specific feature
│ ├── constants.ts # constants scoped to a specific feature
│ ├── index.ts # entry point for the feature, it should serve as the public API of the given feature and exports everything that should be used outside the feature
Everything from a feature should be exported from the index.ts
file which behaves as the public API of the feature.
You should import stuff from other features only by using:
import { AwesomeComponent } from "~features/awesome-feature"
and not
import { AwesomeComponent } from "~features/awesome-feature/components/AwesomeComponent
📝: If you want to be extra-strict, this can also be configured in the ESLint configuration to disallow the later import by the following rule:
{
rules: {
'no-restricted-imports': [
'error',
{
patterns: ['~features/*/*'],
},
],
}
// ...rest of the configuration
}
This was inspired by how NX handles libraries that are isolated but available to be used by the other modules. Think of a feature as a library or a module that is self-contained but can expose different parts to other features via its entry point.