This document describes the main commands used during the project's development and the technical reasoning behind the choices made.
The goal is not to provide a step-by-step tutorial, but to make explicit the decisions that influence the project's architecture, maintainability, and evolution.
pnpm dlx create-nx-workspace@latest assignment-ftechnology \
--preset=apps \
--pm=pnpm \
--nxCloud=skip
This command is used to initialize a new Nx workspace within a dedicated folder, configuring the project as a monorepo.
dlx is the pnpm equivalent of npx.
It allows you to:
- execute Node packages without installing them globally
- ensure the use of a clean and isolated version of the tool
- avoid uncontrolled global dependencies
This mode is particularly suitable for bootstrap and scaffolding commands.
Runs the official Nx generator to create a new workspace.
Using the @latest tag indicates the latest stable version available at the time
of execution, ensuring:
- access to the most up-to-date features
- alignment with best practices recommended by the Nx framework
Name of the workspace and the main project directory.
A descriptive name consistent with the assignment context was chosen to:
- make the project immediately recognizable
- keep the repository name and Nx workspace name aligned
The preset parameter defines the initial workspace type.
The apps option creates a neutral workspace without automatically generating
applications or libraries, leaving full control over the initial structure.
This choice allows to:
- manually create applications (
web,api) only when necessary - avoid unused generated code
- keep the monorepo clean and intentional
Other available presets include:
react-monoreponestnextangularnode-monorepo
In this project, a generic preset was preferred to model the architecture explicitly and progressively.
Specifies the package manager to use in the workspace.
pnpm was chosen for the following reasons:
- better dependency management in monorepo contexts
- faster installations
- lower disk space usage thanks to the global store
- native support recommended by Nx
Disables Nx Cloud configuration.
For this assignment:
- a distributed CI pipeline is not required
- remote caching is not necessary
- reducing non-essential external dependencies is preferred
This choice keeps the project simpler and focused on the required aspects.
This command represents the starting point of the project and defines the architectural foundations upon which the frontend applications, backend applications, and shared libraries will be built.
All choices made in this phase aim to:
- keep the project scalable
- avoid premature configurations
- favor clarity and maintainability
docker compose up -d
This command starts a local instance of PostgreSQL via Docker,
using the docker-compose.yml file located in the project root.
The goal is to provide a completely offline development environment, without dependencies on external cloud services.
Using Docker Compose allows to:
- start the database with a single command
- avoid manual PostgreSQL installations
- ensure a consistent environment across different developers or reviewers
- allow the assignment reviewer to run the project locally without additional configurations
The project is configured to work with both:
- Cloud PostgreSQL (Neon) – for demo and remote development
- Local PostgreSQL (Docker) – for offline development and technical review
Switching between the two environments is done simply by modifying the DATABASE_URL
variable in the .env file.
This choice ensures maximum flexibility without introducing complexity into the application architecture.
pnpm add -D prettier
pnpm nx format:check
pnpm nx format:write
Nx uses Prettier for formatting commands (nx format:*).
Installing Prettier as a devDependency makes the toolchain reproducible for anyone cloning the repository.
format:check is used to verify that the codebase is formatted correctly,
while format:write automatically applies the changes.
In a newly created Nx workspace (without apps/ or libs/), projects with a lint target do not exist yet.
For this reason, pnpm nx lint requires a specific project:target argument.
Once apps/web and apps/api are present, the following command will be used:
pnpm nx run-many -t lint
run-many is an Nx command that allows executing the same target
across multiple workspace projects simultaneously.
It runs the lint target (-t lint) on all workspace projects
(applications and libraries) that expose it, allowing for code quality verification
across the entire monorepo with a single command.
This command becomes the reference as soon as the workspace contains multiple projects and is easily integrable into a CI pipeline.
node -v
The .nvmrc file defines the recommended Node.js version for the project.
- ensure development environment consistency across different developers
- avoid issues related to Node version discrepancies
- make the project setup reproducible
Tools like nvm or fnm automatically use this file to select the correct Node version.
pnpm nx g @nx/nest:application apps/api \
--name=api \
--linter=eslint \
--unitTestRunner=jest \
--e2eTestRunner=none
Notes:
apps/apiis the positional parameter[directory]of the generator (project root).--name=apiis the project name within Nx.--e2eTestRunner=noneskips e2e tests for now (not required in the initial phase).
pnpm nx show project api
pnpm nx lint api
pnpm nx test api
pnpm nx format:check
pnpm nx graph
pnpm nx g @nx/js:lib libs/api/db --bundler=tsc
Description
This command generates a TypeScript library dedicated to database access for the Backend API.
The library is placed in libs/api/db, following a feature-oriented structure and maintaining the database as an isolated concern relative to the rest of the application.
Parameter Explanation
@nx/js:libNx generator for framework-independent JavaScript / TypeScript libraries, ideal for shared or infrastructural logic.libs/api/dbExplicit path for the library. The structure reflects the usage context (api) and responsibility (db), avoiding generic libraries and favoring clear separation of architectural boundaries.--bundler=tscUses the TypeScript compiler (tsc) as the build system. This choice is intentional:- database code does not require advanced bundling
- keeps the build simple, transparent, and fast
- improves debuggability and maintainability
- integrates perfectly with Drizzle ORM and Node.js environments
Architectural Motivation
Isolating database access in a dedicated library allows to:
- avoid direct coupling between the API and the persistence layer
- facilitate testing, refactoring, and database replacement
- keep the backend modular and scalable
- fully leverage the advantages of the Nx monorepo
For the creation of the API authentication module, Nx generators for NestJS were used to maintain structural consistency and best practices.
pnpm nx g @nx/nest:module apps/api/src/app/auth/auth
pnpm nx g @nx/nest:service apps/api/src/app/auth/auth
pnpm nx g @nx/nest:controller apps/api/src/app/auth/auth
Nx generators allow to:
- automatically create files and correct wiring
- maintain naming and structural standards
- correctly integrate code into the Nx graph
Start API in development mode (watch):
pnpm nx serve api --watch
Quality gate:
pnpm nx lint api
pnpm nx test api
pnpm nx format:check
Nx cache reset (useful if watch doesn't detect changes or the daemon is in an inconsistent state):
pnpm nx reset
Login (copy accessToken):
curl -s -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"email":"paolo@example.com",
"password":"Password1",
"rememberMe": false
}'
Protected endpoint /me:
curl -s http://localhost:3000/api/me \
-H "Authorization: Bearer <ACCESS_TOKEN>"
Access history:
curl -s "http://localhost:3000/api/me/access-history?limit=5" \
-H "Authorization: Bearer <ACCESS_TOKEN>"