-
Spring WebFlux as Resource Server (Spring Boot 4.0)
-
Keycloak as OIDC provider
-
Angular 21 application with SSO
-
App store example with PWA support
-
Launch Employee Angular front end directly or using app store
-
Two roles: Admin and User
-
Admin can perform CRUD operations
-
User role can view only
-
Role validation at both frontend and backend
-
Credentials:
admin1/password(admin),user1/password(user)
| Component | Technology | Version |
|---|---|---|
Frontend |
Angular |
21.x |
UI Framework |
Angular Material |
21.x |
CSS Framework |
Tailwind CSS |
4.x |
Backend |
Spring Boot WebFlux |
4.0.x |
Auth Server |
Keycloak |
26.x |
Reverse Proxy |
Traefik |
3.6 |
Static Server |
Go Fiber |
2.x |
Database |
PostgreSQL |
18.x |
ng new app-store \
--ai-config agents \
--ai-config copilot \
--package-manager pnpm \
--routing \
-p kp \
-g \
--style scss \
--test-runner vitest| Option | Description |
|---|---|
|
Enables AI agent configuration (AGENTS.md) |
|
Enables GitHub Copilot integration |
|
Uses pnpm for faster, disk-efficient package management |
|
Adds Angular Router module |
|
Sets component selector prefix to |
|
Skip git initialization (useful when adding to existing repo) |
|
Uses SCSS for styling |
|
Uses Vitest instead of Karma for unit testing |
ng add @angular/materialThis will prompt you to:
-
Choose a prebuilt theme or custom theme
-
Set up global Angular Material typography styles
-
Include Angular animations module
Tailwind CSS v4 uses a new PostCSS-based architecture with automatic content detection.
Create .postcssrc.json in the project root:
export default {
plugins: {
'@tailwindcss/postcss': {}
}
}In angular.json, under configurations.production.optimization:
"optimization": {
"scripts": true,
"styles": {
"minify": true,
"inlineCritical": false
},
"fonts": true
}|
Warning
|
If inlineCritical is true, Angular’s Beasties library may strip Tailwind utility classes that aren’t used in above-the-fold content.
|
| Feature | Tailwind v3 | Tailwind v4 |
|---|---|---|
Configuration |
|
Auto-detection (no config needed) |
PostCSS Plugin |
|
|
CSS Import |
|
|
Content Detection |
Manual |
Automatic |
Prettier ensures consistent code formatting across the team.
pnpm add -D prettier eslint-config-prettier eslint-plugin-prettier| Package | Purpose |
|---|---|
|
Core Prettier formatter |
|
Disables ESLint rules that conflict with Prettier |
|
Runs Prettier as an ESLint rule |
Create .prettierrc in the project root:
{
"singleQuote": true,
"trailingComma": "es5",
"tabWidth": 2,
"semi": true,
"printWidth": 120,
"bracketSpacing": true,
"arrowParens": "avoid",
"endOfLine": "lf"
}Create .prettierignore:
dist
coverage
node_modules
pnpm-lock.yaml
*.min.js
*.min.cssUpdate eslint.config.js:
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import angular from 'angular-eslint';
import prettier from 'eslint-config-prettier';
import prettierPlugin from 'eslint-plugin-prettier';
export default tseslint.config(
{
files: ['**/*.ts'],
extends: [
eslint.configs.recommended,
...tseslint.configs.recommended,
...tseslint.configs.stylistic,
...angular.configs.tsRecommended,
prettier,
],
plugins: {
prettier: prettierPlugin,
},
processor: angular.processInlineTemplates,
rules: {
'prettier/prettier': 'error',
'@angular-eslint/directive-selector': [
'error',
{ type: 'attribute', prefix: 'kp', style: 'camelCase' },
],
'@angular-eslint/component-selector': [
'error',
{ type: 'element', prefix: 'kp', style: 'kebab-case' },
],
},
},
{
files: ['**/*.html'],
extends: [
...angular.configs.templateRecommended,
...angular.configs.templateAccessibility,
],
}
);Add to package.json:
{
"scripts": {
"format": "prettier --write \"src/**/*.{ts,html,scss,css,json}\"",
"format:check": "prettier --check \"src/**/*.{ts,html,scss,css,json}\"",
"lint": "ng lint",
"lint:fix": "ng lint --fix"
}
}Create .vscode/settings.json:
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}Create .vscode/extensions.json for recommended extensions:
{
"recommendations": [
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"angular.ng-template",
"bradlc.vscode-tailwindcss"
]
}ng add @angular/pwaThis adds:
-
Service worker configuration (
ngsw-config.json) -
Web app manifest (
manifest.webmanifest) -
App icons in
public/icons/ -
Service worker registration in
app.config.ts
Add the following to /etc/hosts:
<docker-host-ip> keycloak-ng-sso-example.localReplace <docker-host-ip> with your Docker host IP address (e.g., 192.168.1.100).
# Start all services
docker compose up -d
# View logs
docker compose logs -f
# Rebuild a specific service
docker compose up -d --force-recreate --no-deps --build -V <service-name>Access the application at https://<host-ip>;
# Set environment variable
export HOSTNAME=keycloak-ng-sso-example.local
# Start app-store
cd app-store
pnpm install
pnpm serve-dev
# Or start employee-app (in another terminal)
cd employee-app
pnpm install
pnpm serve-devcd employee-backend
./gradlew bootRun \
-Dspring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:18080/auth/realms/universaldocker compose up -d keycloak postgresKeycloak will be available at http://localhost:18080/auth