An Angular microfrontend e-commerce application built with Nx, Module Federation, NgRx, and Angular Material. The workspace contains three apps (a host shell and two remote microfrontends) and four shared libraries.
| App | Role | Dev port |
|---|---|---|
store |
Host shell — composes the remotes, owns the NgRx store, renders toolbar / banner / footer | 4200 |
products |
Remote microfrontend — product grid with size selection and add-to-cart | 4201 |
checkout |
Remote microfrontend — cart summary and order total | 4202 |
| Library | Path | Role |
|---|---|---|
shared-data-access |
libs/shared/shared-data-access |
NgRx feature slices (products + cart), ProductsService, Product model, facades |
shared-ui |
libs/shared/shared-ui |
Shared presentational utilities — OptimizedImagePipe (wsrv.nl proxy) |
store-feature-shell |
libs/store/store-feature-shell |
Page layout (banner, footer, content projection), home tech-features cards |
products-feature-shell |
libs/products/products-feature-shell |
Products page, ProductCardComponent (dumb), OptimizedImagePipe |
checkout-feature-shell |
libs/checkout/checkout-feature-shell |
Cart item list, quantity controls, order total |
At startup the host fetches module-federation.manifest.json to discover where the remotes live, then uses @module-federation/enhanced to lazy-load their exposed ./Routes modules on navigation.
graph TD
Browser -->|":4200"| Store
subgraph Store ["store (host shell)"]
Toolbar["Toolbar + cart badge"]
Shell["StoreFeatureShell\nbanner · footer · router-outlet"]
Router["Angular Router"]
end
Router -->|"/products"| Products
Router -->|"/checkout"| Checkout
subgraph Products ["products (remote :4201)"]
PShell["ProductsFeatureShellComponent"]
PCard["ProductCardComponent\nsize toggle · add-to-cart"]
PShell --> PCard
end
subgraph Checkout ["checkout (remote :4202)"]
CShell["CheckoutFeatureShellComponent\ncart items · total"]
end
subgraph Shared ["libs/shared/shared-data-access"]
Facade["StoreFacade / CartFacade"]
Store2["NgRx singleton store"]
Facade --> Store2
end
Store --> Shared
Products --> Shared
Checkout --> Shared
The NgRx store is provided once in the host (
app.config.ts). Because Module Federation shares the Angular runtime, every remote dispatches to and selects from the same singleton store instance — a cart action dispatched in the products remote updates the host toolbar badge in real time.
graph TD
Browser -->|"port 80"| Proxy["nginx reverse proxy\nroutes by URL prefix"]
Proxy -->|"/"| StoreNginx["store\nnginx"]
Proxy -->|"/products/*"| ProductsNginx["products\nnginx"]
Proxy -->|"/checkout/*"| CheckoutNginx["checkout\nnginx"]
The proxy strips the prefix before forwarding, so remotes are referenced with relative paths (/products/mf-manifest.json) — no hardcoded hostnames or ports anywhere.
| Browser URL | Routed to |
|---|---|
/ |
store container |
/products/* |
products container |
/checkout/* |
checkout container |
Each app container is built with a two-stage Dockerfile: a Node build stage runs nx build, then the output is copied into a minimal nginx image.
The app uses the modern NgRx APIs throughout: createFeature, createActionGroup, and the Facade pattern to keep components decoupled from the store.
- Effects are used only for async work (loading products from the API).
- Cart is fully synchronous — no effects needed, the reducer handles everything in-memory.
- Components never import NgRx directly; they talk to a
StoreFacadeorCartFacade.
sequenceDiagram
participant Component
participant Facade
participant Store as NgRx Store
participant API as Fakestore API
note over Component,API: Products load
Component->>Facade: loadProducts()
Facade->>Store: dispatch action
Store->>API: GET /api/products
API-->>Store: Product[]
Store-->>Facade: selectStoreProducts
Facade-->>Component: storeProducts$
note over Component,Store: Add to cart
Component->>Facade: addToCart(product, size)
Facade->>Store: dispatch action
Store-->>Facade: selectCartItems / selectCartCount
Facade-->>Component: cartItems$ · cartCount$ · cartTotal$
Install dependencies and start all three apps with a single command:
npm install
npx nx serve store --devRemotes products,checkoutOpen http://localhost:4200.
The host discovers remotes via apps/store/public/module-federation.manifest.json which points to localhost:4201 and localhost:4202.
Builds all three apps and starts them behind an nginx reverse proxy on port 80.
docker compose up --buildOpen http://localhost.
To rebuild a single service after a code change:
docker compose up --build storeTo stop and remove containers:
docker compose downRuns nx serve inside a container with the source directory mounted. Useful for teams without a local Node setup or for CI preview environments.
docker compose -f docker-compose.dev.yml upOpen http://localhost:4200.
Note: The first run installs
node_modulesinside the container — this takes a few minutes. Subsequent runs reuse thenode_modulesnamed volume and start much faster.
├── docker-compose.yml # production: proxy + 3 app containers
├── docker-compose.dev.yml # development: single container with nx serve
├── nginx/
│ ├── proxy.conf # reverse proxy routing rules
│ ├── store.conf # SPA fallback (try_files → index.html)
│ └── remote.conf # static file server for remotes
├── apps/
│ ├── store/
│ │ ├── Dockerfile
│ │ └── public/
│ │ ├── module-federation.manifest.json # dev (localhost URLs)
│ │ └── module-federation.manifest.prod.json # production (relative paths)
│ ├── products/Dockerfile
│ └── checkout/Dockerfile
├── libs/
│ ├── shared/shared-data-access/ # NgRx + API + models
│ ├── store/store-feature-shell/ # page layout + home
│ ├── products/products-feature-shell/ # product cards
│ └── checkout/checkout-feature-shell/ # cart view
Run any Nx target directly:
# Build a single app
npx nx build store
# Lint
npx nx lint products
# Test
npx nx test checkout
# Visualize the project graph
npx nx graph
# Add library
npx nx g @nx/angular:lib libs/store/store-data-access --style=scss