Repository of a fullstack WhoisQuery Application. Originally split into two separate microservices, Whois-Monorepo brings together in one Monolith both backend (server) and frontend (client).
To make the app truly reactive and ready to be manageable at scale, we opted to manage the state with NgRx and recreate Redux-like Patterns to establish a single source of truth to manage the interactions on the client-side.
π STORE
|_ state
|_ actions
|_ reducers
|_ effects
|___ selectors
We procured to keep the code modular, the functions pure and keep side effects to the bare minimum.
Example given, instead of chaining multiple actions within an effect, we opted to draft highly targeted action collections, with a tight scope of execution, that can be atomically actionable and dispatchable as needed from the UI.
- Domain Actions - for API interactions
- UI Actions - for User Interface / User Experience interactions and affordances (e.g: display/hide results, provide validation feedback on user input, etc.)
- WatchList (for additional Ui filtering and scope management)
Add ESLint (linting), Prettier (code-formatting), Husky (for git hooks - pre-commit and pre-push) and Lint-Staged (for running linting only on staged files)
For styling our UI, we opted for TailwindCSS, not only for allowing for fast prototyping, but also for the optimized CSS builds, which are significantly smaller than regular CSS.
To reduce vulnerabilities associated with external dependencies in the core service of the application, we relied solely on the default NET module from NodeJS, which provides an asynchronous network API for creating stream-based TCP servers.
We define environment variables for allowedOrigins and pass them as options on CORS middleware to verify the requests are coming from trusted origins.
On Render, we safely store those environment variables to be accessed in PRODUCTION.
Moreover, we also define the HTTP Request methods to be allowed - for now, only GET requests are allowed, given that we are only storing queries logs on the database.
Using helmet as Middleware to set custom headers, we define custom security policies to reduce the surface and preventing against MIME-Type sniffing, Man-in-the-Middle and XSS-Cross Site Scripting attacks.
We also validate the query parameters from the client against REGEX patterns to match the following conditions: - The domain name shall have between 1 and 63 characters. - Only alphanumeric characters and hiffens (-) are acceptable. - Period (.) signs are allowed as long as they are not placed neither at the beginning nor at the end of the SLD. Also, we implemented sanitization against having two dots in a row; - Parallel validation of the Top-Level Domains against the list of supported TLD by the application.
All the controllers provide consistent error handling, using try/catch blocks, with semantical comments both for guidance over development, but also to provide contextual feedback to enhance the UX / UI, without exposing sensible information on the App architecture.
Although the core of the data validation strategy occurs on the server-side, we still implemented client-side validation on Reactive Forms and disable submission when forbidden characters or missing fields are missing.
Naturally, this doesn't prevent curl / Man-in-the-middle or XSS attacks by itself, but our production environments count with SSL encryption, and we also besides have server-side validation and CORS Policies.
Although we are not dealing with highly-sensitive information (such as passwords, credit card details, etc), we procured to prevent browser caching of user inputs by setting HTML attributes to override the default configuration.
<input formControlName="sldInput" class="w-3/4 h-20 text-xl p-10" type="text" placeholder="Enter a domain name" type="text" min-length="1" maxlength="63" spellcheck="false" autocomplete="off" autocorrect="off" autocapitalize="off">>
In order to keep the codebase clean and maintainable, we followed separation of concerns conventions and keep separate folders and files for Controllers, Models, Utils and Configurations.
Backend: Render.com
Front-End: Vercel
Enable automatic package management with dependabot.
version: 2
updates:
- package-ecosystem: 'npm'
directory: '/'
schedule:
interval: 'daily'
allow:
# to distinguish between development and production environments
- dependency-type: 'production'
ignore:
# dependencies listed here will be ignored
- dependency-name: '@angular-devkit/build-angular'
As further steps, I would refine the following steps:
π Implement JWT
π Expand ErrorBoundaries and Pending / Loading responses
π Expand Test suite
π Responsive Design: Elaborate on media queries for adaptive breakpoint layouts.
π Further CRUD operations for data persistance of the user's watchlist options.
π Remap the whois lookup to an Interface on the server-side
Currently the API request is returning a raw response, which corresponds to an interface IDomainResult that is used for the first load.
Once the user adds a domain to the WatchList, once he clicks it, the response is than parsed to a "prettified" version with a selection of relevant fields from the Whois Query.