A fullstack monorepo built with Nx, Angular 18+, Angular Material, and NestJS, implementing a user management system with shared Zod validation.
- Monorepo: Nx
- Frontend: Angular 18+, Angular Material (Material 3), SCSS
- Backend: NestJS
- Validation: Zod (shared between frontend and backend)
- Language: TypeScript
pdr-cloud/
apps/
frontend/ → Angular app (port 4200)
api/ → NestJS backend (port 3000)
libs/
shared/ → Shared Zod schemas, interfaces, DTOs
nx.json
package.json
tsconfig.base.json
- Node.js 18+
- npm 9+
npm installCopy the provided users.json to the API data folder:
mkdir -p apps/api/data
cp users.json apps/api/data/users.jsonnpx nx serve apiAPI runs at: http://localhost:3000
npx nx serve frontendApp runs at: http://localhost:4200
Open two terminal windows and run one command in each.
| Method | Endpoint | Description |
|---|---|---|
| GET | /users | Returns all users |
| GET | /users/:id | Returns user by ID |
| POST | /users | Creates a new user |
curl -X POST http://localhost:3000/users \
-H "Content-Type: application/json" \
-d '{
"firstName": "John",
"lastName": "Doe",
"email": "john@doe.com",
"phoneNumber": "123456789",
"birthDate": "1990-01-01",
"role": "admin"
}'- RESTful API with 3 endpoints
- Zod validation on
POST /users - Conditional role-based validation (see below)
- In-memory storage with file persistence to
apps/api/data/users.json - Simple write queue to prevent race conditions
- CORS enabled
- Material 3 custom theme with brand colors
- Sticky top navigation bar
- User List: Material table with pagination (25/page) and full-name search
- User Details: Dialog showing full user info loaded from
GET /users/:id - User Create: Reactive form with live Zod validation and snackbar feedback
- Smiley: Pure CSS responsive smiley face at
/smiley(no images or SVGs)
UserinterfaceuserSchema— Zod schema used by both Angular and NestJSCreateUserDto— inferred from Zod schema
The shared Zod schema implements dynamic validation using superRefine:
| Role | Required Fields |
|---|---|
| admin | phoneNumber + birthDate |
| editor | phoneNumber |
| viewer | none (beyond base fields) |
This logic lives in libs/shared/src/lib/user.schema.ts and is imported by both the NestJS controller and the Angular creation form — ensuring consistent validation across the stack.
| Route | Component | Description |
|---|---|---|
/ |
UserListComponent | Main user table with search |
/smiley |
SmileyComponent | Pure CSS responsive smiley face |
| Token | Value |
|---|---|
primary |
#1da4e8 |
secondary |
#20d2a8 |
tertiary |
#e4dc46 |
error |
#d32f55 |
- Nx was chosen as the monorepo tool per the assessment's preference. It provides built-in generators for Angular and NestJS and handles cross-project references cleanly.
- Standalone Angular components (no NgModules) were used throughout, following Angular 18+ best practices.
- Zod over class-validator was chosen as the PDF preferred it and it works isomorphically in both Node.js and the browser, making true schema-sharing possible.
superRefinewas used for conditional validation instead ofdiscriminatedUnionbecause the role field is part of the same object —superRefineallows cross-field validation in a single schema without splitting into separate schemas.- Lazy-loaded dialogs (
import()) are used for the details and create components to reduce initial bundle size. - Write queue in
UsersService(backend) ensures sequential file writes to prevent data corruption under concurrent requests. ChangeDetectorRef.detectChanges()is used in components that update state inside observables, preventing Angular'sNG0100 ExpressionChangedAfterItHasBeenCheckedError.
- The provided
users.jsonseed data contains intentional inconsistencies (typos likefistName, invalid emails, string IDs). These are loaded as-is and served by the API without sanitization, since they are seed/test data. - Authentication and authorization are not implemented — all endpoints are public.
- The backend uses a simple synchronous write queue. For high-concurrency production use, a proper async queue or database would be recommended.
- The Angular Material theme uses CSS custom properties (
--primary, etc.) for custom colors alongside the Material 3 theme setup, since full M3 dynamic color generation requires the Material Design token pipeline.
# Serve frontend
npx nx serve frontend
# Serve backend
npx nx serve api
# Build frontend
npx nx build frontend
# Build backend
npx nx build api
# Lint frontend
npx nx lint frontend
# Lint backend
npx nx lint api