Live: cms-project.sertaccan.com
π§ In Progress β Under active development.
- Media (file upload and listing for content)
- Settings
- User Details
- Dashboard Live Data
- i18n Integration
A content management system built with NestJS + Angular 21. Originally created to manage content for my portfolio site.
After the CMS is complete, additional modules such as ERP and CRM will be added. Note: This repo will only contain the CMS codebase. Other modules will be showcased via a live demo site. Development progress will be documented in this README.
Before adding new modules, the project architecture will be migrated to a Modular Monolith structure. Example:
src/auth/ β src/identity/auth/
src/contents/ β src/cms/
src/common/guards/ β src/_kernel/guards/
src/prisma/ β src/_infra/prisma/
- BullMQ will be integrated as the message queue service since Redis is already in place (fully compatible with NestJS)
| Layer | Technology |
|---|---|
| Backend | NestJS 11, TypeScript |
| Frontend | Angular 21, Angular Material |
| Database | PostgreSQL 16 (Prisma ORM) |
| Cache | Redis 7 (cache-manager + @keyv/redis) |
| Auth | Express-session + JWT (access + refresh token) |
| Deploy | Docker, Traefik v3, GitHub Actions |
- Global prefix:
/apiβ all endpoints start with/api/... - Prisma multi-file schema:
api/prisma/schema/directory, configured viaprisma.config.ts - Winston logger + daily rotate file
- Swagger UI:
/api/docs(non-production only)
- Session-based (
express-session), cookiehttpOnly: true - JWT access token (15min) + refresh token (7 days) pair
- Refresh token stored hashed in the database
- Endpoints protected with
JwtGuard
@nestjs/cache-manager+@keyv/redisadapter- TTL:
CACHE_TTLenv variable (default 30min) - Manual clear:
DELETE /api/settings/cache/clear
- Global rate limiting via
@nestjs/throttler - Protects auth endpoints against brute-force attacks
- File upload via Multer
uploads/named volume β persistent across container restarts
- Angular Material + CDK
- Signal-based state management (services)
- Lazy loading with route-based code splitting
- File generation via Angular CLI (
ng)
Rules for when to use signal vs Observable in services:
| Method | Type | Approach |
|---|---|---|
loadCategories() |
Shared list state | Signal β |
getCategoryDetails() |
One-time fetch (on dialog open) | Observable β |
createCategory() |
Mutation | Observable β |
editCategory() |
Mutation | Observable β |
deleteCategory() |
Mutation | Observable β |
Rule:
- Data read by multiple components, changing over time, persistent β Signal
- Triggered by a user action, one-time, transient result β Observable
Mutation methods (create, edit, delete) return Observables. The caller handles the result with subscribe({ next, error }), then triggers loadCategories() to update the actual state.
Angular uses build-time environments (environments/ directory). .env is not supported (browser runtime).
environment.tsβ production config, not in gitenvironment.development.tsβ dev config, not in git- Reference:
environment.ts.example
- Simple forms without complex validation β FormField signal model (Login page)
- Other form operations β FormsModule (Dialog components)
- Reason: The Material library used for Dialog, Input, etc. is not yet compatible with the new FormField API. Instead of writing extra validation methods, FormField (signal-based) is used for simple forms and FormsModule for the rest.
- i18n translations
- Generating commit messages
- Testing
- Refactoring (improving internal structure without changing external behavior)
- Insomnia
- DBeaver
- Occasionally Prisma Studio (primarily used early in the project)
- Docker Desktop
docker compose -f docker-compose.dev.yml up --buildServices:
localhost:4200β Angular (hot-reload)localhost:3000/apiβ NestJS (watch mode)localhost:5432β PostgreSQLlocalhost:6379β Redis
# 1. Generate diff after schema changes
docker exec cms_project_api npx prisma migrate diff \
--from-config-datasource \
--to-schema prisma/schema \
--script
# 2. Save the SQL to api/prisma/migrations/<timestamp>_<name>/migration.sql
# 3. Apply
docker exec cms_project_api npx prisma migrate deploy
# 4. Regenerate client (Docker + local)
docker exec cms_project_api npx prisma generate
cd api && npx prisma generate- Traefik v3 reverse proxy + Let's Encrypt SSL (automatic)
- Services exposed to Traefik via
traefik-netexternal network - Services communicate internally via
cms-internalbridge network
Push to main branch β automatic deploy:
- SSH into VPS
git pull origin maindocker compose --env-file api/.env up -d --builddocker exec cms_project_api npx prisma migrate deploydocker image prune -f
| Secret | Description |
|---|---|
VPS_HOST |
VPS IP address |
VPS_SSH_KEY |
Private SSH key |
# API
cd api
npm run start:dev # watch mode
npm run build # production build
npm run lint # eslint fix
npx prisma studio # DB GUI (requires DATABASE_URL=localhost)
# Client
cd client
npm start # ng serve
npm run build # production build