Frontend Application Architecture (JavaScript, TypeScript)
Intro
A software engineer day to day work usually focused on some specific application or even piece of that software. When a long period of time developer works on only one program he may experience difficulties in the case he needs to start with a new project.
In this article, I would like to share my findings which come from frontend applications built by me in recent years.
Overview
First, a walk when planning a new application it has to be decided what is the main purpose.
There are two main directions for a web-based application:
- A website with a public content
- Web/Native application
For content websites, it’s highly suggested to use server-side rendering such as Next.Js, Angular SSR, Gatsby, or similar. These technologies will provide a better performance as well as better Search engine optimization.
On the other hand, web or native applications are used when it’s required a high level of in-app interactions.
In addition, it has to be defined whether the application will have offline mode or features. In that case, the application is considered a Progressive Web App and the usage of service workers will be required.
Proposed Technologies list
- State — Redux
- React, react-router
- UI — MUI or Bootstrap
- Linting — Husky, ESLint, prettier
- Testing — Jest
- CI/CD — Gitlab
Folders structure
The following folder structure can be used for the medium as well as small apps:
- Components — all components. Each can have input/output
- Containers — components defining specific layout
- Pages — A page will use one of the containers and contains components.
- Routes — contains route declarations
- Config — Constants
- Services
- Api specific files
- Authorization
- General services — such as traces/logs, system notifications, etc
- Store — Redux’s store files. Such as reducers
- The root folder will contain package.json, ESLint, etc.
- .ENV — environment-specific constants
For large and multi-application projects consider reading the article “Semantic Grouping Folders with Nx”.
General essential features
- Logging, tracing
- Authorization: Send credentials -> receive token. All manipulations with sensitive data should be working via the Authorization header.
- Centralized system notifications
- Generic pop-ups: Confirmation popup
- User activity statistics: Backend should save each user action/request for further analysis or an external service can be used.
Code modularity
Modularity is achieved by separating functionality into components. Each of them should have one responsibility. Components will have Input/Output data.
State management
Redux is one of the suggested options for state management. State flow in react app unidirectional and immutable. Immutability allows stability and debugging features such as states history. Where it’s possible to go “back in time” analyzing all the state changes.
There are two types of components:
- Stateless — has I/O data and does not manage states
- Stateful — manages states and transfers into the components. Also are dividing when to transfer state to regular components in order to reduce component rerendering
Performance
- APi calls In browser caching — rarely updated data should be stored in Browser’s cache. It is achieved by setting cache headers for HTTP responses.
- App files caching — images, fonts, js bundles should be cached in the browser.
- Reduce components re-rendering by limiting states flow.
- Lazy loading — the application will load only necessary bundle files. Achieved by code splitting techniques.
Code quality
In general, the code has to obey industry best practices. The code has to be easily readable and built of small components/methods/classes where each has one responsibility focused.
However, each developer may have his own code style preferences. Therefore it’s strongly suggested to force project-specific code standards. Alignment with code standards can be achieved by using the ESLint library. Pre-commit hooks will secure that no non-standard code arrives in the Git repository.
In addition code formatting could be done by Prettier. This operation can be attached to the pre-commit hook as well.
Code readability can be achieved with the help of the CodeMetrics, SonarLint, SonarQube vscode plugins, or similar. The tool will analyze the cognitive complexity of the code and will suggest improvements. In general functions/methods should be short and avoid multilevel nested loops or conditionals.
Type declarations are another important point in frontend development. Static type definition provides greater reliability as well as readability. The application should be written on TypeScript which has wide support and community.
Unit testing
Each component has to be test covered by at least 70%. Jest is one of the well-supported libraries for that purpose.
Versioning
Git is the most preferred option for version control.
Each commit has to obey commit message standards. This link gives a good explanation. Following the standard will provide good readability of the app development history.
Deployment
Gitlab can be used for managing deployments and Continous Integration. Repository updates have to be pushed as new branches. On each commit, Gitlab will run unit tests.
After the code review and pipeline passes, a merge request can be created. After the MR is approved, commits will become a part of the master/main branch and the original branch will be erased.
The application should have multiple deployment environments such as Stage, Dev, Production. The stage will have the latest master version. After it passes QA tests it can be promoted to Production.
Accessibility
The application has to be accessible to people with all, abilities. It has to support screen readers, color schemes, and font size adaptability.
Chrome Lighthouse dev tool can be used to analyze the level of accessibility covered by an application.
UI
- Use one of the well-supported UI frameworks such as Mui or Bootstrap
- Multiple themes support. At least two: light and dark modes should be
- Responsive — mobile-first approach. Guarantees the app will not lack functionality on all devices.
Security
At least the following points should be considered when creating a Frontend application.
Frontend:
- User-generated data sanitation. React and Angular natively support sanitation.
- Auth token secure storage in HttpOnly only cookies. Refer to the explanation on the OWASP page.
Backend:
- Limit number of HTTP requests per user in order to avoid DDOS attacks
- Limit login attempts
- OWASP rules
Migration
Styles separation — when creating custom styles separate a set of SCSS files containing all the common styles. In the case of migration to another SPA library, the styles can be reused.
It is always difficult to migrate a large codebase. For instance, an Angular app migrates to React. In the major part of the cases, each SPA library has its own architecture and it will not be possible to copy the components.
However such tools as NX or Module Federation can manage micro frontends and allow gradual migration from one SPA library to another.
Conclusion
The current article discussed the main best practices which should be considered when planning a new frontend application. All the mentioned techniques may seem like an exaggeration. Though each of them increases the maintainability and reliability of an application.
Let’s recap what we learned:
- Define the type of the project if it’s content-based or app
- Performance
- Modularity
- State management
- Code quality: TypeScript, linting
- Stability: Unit tests
- Versioning: Git
- Deployment: GitLab
- UI: MUI, Bootstrap
- Accessibility
- Security
- Migration
To read more articles written by me: anton-m.dev