forge-server 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/hooks/worktree-create.sh +64 -0
- package/.claude/hooks/worktree-remove.sh +57 -0
- package/.claude/settings.local.json +29 -0
- package/.forge/knowledge/conventions.yaml +1 -0
- package/.forge/knowledge/decisions.yaml +1 -0
- package/.forge/knowledge/gotchas.yaml +1 -0
- package/.forge/knowledge/patterns.yaml +1 -0
- package/.forge/manifest.yaml +6 -0
- package/CLAUDE.md +144 -0
- package/bin/setup-forge.sh +132 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +553 -0
- package/dist/cli.js.map +1 -0
- package/dist/context/codebase.d.ts +57 -0
- package/dist/context/codebase.d.ts.map +1 -0
- package/dist/context/codebase.js +301 -0
- package/dist/context/codebase.js.map +1 -0
- package/dist/context/injector.d.ts +147 -0
- package/dist/context/injector.d.ts.map +1 -0
- package/dist/context/injector.js +533 -0
- package/dist/context/injector.js.map +1 -0
- package/dist/context/memory.d.ts +32 -0
- package/dist/context/memory.d.ts.map +1 -0
- package/dist/context/memory.js +140 -0
- package/dist/context/memory.js.map +1 -0
- package/dist/context/session-index.d.ts +54 -0
- package/dist/context/session-index.d.ts.map +1 -0
- package/dist/context/session-index.js +265 -0
- package/dist/context/session-index.js.map +1 -0
- package/dist/context/session.d.ts +42 -0
- package/dist/context/session.d.ts.map +1 -0
- package/dist/context/session.js +121 -0
- package/dist/context/session.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/ingestion/chunker.d.ts +19 -0
- package/dist/ingestion/chunker.d.ts.map +1 -0
- package/dist/ingestion/chunker.js +189 -0
- package/dist/ingestion/chunker.js.map +1 -0
- package/dist/ingestion/embedder.d.ts +45 -0
- package/dist/ingestion/embedder.d.ts.map +1 -0
- package/dist/ingestion/embedder.js +152 -0
- package/dist/ingestion/embedder.js.map +1 -0
- package/dist/ingestion/git-analyzer.d.ts +77 -0
- package/dist/ingestion/git-analyzer.d.ts.map +1 -0
- package/dist/ingestion/git-analyzer.js +437 -0
- package/dist/ingestion/git-analyzer.js.map +1 -0
- package/dist/ingestion/indexer.d.ts +79 -0
- package/dist/ingestion/indexer.d.ts.map +1 -0
- package/dist/ingestion/indexer.js +766 -0
- package/dist/ingestion/indexer.js.map +1 -0
- package/dist/ingestion/markdown-chunker.d.ts +19 -0
- package/dist/ingestion/markdown-chunker.d.ts.map +1 -0
- package/dist/ingestion/markdown-chunker.js +243 -0
- package/dist/ingestion/markdown-chunker.js.map +1 -0
- package/dist/ingestion/markdown-knowledge.d.ts +21 -0
- package/dist/ingestion/markdown-knowledge.d.ts.map +1 -0
- package/dist/ingestion/markdown-knowledge.js +129 -0
- package/dist/ingestion/markdown-knowledge.js.map +1 -0
- package/dist/ingestion/parser.d.ts +20 -0
- package/dist/ingestion/parser.d.ts.map +1 -0
- package/dist/ingestion/parser.js +429 -0
- package/dist/ingestion/parser.js.map +1 -0
- package/dist/ingestion/watcher.d.ts +28 -0
- package/dist/ingestion/watcher.d.ts.map +1 -0
- package/dist/ingestion/watcher.js +147 -0
- package/dist/ingestion/watcher.js.map +1 -0
- package/dist/knowledge/hydrator.d.ts +37 -0
- package/dist/knowledge/hydrator.d.ts.map +1 -0
- package/dist/knowledge/hydrator.js +220 -0
- package/dist/knowledge/hydrator.js.map +1 -0
- package/dist/knowledge/registry.d.ts +129 -0
- package/dist/knowledge/registry.d.ts.map +1 -0
- package/dist/knowledge/registry.js +361 -0
- package/dist/knowledge/registry.js.map +1 -0
- package/dist/knowledge/search.d.ts +114 -0
- package/dist/knowledge/search.d.ts.map +1 -0
- package/dist/knowledge/search.js +428 -0
- package/dist/knowledge/search.js.map +1 -0
- package/dist/knowledge/store.d.ts +76 -0
- package/dist/knowledge/store.d.ts.map +1 -0
- package/dist/knowledge/store.js +230 -0
- package/dist/knowledge/store.js.map +1 -0
- package/dist/learning/confidence.d.ts +30 -0
- package/dist/learning/confidence.d.ts.map +1 -0
- package/dist/learning/confidence.js +165 -0
- package/dist/learning/confidence.js.map +1 -0
- package/dist/learning/patterns.d.ts +52 -0
- package/dist/learning/patterns.d.ts.map +1 -0
- package/dist/learning/patterns.js +290 -0
- package/dist/learning/patterns.js.map +1 -0
- package/dist/learning/trajectory.d.ts +55 -0
- package/dist/learning/trajectory.d.ts.map +1 -0
- package/dist/learning/trajectory.js +200 -0
- package/dist/learning/trajectory.js.map +1 -0
- package/dist/memory/memory-compat.d.ts +100 -0
- package/dist/memory/memory-compat.d.ts.map +1 -0
- package/dist/memory/memory-compat.js +146 -0
- package/dist/memory/memory-compat.js.map +1 -0
- package/dist/memory/observation-store.d.ts +57 -0
- package/dist/memory/observation-store.d.ts.map +1 -0
- package/dist/memory/observation-store.js +154 -0
- package/dist/memory/observation-store.js.map +1 -0
- package/dist/memory/session-tracker.d.ts +81 -0
- package/dist/memory/session-tracker.d.ts.map +1 -0
- package/dist/memory/session-tracker.js +262 -0
- package/dist/memory/session-tracker.js.map +1 -0
- package/dist/pipeline/engine.d.ts +179 -0
- package/dist/pipeline/engine.d.ts.map +1 -0
- package/dist/pipeline/engine.js +691 -0
- package/dist/pipeline/engine.js.map +1 -0
- package/dist/pipeline/events.d.ts +54 -0
- package/dist/pipeline/events.d.ts.map +1 -0
- package/dist/pipeline/events.js +157 -0
- package/dist/pipeline/events.js.map +1 -0
- package/dist/pipeline/parallel.d.ts +83 -0
- package/dist/pipeline/parallel.d.ts.map +1 -0
- package/dist/pipeline/parallel.js +277 -0
- package/dist/pipeline/parallel.js.map +1 -0
- package/dist/pipeline/state-machine.d.ts +65 -0
- package/dist/pipeline/state-machine.d.ts.map +1 -0
- package/dist/pipeline/state-machine.js +176 -0
- package/dist/pipeline/state-machine.js.map +1 -0
- package/dist/query/graph-queries.d.ts +84 -0
- package/dist/query/graph-queries.d.ts.map +1 -0
- package/dist/query/graph-queries.js +216 -0
- package/dist/query/graph-queries.js.map +1 -0
- package/dist/query/hybrid-search.d.ts +34 -0
- package/dist/query/hybrid-search.d.ts.map +1 -0
- package/dist/query/hybrid-search.js +263 -0
- package/dist/query/hybrid-search.js.map +1 -0
- package/dist/query/intent-detector.d.ts +35 -0
- package/dist/query/intent-detector.d.ts.map +1 -0
- package/dist/query/intent-detector.js +115 -0
- package/dist/query/intent-detector.js.map +1 -0
- package/dist/query/ranking.d.ts +57 -0
- package/dist/query/ranking.d.ts.map +1 -0
- package/dist/query/ranking.js +109 -0
- package/dist/query/ranking.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +291 -0
- package/dist/server.js.map +1 -0
- package/dist/storage/falkordb-store.d.ts +73 -0
- package/dist/storage/falkordb-store.d.ts.map +1 -0
- package/dist/storage/falkordb-store.js +346 -0
- package/dist/storage/falkordb-store.js.map +1 -0
- package/dist/storage/file-cache.d.ts +32 -0
- package/dist/storage/file-cache.d.ts.map +1 -0
- package/dist/storage/file-cache.js +115 -0
- package/dist/storage/file-cache.js.map +1 -0
- package/dist/storage/interfaces.d.ts +151 -0
- package/dist/storage/interfaces.d.ts.map +1 -0
- package/dist/storage/interfaces.js +7 -0
- package/dist/storage/interfaces.js.map +1 -0
- package/dist/storage/qdrant-store.d.ts +110 -0
- package/dist/storage/qdrant-store.d.ts.map +1 -0
- package/dist/storage/qdrant-store.js +467 -0
- package/dist/storage/qdrant-store.js.map +1 -0
- package/dist/storage/schema.d.ts +4 -0
- package/dist/storage/schema.d.ts.map +1 -0
- package/dist/storage/schema.js +136 -0
- package/dist/storage/schema.js.map +1 -0
- package/dist/storage/sqlite.d.ts +35 -0
- package/dist/storage/sqlite.d.ts.map +1 -0
- package/dist/storage/sqlite.js +132 -0
- package/dist/storage/sqlite.js.map +1 -0
- package/dist/tools/collaboration-tools.d.ts +111 -0
- package/dist/tools/collaboration-tools.d.ts.map +1 -0
- package/dist/tools/collaboration-tools.js +174 -0
- package/dist/tools/collaboration-tools.js.map +1 -0
- package/dist/tools/context-tools.d.ts +293 -0
- package/dist/tools/context-tools.d.ts.map +1 -0
- package/dist/tools/context-tools.js +437 -0
- package/dist/tools/context-tools.js.map +1 -0
- package/dist/tools/graph-tools.d.ts +129 -0
- package/dist/tools/graph-tools.d.ts.map +1 -0
- package/dist/tools/graph-tools.js +237 -0
- package/dist/tools/graph-tools.js.map +1 -0
- package/dist/tools/ingestion-tools.d.ts +96 -0
- package/dist/tools/ingestion-tools.d.ts.map +1 -0
- package/dist/tools/ingestion-tools.js +90 -0
- package/dist/tools/ingestion-tools.js.map +1 -0
- package/dist/tools/learning-tools.d.ts +168 -0
- package/dist/tools/learning-tools.d.ts.map +1 -0
- package/dist/tools/learning-tools.js +158 -0
- package/dist/tools/learning-tools.js.map +1 -0
- package/dist/tools/memory-tools.d.ts +183 -0
- package/dist/tools/memory-tools.d.ts.map +1 -0
- package/dist/tools/memory-tools.js +197 -0
- package/dist/tools/memory-tools.js.map +1 -0
- package/dist/tools/phase-tools.d.ts +954 -0
- package/dist/tools/phase-tools.d.ts.map +1 -0
- package/dist/tools/phase-tools.js +1215 -0
- package/dist/tools/phase-tools.js.map +1 -0
- package/dist/tools/pipeline-tools.d.ts +140 -0
- package/dist/tools/pipeline-tools.d.ts.map +1 -0
- package/dist/tools/pipeline-tools.js +162 -0
- package/dist/tools/pipeline-tools.js.map +1 -0
- package/dist/tools/registration-tools.d.ts +220 -0
- package/dist/tools/registration-tools.d.ts.map +1 -0
- package/dist/tools/registration-tools.js +391 -0
- package/dist/tools/registration-tools.js.map +1 -0
- package/dist/util/circuit-breaker.d.ts +75 -0
- package/dist/util/circuit-breaker.d.ts.map +1 -0
- package/dist/util/circuit-breaker.js +159 -0
- package/dist/util/circuit-breaker.js.map +1 -0
- package/dist/util/config.d.ts +23 -0
- package/dist/util/config.d.ts.map +1 -0
- package/dist/util/config.js +164 -0
- package/dist/util/config.js.map +1 -0
- package/dist/util/logger.d.ts +13 -0
- package/dist/util/logger.d.ts.map +1 -0
- package/dist/util/logger.js +45 -0
- package/dist/util/logger.js.map +1 -0
- package/dist/util/token-counter.d.ts +24 -0
- package/dist/util/token-counter.d.ts.map +1 -0
- package/dist/util/token-counter.js +48 -0
- package/dist/util/token-counter.js.map +1 -0
- package/dist/util/types.d.ts +525 -0
- package/dist/util/types.d.ts.map +1 -0
- package/dist/util/types.js +5 -0
- package/dist/util/types.js.map +1 -0
- package/docker-compose.yml +20 -0
- package/docs/plans/2026-02-27-swarm-coordination/architecture.md +203 -0
- package/docs/plans/2026-02-27-swarm-coordination/vision.md +57 -0
- package/docs/plans/completed/2026-02-26-forge-plugin-bundling/architecture.md +1 -0
- package/docs/plans/completed/2026-02-26-forge-plugin-bundling/vision.md +300 -0
- package/docs/plans/completed/2026-02-27-forge-swarm-learning/architecture.md +480 -0
- package/docs/plans/completed/2026-02-27-forge-swarm-learning/verification-checklist.md +462 -0
- package/docs/plans/completed/2026-02-27-git-history-atlassian/git-jira-plan.md +181 -0
- package/package.json +39 -0
- package/plugin/.claude-plugin/plugin.json +8 -0
- package/plugin/.mcp.json +15 -0
- package/plugin/README.md +134 -0
- package/plugin/agents/architect.md +367 -0
- package/plugin/agents/backend-specialist.md +263 -0
- package/plugin/agents/brainstormer.md +122 -0
- package/plugin/agents/data-specialist.md +266 -0
- package/plugin/agents/designer.md +408 -0
- package/plugin/agents/frontend-specialist.md +241 -0
- package/plugin/agents/inspector.md +406 -0
- package/plugin/agents/knowledge-keeper.md +443 -0
- package/plugin/agents/platform-engineer.md +326 -0
- package/plugin/agents/product-manager.md +268 -0
- package/plugin/agents/product-owner.md +438 -0
- package/plugin/agents/pulse-checker.md +73 -0
- package/plugin/agents/qa-strategist.md +500 -0
- package/plugin/agents/self-improver.md +310 -0
- package/plugin/agents/strategist.md +360 -0
- package/plugin/agents/supervisor.md +380 -0
- package/plugin/commands/brainstorm.md +25 -0
- package/plugin/commands/forge.md +88 -0
- package/plugin/docs/atlassian-integration.md +110 -0
- package/plugin/docs/workflow.md +126 -0
- package/plugin/skills/agent-development/.skillfish.json +10 -0
- package/plugin/skills/agent-development/SKILL.md +415 -0
- package/plugin/skills/agent-development/examples/agent-creation-prompt.md +238 -0
- package/plugin/skills/agent-development/examples/complete-agent-examples.md +427 -0
- package/plugin/skills/agent-development/references/agent-creation-system-prompt.md +207 -0
- package/plugin/skills/agent-development/references/system-prompt-design.md +411 -0
- package/plugin/skills/agent-development/references/triggering-examples.md +491 -0
- package/plugin/skills/agent-development/scripts/validate-agent.sh +217 -0
- package/plugin/skills/agent-handoff/SKILL.md +335 -0
- package/plugin/skills/anti-stub/SKILL.md +317 -0
- package/plugin/skills/brainstorm/SKILL.md +31 -0
- package/plugin/skills/debugging/SKILL.md +276 -0
- package/plugin/skills/fix/SKILL.md +62 -0
- package/plugin/skills/frontend-design/.skillfish.json +10 -0
- package/plugin/skills/frontend-design/SKILL.md +42 -0
- package/plugin/skills/gotchas/SKILL.md +61 -0
- package/plugin/skills/graph-orchestrator/SKILL.md +38 -0
- package/plugin/skills/history/SKILL.md +58 -0
- package/plugin/skills/impact/SKILL.md +59 -0
- package/plugin/skills/implementation-execution/SKILL.md +291 -0
- package/plugin/skills/index-repo/SKILL.md +55 -0
- package/plugin/skills/interviewing/SKILL.md +225 -0
- package/plugin/skills/knowledge-curation/SKILL.md +393 -0
- package/plugin/skills/learn/SKILL.md +69 -0
- package/plugin/skills/mcp-integration/.skillfish.json +10 -0
- package/plugin/skills/mcp-integration/SKILL.md +554 -0
- package/plugin/skills/mcp-integration/examples/http-server.json +20 -0
- package/plugin/skills/mcp-integration/examples/sse-server.json +19 -0
- package/plugin/skills/mcp-integration/examples/stdio-server.json +26 -0
- package/plugin/skills/mcp-integration/references/authentication.md +549 -0
- package/plugin/skills/mcp-integration/references/server-types.md +536 -0
- package/plugin/skills/mcp-integration/references/tool-usage.md +538 -0
- package/plugin/skills/nestjs/.skillfish.json +10 -0
- package/plugin/skills/nestjs/SKILL.md +669 -0
- package/plugin/skills/nestjs/drizzle-reference.md +1894 -0
- package/plugin/skills/nestjs/reference.md +1447 -0
- package/plugin/skills/nestjs/workflow-optimization.md +229 -0
- package/plugin/skills/parallel-dispatch/SKILL.md +308 -0
- package/plugin/skills/project-discovery/SKILL.md +304 -0
- package/plugin/skills/search/SKILL.md +56 -0
- package/plugin/skills/security-audit/SKILL.md +362 -0
- package/plugin/skills/skill-development/.skillfish.json +10 -0
- package/plugin/skills/skill-development/SKILL.md +637 -0
- package/plugin/skills/skill-development/references/skill-creator-original.md +209 -0
- package/plugin/skills/tdd/SKILL.md +273 -0
- package/plugin/skills/terminal-presentation/SKILL.md +395 -0
- package/plugin/skills/test-strategy/SKILL.md +365 -0
- package/plugin/skills/verification-protocol/SKILL.md +256 -0
- package/plugin/skills/visual-explainer/CHANGELOG.md +97 -0
- package/plugin/skills/visual-explainer/LICENSE +21 -0
- package/plugin/skills/visual-explainer/README.md +137 -0
- package/plugin/skills/visual-explainer/SKILL.md +352 -0
- package/plugin/skills/visual-explainer/banner.png +0 -0
- package/plugin/skills/visual-explainer/package.json +11 -0
- package/plugin/skills/visual-explainer/prompts/diff-review.md +68 -0
- package/plugin/skills/visual-explainer/prompts/fact-check.md +63 -0
- package/plugin/skills/visual-explainer/prompts/generate-slides.md +18 -0
- package/plugin/skills/visual-explainer/prompts/generate-web-diagram.md +10 -0
- package/plugin/skills/visual-explainer/prompts/plan-review.md +86 -0
- package/plugin/skills/visual-explainer/prompts/project-recap.md +61 -0
- package/plugin/skills/visual-explainer/references/css-patterns.md +1188 -0
- package/plugin/skills/visual-explainer/references/libraries.md +470 -0
- package/plugin/skills/visual-explainer/references/responsive-nav.md +212 -0
- package/plugin/skills/visual-explainer/references/slide-patterns.md +1403 -0
- package/plugin/skills/visual-explainer/templates/architecture.html +596 -0
- package/plugin/skills/visual-explainer/templates/data-table.html +540 -0
- package/plugin/skills/visual-explainer/templates/mermaid-flowchart.html +435 -0
- package/plugin/skills/visual-explainer/templates/slide-deck.html +913 -0
- package/src/cli.ts +655 -0
- package/src/context/.gitkeep +0 -0
- package/src/context/codebase.ts +393 -0
- package/src/context/injector.ts +797 -0
- package/src/context/memory.ts +187 -0
- package/src/context/session-index.ts +327 -0
- package/src/context/session.ts +152 -0
- package/src/index.ts +47 -0
- package/src/ingestion/.gitkeep +0 -0
- package/src/ingestion/chunker.ts +277 -0
- package/src/ingestion/embedder.ts +167 -0
- package/src/ingestion/git-analyzer.ts +545 -0
- package/src/ingestion/indexer.ts +984 -0
- package/src/ingestion/markdown-chunker.ts +337 -0
- package/src/ingestion/markdown-knowledge.ts +175 -0
- package/src/ingestion/parser.ts +475 -0
- package/src/ingestion/watcher.ts +182 -0
- package/src/knowledge/.gitkeep +0 -0
- package/src/knowledge/hydrator.ts +246 -0
- package/src/knowledge/registry.ts +463 -0
- package/src/knowledge/search.ts +565 -0
- package/src/knowledge/store.ts +262 -0
- package/src/learning/.gitkeep +0 -0
- package/src/learning/confidence.ts +193 -0
- package/src/learning/patterns.ts +360 -0
- package/src/learning/trajectory.ts +268 -0
- package/src/memory/.gitkeep +0 -0
- package/src/memory/memory-compat.ts +233 -0
- package/src/memory/observation-store.ts +224 -0
- package/src/memory/session-tracker.ts +332 -0
- package/src/pipeline/.gitkeep +0 -0
- package/src/pipeline/engine.ts +1139 -0
- package/src/pipeline/events.ts +253 -0
- package/src/pipeline/parallel.ts +394 -0
- package/src/pipeline/state-machine.ts +199 -0
- package/src/query/.gitkeep +0 -0
- package/src/query/graph-queries.ts +262 -0
- package/src/query/hybrid-search.ts +337 -0
- package/src/query/intent-detector.ts +131 -0
- package/src/query/ranking.ts +161 -0
- package/src/server.ts +352 -0
- package/src/storage/.gitkeep +0 -0
- package/src/storage/falkordb-store.ts +388 -0
- package/src/storage/file-cache.ts +141 -0
- package/src/storage/interfaces.ts +201 -0
- package/src/storage/qdrant-store.ts +557 -0
- package/src/storage/schema.ts +139 -0
- package/src/storage/sqlite.ts +168 -0
- package/src/tools/.gitkeep +0 -0
- package/src/tools/collaboration-tools.ts +208 -0
- package/src/tools/context-tools.ts +493 -0
- package/src/tools/graph-tools.ts +295 -0
- package/src/tools/ingestion-tools.ts +122 -0
- package/src/tools/learning-tools.ts +181 -0
- package/src/tools/memory-tools.ts +234 -0
- package/src/tools/phase-tools.ts +1452 -0
- package/src/tools/pipeline-tools.ts +188 -0
- package/src/tools/registration-tools.ts +450 -0
- package/src/util/.gitkeep +0 -0
- package/src/util/circuit-breaker.ts +193 -0
- package/src/util/config.ts +177 -0
- package/src/util/logger.ts +53 -0
- package/src/util/token-counter.ts +52 -0
- package/src/util/types.ts +710 -0
- package/tests/context/.gitkeep +0 -0
- package/tests/integration/.gitkeep +0 -0
- package/tests/knowledge/.gitkeep +0 -0
- package/tests/learning/.gitkeep +0 -0
- package/tests/pipeline/.gitkeep +0 -0
- package/tests/tools/.gitkeep +0 -0
- package/tsconfig.json +21 -0
- package/vitest.config.ts +10 -0
- package/vscode-extension/.vscodeignore +7 -0
- package/vscode-extension/README.md +43 -0
- package/vscode-extension/out/edge-collector.js +274 -0
- package/vscode-extension/out/edge-collector.js.map +1 -0
- package/vscode-extension/out/extension.js +264 -0
- package/vscode-extension/out/extension.js.map +1 -0
- package/vscode-extension/out/forge-client.js +318 -0
- package/vscode-extension/out/forge-client.js.map +1 -0
- package/vscode-extension/package-lock.json +59 -0
- package/vscode-extension/package.json +71 -0
- package/vscode-extension/src/edge-collector.ts +320 -0
- package/vscode-extension/src/extension.ts +269 -0
- package/vscode-extension/src/forge-client.ts +364 -0
- package/vscode-extension/tsconfig.json +19 -0
|
@@ -0,0 +1,1447 @@
|
|
|
1
|
+
# NestJS Framework Documentation
|
|
2
|
+
|
|
3
|
+
NestJS is a progressive Node.js framework for building efficient, reliable and scalable server-side applications. Built with TypeScript and fully supporting both TypeScript and JavaScript, it combines elements of Object Oriented Programming (OOP), Functional Programming (FP), and Functional Reactive Programming (FRP). Under the hood, NestJS uses robust HTTP server frameworks like Express (default) or Fastify, providing a level of abstraction while exposing their APIs directly to developers.
|
|
4
|
+
|
|
5
|
+
The framework provides an out-of-the-box application architecture inspired by Angular, enabling developers to create highly testable, scalable, loosely coupled, and maintainable applications. NestJS leverages dependency injection, decorators, modules, guards, interceptors, and pipes to organize code effectively. It supports multiple transport layers for microservices, integrates seamlessly with databases through TypeORM and Sequelize, and provides first-class support for GraphQL and OpenAPI documentation.
|
|
6
|
+
|
|
7
|
+
## Core Building Blocks
|
|
8
|
+
|
|
9
|
+
### Creating a Basic Application
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { NestFactory } from '@nestjs/core';
|
|
13
|
+
import { AppModule } from './app.module';
|
|
14
|
+
|
|
15
|
+
async function bootstrap() {
|
|
16
|
+
const app = await NestFactory.create(AppModule);
|
|
17
|
+
await app.listen(process.env.PORT ?? 3000);
|
|
18
|
+
}
|
|
19
|
+
bootstrap();
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Controllers - HTTP Request Handling
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { Controller, Get, Post, Put, Delete, Body, Param, Query } from '@nestjs/common';
|
|
26
|
+
import { CreateCatDto, UpdateCatDto } from './dto';
|
|
27
|
+
import { CatsService } from './cats.service';
|
|
28
|
+
|
|
29
|
+
@Controller('cats')
|
|
30
|
+
export class CatsController {
|
|
31
|
+
constructor(private catsService: CatsService) {}
|
|
32
|
+
|
|
33
|
+
@Post()
|
|
34
|
+
async create(@Body() createCatDto: CreateCatDto) {
|
|
35
|
+
this.catsService.create(createCatDto);
|
|
36
|
+
return { message: 'Cat created successfully' };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@Get()
|
|
40
|
+
async findAll(@Query('age') age?: number, @Query('breed') breed?: string) {
|
|
41
|
+
return this.catsService.findAll({ age, breed });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@Get(':id')
|
|
45
|
+
async findOne(@Param('id') id: string) {
|
|
46
|
+
return this.catsService.findOne(id);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@Put(':id')
|
|
50
|
+
async update(@Param('id') id: string, @Body() updateCatDto: UpdateCatDto) {
|
|
51
|
+
return this.catsService.update(id, updateCatDto);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@Delete(':id')
|
|
55
|
+
async remove(@Param('id') id: string) {
|
|
56
|
+
await this.catsService.remove(id);
|
|
57
|
+
return { message: 'Cat removed successfully' };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Providers - Business Logic and Services
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { Injectable } from '@nestjs/common';
|
|
66
|
+
import { Cat } from './interfaces/cat.interface';
|
|
67
|
+
|
|
68
|
+
@Injectable()
|
|
69
|
+
export class CatsService {
|
|
70
|
+
private readonly cats: Cat[] = [];
|
|
71
|
+
|
|
72
|
+
create(cat: Cat): void {
|
|
73
|
+
this.cats.push(cat);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
findAll(filter?: { age?: number; breed?: string }): Cat[] {
|
|
77
|
+
if (!filter) return this.cats;
|
|
78
|
+
|
|
79
|
+
return this.cats.filter(cat => {
|
|
80
|
+
if (filter.age && cat.age !== filter.age) return false;
|
|
81
|
+
if (filter.breed && cat.breed !== filter.breed) return false;
|
|
82
|
+
return true;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
findOne(id: string): Cat | undefined {
|
|
87
|
+
return this.cats.find(cat => cat.id === id);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
update(id: string, updateData: Partial): Cat {
|
|
91
|
+
const catIndex = this.cats.findIndex(cat => cat.id === id);
|
|
92
|
+
if (catIndex === -1) throw new Error('Cat not found');
|
|
93
|
+
|
|
94
|
+
this.cats[catIndex] = { ...this.cats[catIndex], ...updateData };
|
|
95
|
+
return this.cats[catIndex];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
remove(id: string): void {
|
|
99
|
+
const catIndex = this.cats.findIndex(cat => cat.id === id);
|
|
100
|
+
if (catIndex === -1) throw new Error('Cat not found');
|
|
101
|
+
this.cats.splice(catIndex, 1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Modules - Application Organization
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { Module } from '@nestjs/common';
|
|
110
|
+
import { CatsController } from './cats.controller';
|
|
111
|
+
import { CatsService } from './cats.service';
|
|
112
|
+
|
|
113
|
+
@Module({
|
|
114
|
+
controllers: [CatsController],
|
|
115
|
+
providers: [CatsService],
|
|
116
|
+
exports: [CatsService], // Make service available to other modules
|
|
117
|
+
})
|
|
118
|
+
export class CatsModule {}
|
|
119
|
+
|
|
120
|
+
// Root module
|
|
121
|
+
import { Module } from '@nestjs/common';
|
|
122
|
+
import { CatsModule } from './cats/cats.module';
|
|
123
|
+
import { DogsModule } from './dogs/dogs.module';
|
|
124
|
+
|
|
125
|
+
@Module({
|
|
126
|
+
imports: [CatsModule, DogsModule],
|
|
127
|
+
})
|
|
128
|
+
export class AppModule {}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Middleware
|
|
132
|
+
|
|
133
|
+
### Class-Based Middleware
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import { Injectable, NestMiddleware } from '@nestjs/common';
|
|
137
|
+
import { Request, Response, NextFunction } from 'express';
|
|
138
|
+
|
|
139
|
+
@Injectable()
|
|
140
|
+
export class LoggerMiddleware implements NestMiddleware {
|
|
141
|
+
use(req: Request, res: Response, next: NextFunction) {
|
|
142
|
+
console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`);
|
|
143
|
+
next();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Apply in module
|
|
148
|
+
import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
|
|
149
|
+
|
|
150
|
+
@Module({
|
|
151
|
+
imports: [CatsModule],
|
|
152
|
+
})
|
|
153
|
+
export class AppModule implements NestModule {
|
|
154
|
+
configure(consumer: MiddlewareConsumer) {
|
|
155
|
+
consumer
|
|
156
|
+
.apply(LoggerMiddleware)
|
|
157
|
+
.exclude(
|
|
158
|
+
{ path: 'cats', method: RequestMethod.GET },
|
|
159
|
+
'cats/{*splat}',
|
|
160
|
+
)
|
|
161
|
+
.forRoutes(CatsController);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Functional Middleware
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
import { Request, Response, NextFunction } from 'express';
|
|
170
|
+
|
|
171
|
+
export function logger(req: Request, res: Response, next: NextFunction) {
|
|
172
|
+
console.log(`Request...`);
|
|
173
|
+
next();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Apply in module
|
|
177
|
+
consumer.apply(logger).forRoutes(CatsController);
|
|
178
|
+
|
|
179
|
+
// Global middleware
|
|
180
|
+
const app = await NestFactory.create(AppModule);
|
|
181
|
+
app.use(logger);
|
|
182
|
+
await app.listen(3000);
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Exception Filters
|
|
186
|
+
|
|
187
|
+
### Built-in Exception Handling
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
import { Controller, Get, Post, Body, HttpException, HttpStatus } from '@nestjs/common';
|
|
191
|
+
|
|
192
|
+
@Controller('cats')
|
|
193
|
+
export class CatsController {
|
|
194
|
+
@Get()
|
|
195
|
+
async findAll() {
|
|
196
|
+
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
@Post()
|
|
200
|
+
async create(@Body() createCatDto: CreateCatDto) {
|
|
201
|
+
throw new HttpException({
|
|
202
|
+
status: HttpStatus.FORBIDDEN,
|
|
203
|
+
error: 'This is a custom message',
|
|
204
|
+
}, HttpStatus.FORBIDDEN);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Custom Exception Filter
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
|
|
213
|
+
import { Request, Response } from 'express';
|
|
214
|
+
|
|
215
|
+
@Catch(HttpException)
|
|
216
|
+
export class HttpExceptionFilter implements ExceptionFilter {
|
|
217
|
+
catch(exception: HttpException, host: ArgumentsHost) {
|
|
218
|
+
const ctx = host.switchToHttp();
|
|
219
|
+
const response = ctx.getResponse();
|
|
220
|
+
const request = ctx.getRequest();
|
|
221
|
+
const status = exception.getStatus();
|
|
222
|
+
|
|
223
|
+
response.status(status).json({
|
|
224
|
+
statusCode: status,
|
|
225
|
+
timestamp: new Date().toISOString(),
|
|
226
|
+
path: request.url,
|
|
227
|
+
message: exception.message,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Apply to controller or method
|
|
233
|
+
import { UseFilters } from '@nestjs/common';
|
|
234
|
+
|
|
235
|
+
@Post()
|
|
236
|
+
@UseFilters(new HttpExceptionFilter())
|
|
237
|
+
async create(@Body() createCatDto: CreateCatDto) {
|
|
238
|
+
throw new ForbiddenException();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Global exception filter
|
|
242
|
+
const app = await NestFactory.create(AppModule);
|
|
243
|
+
app.useGlobalFilters(new HttpExceptionFilter());
|
|
244
|
+
|
|
245
|
+
// Global with dependency injection
|
|
246
|
+
@Module({
|
|
247
|
+
providers: [
|
|
248
|
+
{
|
|
249
|
+
provide: APP_FILTER,
|
|
250
|
+
useClass: HttpExceptionFilter,
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
})
|
|
254
|
+
export class AppModule {}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Catch-All Exception Filter
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
import { Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common';
|
|
261
|
+
import { HttpAdapterHost } from '@nestjs/core';
|
|
262
|
+
|
|
263
|
+
@Catch()
|
|
264
|
+
export class AllExceptionsFilter implements ExceptionFilter {
|
|
265
|
+
constructor(private readonly httpAdapterHost: HttpAdapterHost) {}
|
|
266
|
+
|
|
267
|
+
catch(exception: unknown, host: ArgumentsHost): void {
|
|
268
|
+
const { httpAdapter } = this.httpAdapterHost;
|
|
269
|
+
const ctx = host.switchToHttp();
|
|
270
|
+
|
|
271
|
+
const httpStatus = exception instanceof HttpException
|
|
272
|
+
? exception.getStatus()
|
|
273
|
+
: HttpStatus.INTERNAL_SERVER_ERROR;
|
|
274
|
+
|
|
275
|
+
const responseBody = {
|
|
276
|
+
statusCode: httpStatus,
|
|
277
|
+
timestamp: new Date().toISOString(),
|
|
278
|
+
path: httpAdapter.getRequestUrl(ctx.getRequest()),
|
|
279
|
+
message: exception instanceof Error ? exception.message : 'Internal server error',
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
httpAdapter.reply(ctx.getResponse(), responseBody, httpStatus);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## Pipes
|
|
288
|
+
|
|
289
|
+
### Built-in Transformation Pipes
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
import { Controller, Get, Param, Query, ParseIntPipe, ParseUUIDPipe } from '@nestjs/common';
|
|
293
|
+
|
|
294
|
+
@Controller('cats')
|
|
295
|
+
export class CatsController {
|
|
296
|
+
@Get(':id')
|
|
297
|
+
async findOne(@Param('id', ParseIntPipe) id: number) {
|
|
298
|
+
return this.catsService.findOne(id);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
@Get('user/:uuid')
|
|
302
|
+
async findByUser(@Param('uuid', ParseUUIDPipe) uuid: string) {
|
|
303
|
+
return this.catsService.findByUser(uuid);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
@Get()
|
|
307
|
+
async findAll(@Query('page', ParseIntPipe) page: number) {
|
|
308
|
+
return this.catsService.findAll(page);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Validation Pipe with class-validator
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
import { IsString, IsInt, Min, Max } from 'class-validator';
|
|
317
|
+
|
|
318
|
+
export class CreateCatDto {
|
|
319
|
+
@IsString()
|
|
320
|
+
name: string;
|
|
321
|
+
|
|
322
|
+
@IsInt()
|
|
323
|
+
@Min(0)
|
|
324
|
+
@Max(30)
|
|
325
|
+
age: number;
|
|
326
|
+
|
|
327
|
+
@IsString()
|
|
328
|
+
breed: string;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Apply validation pipe
|
|
332
|
+
import { Controller, Post, Body, UsePipes, ValidationPipe } from '@nestjs/common';
|
|
333
|
+
|
|
334
|
+
@Controller('cats')
|
|
335
|
+
export class CatsController {
|
|
336
|
+
@Post()
|
|
337
|
+
@UsePipes(new ValidationPipe())
|
|
338
|
+
async create(@Body() createCatDto: CreateCatDto) {
|
|
339
|
+
return this.catsService.create(createCatDto);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Global validation pipe
|
|
344
|
+
async function bootstrap() {
|
|
345
|
+
const app = await NestFactory.create(AppModule);
|
|
346
|
+
app.useGlobalPipes(new ValidationPipe({
|
|
347
|
+
whitelist: true,
|
|
348
|
+
forbidNonWhitelisted: true,
|
|
349
|
+
transform: true,
|
|
350
|
+
}));
|
|
351
|
+
await app.listen(3000);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// With dependency injection
|
|
355
|
+
@Module({
|
|
356
|
+
providers: [
|
|
357
|
+
{
|
|
358
|
+
provide: APP_PIPE,
|
|
359
|
+
useClass: ValidationPipe,
|
|
360
|
+
},
|
|
361
|
+
],
|
|
362
|
+
})
|
|
363
|
+
export class AppModule {}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Custom Transformation Pipe
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
|
|
370
|
+
|
|
371
|
+
@Injectable()
|
|
372
|
+
export class ParseIntPipe implements PipeTransform {
|
|
373
|
+
transform(value: string, metadata: ArgumentMetadata): number {
|
|
374
|
+
const val = parseInt(value, 10);
|
|
375
|
+
if (isNaN(val)) {
|
|
376
|
+
throw new BadRequestException('Validation failed (numeric string is expected)');
|
|
377
|
+
}
|
|
378
|
+
return val;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
@Get(':id')
|
|
383
|
+
async findOne(@Param('id', new ParseIntPipe()) id: number) {
|
|
384
|
+
return this.catsService.findOne(id);
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### Schema Validation with Zod
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
import { PipeTransform, ArgumentMetadata, BadRequestException } from '@nestjs/common';
|
|
392
|
+
import { ZodSchema } from 'zod';
|
|
393
|
+
|
|
394
|
+
export class ZodValidationPipe implements PipeTransform {
|
|
395
|
+
constructor(private schema: ZodSchema) {}
|
|
396
|
+
|
|
397
|
+
transform(value: unknown, metadata: ArgumentMetadata) {
|
|
398
|
+
try {
|
|
399
|
+
const parsedValue = this.schema.parse(value);
|
|
400
|
+
return parsedValue;
|
|
401
|
+
} catch (error) {
|
|
402
|
+
throw new BadRequestException('Validation failed');
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Define schema
|
|
408
|
+
import { z } from 'zod';
|
|
409
|
+
|
|
410
|
+
export const createCatSchema = z.object({
|
|
411
|
+
name: z.string(),
|
|
412
|
+
age: z.number().min(0).max(30),
|
|
413
|
+
breed: z.string(),
|
|
414
|
+
}).required();
|
|
415
|
+
|
|
416
|
+
export type CreateCatDto = z.infer;
|
|
417
|
+
|
|
418
|
+
// Use in controller
|
|
419
|
+
@Post()
|
|
420
|
+
@UsePipes(new ZodValidationPipe(createCatSchema))
|
|
421
|
+
async create(@Body() createCatDto: CreateCatDto) {
|
|
422
|
+
return this.catsService.create(createCatDto);
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
## Guards
|
|
427
|
+
|
|
428
|
+
### Authentication Guard
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
|
|
432
|
+
import { Observable } from 'rxjs';
|
|
433
|
+
|
|
434
|
+
@Injectable()
|
|
435
|
+
export class AuthGuard implements CanActivate {
|
|
436
|
+
canActivate(context: ExecutionContext): boolean | Promise | Observable {
|
|
437
|
+
const request = context.switchToHttp().getRequest();
|
|
438
|
+
const token = request.headers.authorization;
|
|
439
|
+
|
|
440
|
+
if (!token) {
|
|
441
|
+
throw new UnauthorizedException('No token provided');
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
try {
|
|
445
|
+
// Validate token logic here
|
|
446
|
+
const user = this.validateToken(token);
|
|
447
|
+
request.user = user;
|
|
448
|
+
return true;
|
|
449
|
+
} catch (error) {
|
|
450
|
+
throw new UnauthorizedException('Invalid token');
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
private validateToken(token: string) {
|
|
455
|
+
// Token validation logic
|
|
456
|
+
return { id: '123', username: 'john' };
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Apply guard
|
|
461
|
+
import { Controller, Get, UseGuards } from '@nestjs/common';
|
|
462
|
+
|
|
463
|
+
@Controller('cats')
|
|
464
|
+
@UseGuards(AuthGuard)
|
|
465
|
+
export class CatsController {
|
|
466
|
+
@Get()
|
|
467
|
+
findAll() {
|
|
468
|
+
return this.catsService.findAll();
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### Role-Based Authorization Guard
|
|
474
|
+
|
|
475
|
+
```typescript
|
|
476
|
+
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
|
477
|
+
import { Reflector } from '@nestjs/core';
|
|
478
|
+
|
|
479
|
+
// Custom decorator
|
|
480
|
+
export const Roles = Reflector.createDecorator();
|
|
481
|
+
|
|
482
|
+
@Injectable()
|
|
483
|
+
export class RolesGuard implements CanActivate {
|
|
484
|
+
constructor(private reflector: Reflector) {}
|
|
485
|
+
|
|
486
|
+
canActivate(context: ExecutionContext): boolean {
|
|
487
|
+
const roles = this.reflector.get(Roles, context.getHandler());
|
|
488
|
+
if (!roles) {
|
|
489
|
+
return true;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const request = context.switchToHttp().getRequest();
|
|
493
|
+
const user = request.user;
|
|
494
|
+
|
|
495
|
+
return this.matchRoles(roles, user.roles);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
private matchRoles(requiredRoles: string[], userRoles: string[]): boolean {
|
|
499
|
+
return requiredRoles.some(role => userRoles.includes(role));
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Use in controller
|
|
504
|
+
@Post()
|
|
505
|
+
@Roles(['admin'])
|
|
506
|
+
@UseGuards(RolesGuard)
|
|
507
|
+
async create(@Body() createCatDto: CreateCatDto) {
|
|
508
|
+
return this.catsService.create(createCatDto);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Global guard with DI
|
|
512
|
+
@Module({
|
|
513
|
+
providers: [
|
|
514
|
+
{
|
|
515
|
+
provide: APP_GUARD,
|
|
516
|
+
useClass: RolesGuard,
|
|
517
|
+
},
|
|
518
|
+
],
|
|
519
|
+
})
|
|
520
|
+
export class AppModule {}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
## Interceptors
|
|
524
|
+
|
|
525
|
+
### Logging Interceptor
|
|
526
|
+
|
|
527
|
+
```typescript
|
|
528
|
+
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
|
|
529
|
+
import { Observable } from 'rxjs';
|
|
530
|
+
import { tap } from 'rxjs/operators';
|
|
531
|
+
|
|
532
|
+
@Injectable()
|
|
533
|
+
export class LoggingInterceptor implements NestInterceptor {
|
|
534
|
+
intercept(context: ExecutionContext, next: CallHandler): Observable {
|
|
535
|
+
const request = context.switchToHttp().getRequest();
|
|
536
|
+
const { method, url } = request;
|
|
537
|
+
const now = Date.now();
|
|
538
|
+
|
|
539
|
+
console.log(`[${method}] ${url} - Start`);
|
|
540
|
+
|
|
541
|
+
return next.handle().pipe(
|
|
542
|
+
tap(() => {
|
|
543
|
+
console.log(`[${method}] ${url} - Completed in ${Date.now() - now}ms`);
|
|
544
|
+
}),
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
@UseInterceptors(LoggingInterceptor)
|
|
550
|
+
@Controller('cats')
|
|
551
|
+
export class CatsController {}
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
### Response Transformation Interceptor
|
|
555
|
+
|
|
556
|
+
```typescript
|
|
557
|
+
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
|
|
558
|
+
import { Observable } from 'rxjs';
|
|
559
|
+
import { map } from 'rxjs/operators';
|
|
560
|
+
|
|
561
|
+
export interface Response {
|
|
562
|
+
data: T;
|
|
563
|
+
timestamp: string;
|
|
564
|
+
path: string;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
@Injectable()
|
|
568
|
+
export class TransformInterceptor implements NestInterceptor> {
|
|
569
|
+
intercept(context: ExecutionContext, next: CallHandler): Observable> {
|
|
570
|
+
const request = context.switchToHttp().getRequest();
|
|
571
|
+
|
|
572
|
+
return next.handle().pipe(
|
|
573
|
+
map(data => ({
|
|
574
|
+
data,
|
|
575
|
+
timestamp: new Date().toISOString(),
|
|
576
|
+
path: request.url,
|
|
577
|
+
})),
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// Result: GET /cats returns { data: [...], timestamp: "...", path: "/cats" }
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
### Cache Interceptor
|
|
586
|
+
|
|
587
|
+
```typescript
|
|
588
|
+
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
|
|
589
|
+
import { Observable, of } from 'rxjs';
|
|
590
|
+
|
|
591
|
+
@Injectable()
|
|
592
|
+
export class CacheInterceptor implements NestInterceptor {
|
|
593
|
+
private cache = new Map();
|
|
594
|
+
|
|
595
|
+
intercept(context: ExecutionContext, next: CallHandler): Observable {
|
|
596
|
+
const request = context.switchToHttp().getRequest();
|
|
597
|
+
const cacheKey = `${request.method}:${request.url}`;
|
|
598
|
+
|
|
599
|
+
if (this.cache.has(cacheKey)) {
|
|
600
|
+
console.log('Returning cached response');
|
|
601
|
+
return of(this.cache.get(cacheKey));
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
return next.handle().pipe(
|
|
605
|
+
tap(response => {
|
|
606
|
+
this.cache.set(cacheKey, response);
|
|
607
|
+
}),
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
### Timeout Interceptor
|
|
614
|
+
|
|
615
|
+
```typescript
|
|
616
|
+
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, RequestTimeoutException } from '@nestjs/common';
|
|
617
|
+
import { Observable, throwError, TimeoutError } from 'rxjs';
|
|
618
|
+
import { catchError, timeout } from 'rxjs/operators';
|
|
619
|
+
|
|
620
|
+
@Injectable()
|
|
621
|
+
export class TimeoutInterceptor implements NestInterceptor {
|
|
622
|
+
intercept(context: ExecutionContext, next: CallHandler): Observable {
|
|
623
|
+
return next.handle().pipe(
|
|
624
|
+
timeout(5000),
|
|
625
|
+
catchError(err => {
|
|
626
|
+
if (err instanceof TimeoutError) {
|
|
627
|
+
return throwError(() => new RequestTimeoutException('Request timeout'));
|
|
628
|
+
}
|
|
629
|
+
return throwError(() => err);
|
|
630
|
+
}),
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
## Dependency Injection
|
|
637
|
+
|
|
638
|
+
### Constructor Injection
|
|
639
|
+
|
|
640
|
+
```typescript
|
|
641
|
+
@Injectable()
|
|
642
|
+
export class CatsService {
|
|
643
|
+
constructor(
|
|
644
|
+
private readonly dogsService: DogsService,
|
|
645
|
+
private readonly configService: ConfigService,
|
|
646
|
+
) {}
|
|
647
|
+
|
|
648
|
+
async findAll() {
|
|
649
|
+
const config = this.configService.get('database');
|
|
650
|
+
return this.catsRepository.find();
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
### Custom Provider with Token
|
|
656
|
+
|
|
657
|
+
```typescript
|
|
658
|
+
const CONNECTION = 'DATABASE_CONNECTION';
|
|
659
|
+
|
|
660
|
+
@Module({
|
|
661
|
+
providers: [
|
|
662
|
+
{
|
|
663
|
+
provide: CONNECTION,
|
|
664
|
+
useValue: {
|
|
665
|
+
host: 'localhost',
|
|
666
|
+
port: 5432,
|
|
667
|
+
database: 'test',
|
|
668
|
+
},
|
|
669
|
+
},
|
|
670
|
+
],
|
|
671
|
+
})
|
|
672
|
+
export class DatabaseModule {}
|
|
673
|
+
|
|
674
|
+
// Inject custom provider
|
|
675
|
+
@Injectable()
|
|
676
|
+
export class CatsRepository {
|
|
677
|
+
constructor(@Inject(CONNECTION) private connection: any) {}
|
|
678
|
+
}
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
### Factory Provider
|
|
682
|
+
|
|
683
|
+
```typescript
|
|
684
|
+
@Module({
|
|
685
|
+
providers: [
|
|
686
|
+
{
|
|
687
|
+
provide: 'DATABASE_CONNECTION',
|
|
688
|
+
useFactory: async (configService: ConfigService) => {
|
|
689
|
+
const config = configService.get('database');
|
|
690
|
+
const connection = await createConnection(config);
|
|
691
|
+
return connection;
|
|
692
|
+
},
|
|
693
|
+
inject: [ConfigService],
|
|
694
|
+
},
|
|
695
|
+
],
|
|
696
|
+
})
|
|
697
|
+
export class DatabaseModule {}
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
### Async Provider
|
|
701
|
+
|
|
702
|
+
```typescript
|
|
703
|
+
@Module({
|
|
704
|
+
providers: [
|
|
705
|
+
{
|
|
706
|
+
provide: 'ASYNC_CONNECTION',
|
|
707
|
+
useFactory: async () => {
|
|
708
|
+
const connection = await createAsyncConnection();
|
|
709
|
+
return connection;
|
|
710
|
+
},
|
|
711
|
+
},
|
|
712
|
+
],
|
|
713
|
+
})
|
|
714
|
+
export class AppModule {}
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
### Class Provider with Conditional Logic
|
|
718
|
+
|
|
719
|
+
```typescript
|
|
720
|
+
const configServiceProvider = {
|
|
721
|
+
provide: ConfigService,
|
|
722
|
+
useClass: process.env.NODE_ENV === 'development'
|
|
723
|
+
? DevelopmentConfigService
|
|
724
|
+
: ProductionConfigService,
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
@Module({
|
|
728
|
+
providers: [configServiceProvider],
|
|
729
|
+
})
|
|
730
|
+
export class AppModule {}
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
## Database Integration
|
|
734
|
+
|
|
735
|
+
### TypeORM Setup
|
|
736
|
+
|
|
737
|
+
```typescript
|
|
738
|
+
import { Module } from '@nestjs/common';
|
|
739
|
+
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
740
|
+
|
|
741
|
+
@Module({
|
|
742
|
+
imports: [
|
|
743
|
+
TypeOrmModule.forRoot({
|
|
744
|
+
type: 'postgres',
|
|
745
|
+
host: 'localhost',
|
|
746
|
+
port: 5432,
|
|
747
|
+
username: 'postgres',
|
|
748
|
+
password: 'password',
|
|
749
|
+
database: 'nest_db',
|
|
750
|
+
entities: [__dirname + '/**/*.entity{.ts,.js}'],
|
|
751
|
+
synchronize: true, // Don't use in production
|
|
752
|
+
logging: true,
|
|
753
|
+
}),
|
|
754
|
+
],
|
|
755
|
+
})
|
|
756
|
+
export class AppModule {}
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
### Entity Definition
|
|
760
|
+
|
|
761
|
+
```typescript
|
|
762
|
+
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, OneToMany } from 'typeorm';
|
|
763
|
+
import { Photo } from '../photos/photo.entity';
|
|
764
|
+
|
|
765
|
+
@Entity('users')
|
|
766
|
+
export class User {
|
|
767
|
+
@PrimaryGeneratedColumn('uuid')
|
|
768
|
+
id: string;
|
|
769
|
+
|
|
770
|
+
@Column({ unique: true })
|
|
771
|
+
email: string;
|
|
772
|
+
|
|
773
|
+
@Column()
|
|
774
|
+
firstName: string;
|
|
775
|
+
|
|
776
|
+
@Column()
|
|
777
|
+
lastName: string;
|
|
778
|
+
|
|
779
|
+
@Column({ default: true })
|
|
780
|
+
isActive: boolean;
|
|
781
|
+
|
|
782
|
+
@CreateDateColumn()
|
|
783
|
+
createdAt: Date;
|
|
784
|
+
|
|
785
|
+
@UpdateDateColumn()
|
|
786
|
+
updatedAt: Date;
|
|
787
|
+
|
|
788
|
+
@OneToMany(() => Photo, photo => photo.user)
|
|
789
|
+
photos: Photo[];
|
|
790
|
+
}
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
### Repository Pattern
|
|
794
|
+
|
|
795
|
+
```typescript
|
|
796
|
+
import { Injectable } from '@nestjs/common';
|
|
797
|
+
import { InjectRepository } from '@nestjs/typeorm';
|
|
798
|
+
import { Repository } from 'typeorm';
|
|
799
|
+
import { User } from './user.entity';
|
|
800
|
+
|
|
801
|
+
@Injectable()
|
|
802
|
+
export class UsersService {
|
|
803
|
+
constructor(
|
|
804
|
+
@InjectRepository(User)
|
|
805
|
+
private usersRepository: Repository,
|
|
806
|
+
) {}
|
|
807
|
+
|
|
808
|
+
async findAll(): Promise {
|
|
809
|
+
return this.usersRepository.find({ relations: ['photos'] });
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
async findOne(id: string): Promise {
|
|
813
|
+
const user = await this.usersRepository.findOne({
|
|
814
|
+
where: { id },
|
|
815
|
+
relations: ['photos'],
|
|
816
|
+
});
|
|
817
|
+
if (!user) throw new NotFoundException('User not found');
|
|
818
|
+
return user;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
async create(userData: CreateUserDto): Promise {
|
|
822
|
+
const user = this.usersRepository.create(userData);
|
|
823
|
+
return this.usersRepository.save(user);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
async update(id: string, updateData: UpdateUserDto): Promise {
|
|
827
|
+
await this.usersRepository.update(id, updateData);
|
|
828
|
+
return this.findOne(id);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
async remove(id: string): Promise {
|
|
832
|
+
const result = await this.usersRepository.delete(id);
|
|
833
|
+
if (result.affected === 0) {
|
|
834
|
+
throw new NotFoundException('User not found');
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
@Module({
|
|
840
|
+
imports: [TypeOrmModule.forFeature([User])],
|
|
841
|
+
providers: [UsersService],
|
|
842
|
+
controllers: [UsersController],
|
|
843
|
+
exports: [UsersService],
|
|
844
|
+
})
|
|
845
|
+
export class UsersModule {}
|
|
846
|
+
```
|
|
847
|
+
|
|
848
|
+
### Database Transactions
|
|
849
|
+
|
|
850
|
+
```typescript
|
|
851
|
+
import { DataSource } from 'typeorm';
|
|
852
|
+
|
|
853
|
+
@Injectable()
|
|
854
|
+
export class UsersService {
|
|
855
|
+
constructor(
|
|
856
|
+
@InjectRepository(User) private usersRepository: Repository,
|
|
857
|
+
private dataSource: DataSource,
|
|
858
|
+
) {}
|
|
859
|
+
|
|
860
|
+
async createUserWithPhotos(userData: CreateUserDto, photos: CreatePhotoDto[]) {
|
|
861
|
+
const queryRunner = this.dataSource.createQueryRunner();
|
|
862
|
+
await queryRunner.connect();
|
|
863
|
+
await queryRunner.startTransaction();
|
|
864
|
+
|
|
865
|
+
try {
|
|
866
|
+
const user = await queryRunner.manager.save(User, userData);
|
|
867
|
+
|
|
868
|
+
for (const photoData of photos) {
|
|
869
|
+
await queryRunner.manager.save(Photo, { ...photoData, user });
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
await queryRunner.commitTransaction();
|
|
873
|
+
return user;
|
|
874
|
+
} catch (err) {
|
|
875
|
+
await queryRunner.rollbackTransaction();
|
|
876
|
+
throw err;
|
|
877
|
+
} finally {
|
|
878
|
+
await queryRunner.release();
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
```
|
|
883
|
+
|
|
884
|
+
### Async Configuration
|
|
885
|
+
|
|
886
|
+
```typescript
|
|
887
|
+
TypeOrmModule.forRootAsync({
|
|
888
|
+
imports: [ConfigModule],
|
|
889
|
+
useFactory: (configService: ConfigService) => ({
|
|
890
|
+
type: 'postgres',
|
|
891
|
+
host: configService.get('DB_HOST'),
|
|
892
|
+
port: configService.get('DB_PORT'),
|
|
893
|
+
username: configService.get('DB_USERNAME'),
|
|
894
|
+
password: configService.get('DB_PASSWORD'),
|
|
895
|
+
database: configService.get('DB_NAME'),
|
|
896
|
+
entities: [__dirname + '/**/*.entity{.ts,.js}'],
|
|
897
|
+
synchronize: configService.get('DB_SYNC') === 'true',
|
|
898
|
+
}),
|
|
899
|
+
inject: [ConfigService],
|
|
900
|
+
})
|
|
901
|
+
```
|
|
902
|
+
|
|
903
|
+
## Testing
|
|
904
|
+
|
|
905
|
+
### Unit Testing with Mocks
|
|
906
|
+
|
|
907
|
+
```typescript
|
|
908
|
+
import { Test, TestingModule } from '@nestjs/testing';
|
|
909
|
+
import { CatsController } from './cats.controller';
|
|
910
|
+
import { CatsService } from './cats.service';
|
|
911
|
+
|
|
912
|
+
describe('CatsController', () => {
|
|
913
|
+
let catsController: CatsController;
|
|
914
|
+
let catsService: CatsService;
|
|
915
|
+
|
|
916
|
+
beforeEach(async () => {
|
|
917
|
+
const module: TestingModule = await Test.createTestingModule({
|
|
918
|
+
controllers: [CatsController],
|
|
919
|
+
providers: [CatsService],
|
|
920
|
+
}).compile();
|
|
921
|
+
|
|
922
|
+
catsService = module.get(CatsService);
|
|
923
|
+
catsController = module.get(CatsController);
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
describe('findAll', () => {
|
|
927
|
+
it('should return an array of cats', async () => {
|
|
928
|
+
const result = [{ name: 'Test Cat', age: 2, breed: 'Persian' }];
|
|
929
|
+
jest.spyOn(catsService, 'findAll').mockImplementation(() => Promise.resolve(result));
|
|
930
|
+
|
|
931
|
+
expect(await catsController.findAll()).toBe(result);
|
|
932
|
+
});
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
describe('create', () => {
|
|
936
|
+
it('should create a cat', async () => {
|
|
937
|
+
const catDto = { name: 'New Cat', age: 1, breed: 'Siamese' };
|
|
938
|
+
jest.spyOn(catsService, 'create').mockImplementation(() => Promise.resolve(catDto));
|
|
939
|
+
|
|
940
|
+
expect(await catsController.create(catDto)).toEqual(catDto);
|
|
941
|
+
});
|
|
942
|
+
});
|
|
943
|
+
});
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
### Testing with Provider Override
|
|
947
|
+
|
|
948
|
+
```typescript
|
|
949
|
+
describe('CatsController', () => {
|
|
950
|
+
let controller: CatsController;
|
|
951
|
+
|
|
952
|
+
const mockCatsService = {
|
|
953
|
+
findAll: jest.fn(() => [{ name: 'Test', age: 2, breed: 'Persian' }]),
|
|
954
|
+
findOne: jest.fn((id) => ({ id, name: 'Test', age: 2, breed: 'Persian' })),
|
|
955
|
+
create: jest.fn((dto) => ({ id: '123', ...dto })),
|
|
956
|
+
};
|
|
957
|
+
|
|
958
|
+
beforeEach(async () => {
|
|
959
|
+
const module: TestingModule = await Test.createTestingModule({
|
|
960
|
+
controllers: [CatsController],
|
|
961
|
+
providers: [CatsService],
|
|
962
|
+
})
|
|
963
|
+
.overrideProvider(CatsService)
|
|
964
|
+
.useValue(mockCatsService)
|
|
965
|
+
.compile();
|
|
966
|
+
|
|
967
|
+
controller = module.get(CatsController);
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
it('should be defined', () => {
|
|
971
|
+
expect(controller).toBeDefined();
|
|
972
|
+
});
|
|
973
|
+
});
|
|
974
|
+
```
|
|
975
|
+
|
|
976
|
+
### End-to-End Testing
|
|
977
|
+
|
|
978
|
+
```typescript
|
|
979
|
+
import { Test, TestingModule } from '@nestjs/testing';
|
|
980
|
+
import { INestApplication } from '@nestjs/common';
|
|
981
|
+
import * as request from 'supertest';
|
|
982
|
+
import { AppModule } from './../src/app.module';
|
|
983
|
+
|
|
984
|
+
describe('CatsController (e2e)', () => {
|
|
985
|
+
let app: INestApplication;
|
|
986
|
+
|
|
987
|
+
beforeAll(async () => {
|
|
988
|
+
const moduleFixture: TestingModule = await Test.createTestingModule({
|
|
989
|
+
imports: [AppModule],
|
|
990
|
+
}).compile();
|
|
991
|
+
|
|
992
|
+
app = moduleFixture.createNestApplication();
|
|
993
|
+
await app.init();
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
afterAll(async () => {
|
|
997
|
+
await app.close();
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
it('/cats (GET)', () => {
|
|
1001
|
+
return request(app.getHttpServer())
|
|
1002
|
+
.get('/cats')
|
|
1003
|
+
.expect(200)
|
|
1004
|
+
.expect((res) => {
|
|
1005
|
+
expect(Array.isArray(res.body)).toBe(true);
|
|
1006
|
+
});
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
it('/cats (POST)', () => {
|
|
1010
|
+
return request(app.getHttpServer())
|
|
1011
|
+
.post('/cats')
|
|
1012
|
+
.send({ name: 'Test Cat', age: 2, breed: 'Persian' })
|
|
1013
|
+
.expect(201)
|
|
1014
|
+
.expect((res) => {
|
|
1015
|
+
expect(res.body).toHaveProperty('id');
|
|
1016
|
+
expect(res.body.name).toBe('Test Cat');
|
|
1017
|
+
});
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
it('/cats/:id (GET)', () => {
|
|
1021
|
+
return request(app.getHttpServer())
|
|
1022
|
+
.get('/cats/123')
|
|
1023
|
+
.expect(200)
|
|
1024
|
+
.expect((res) => {
|
|
1025
|
+
expect(res.body).toHaveProperty('id', '123');
|
|
1026
|
+
});
|
|
1027
|
+
});
|
|
1028
|
+
});
|
|
1029
|
+
```
|
|
1030
|
+
|
|
1031
|
+
### Testing with Repository Mocks
|
|
1032
|
+
|
|
1033
|
+
```typescript
|
|
1034
|
+
describe('UsersService', () => {
|
|
1035
|
+
let service: UsersService;
|
|
1036
|
+
let repository: Repository;
|
|
1037
|
+
|
|
1038
|
+
const mockRepository = {
|
|
1039
|
+
find: jest.fn(),
|
|
1040
|
+
findOne: jest.fn(),
|
|
1041
|
+
create: jest.fn(),
|
|
1042
|
+
save: jest.fn(),
|
|
1043
|
+
update: jest.fn(),
|
|
1044
|
+
delete: jest.fn(),
|
|
1045
|
+
};
|
|
1046
|
+
|
|
1047
|
+
beforeEach(async () => {
|
|
1048
|
+
const module: TestingModule = await Test.createTestingModule({
|
|
1049
|
+
providers: [
|
|
1050
|
+
UsersService,
|
|
1051
|
+
{
|
|
1052
|
+
provide: getRepositoryToken(User),
|
|
1053
|
+
useValue: mockRepository,
|
|
1054
|
+
},
|
|
1055
|
+
],
|
|
1056
|
+
}).compile();
|
|
1057
|
+
|
|
1058
|
+
service = module.get(UsersService);
|
|
1059
|
+
repository = module.get>(getRepositoryToken(User));
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1062
|
+
it('should find all users', async () => {
|
|
1063
|
+
const users = [{ id: '1', email: 'test@example.com' }];
|
|
1064
|
+
mockRepository.find.mockResolvedValue(users);
|
|
1065
|
+
|
|
1066
|
+
expect(await service.findAll()).toEqual(users);
|
|
1067
|
+
expect(repository.find).toHaveBeenCalled();
|
|
1068
|
+
});
|
|
1069
|
+
});
|
|
1070
|
+
```
|
|
1071
|
+
|
|
1072
|
+
## Microservices
|
|
1073
|
+
|
|
1074
|
+
### TCP Microservice Setup
|
|
1075
|
+
|
|
1076
|
+
```typescript
|
|
1077
|
+
import { NestFactory } from '@nestjs/core';
|
|
1078
|
+
import { Transport, MicroserviceOptions } from '@nestjs/microservices';
|
|
1079
|
+
import { AppModule } from './app.module';
|
|
1080
|
+
|
|
1081
|
+
async function bootstrap() {
|
|
1082
|
+
const app = await NestFactory.createMicroservice(
|
|
1083
|
+
AppModule,
|
|
1084
|
+
{
|
|
1085
|
+
transport: Transport.TCP,
|
|
1086
|
+
options: {
|
|
1087
|
+
host: '127.0.0.1',
|
|
1088
|
+
port: 8877,
|
|
1089
|
+
},
|
|
1090
|
+
},
|
|
1091
|
+
);
|
|
1092
|
+
await app.listen();
|
|
1093
|
+
}
|
|
1094
|
+
bootstrap();
|
|
1095
|
+
```
|
|
1096
|
+
|
|
1097
|
+
### Message Pattern Handler
|
|
1098
|
+
|
|
1099
|
+
```typescript
|
|
1100
|
+
import { Controller } from '@nestjs/common';
|
|
1101
|
+
import { MessagePattern, Payload, Ctx, NatsContext } from '@nestjs/microservices';
|
|
1102
|
+
|
|
1103
|
+
@Controller()
|
|
1104
|
+
export class MathController {
|
|
1105
|
+
@MessagePattern({ cmd: 'sum' })
|
|
1106
|
+
accumulate(@Payload() data: number[]): number {
|
|
1107
|
+
return (data || []).reduce((a, b) => a + b, 0);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
@MessagePattern({ cmd: 'multiply' })
|
|
1111
|
+
multiply(@Payload() data: { a: number; b: number }): number {
|
|
1112
|
+
return data.a * data.b;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
```
|
|
1116
|
+
|
|
1117
|
+
### Event Pattern Handler
|
|
1118
|
+
|
|
1119
|
+
```typescript
|
|
1120
|
+
@Controller()
|
|
1121
|
+
export class NotificationsController {
|
|
1122
|
+
@EventPattern('user_created')
|
|
1123
|
+
async handleUserCreated(@Payload() data: CreateUserEvent) {
|
|
1124
|
+
console.log('New user created:', data);
|
|
1125
|
+
// Send welcome email
|
|
1126
|
+
await this.emailService.sendWelcome(data.email);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
@EventPattern('order_placed')
|
|
1130
|
+
async handleOrderPlaced(@Payload() data: OrderPlacedEvent) {
|
|
1131
|
+
console.log('Order placed:', data);
|
|
1132
|
+
// Process order
|
|
1133
|
+
await this.orderService.process(data);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
```
|
|
1137
|
+
|
|
1138
|
+
### Microservice Client
|
|
1139
|
+
|
|
1140
|
+
```typescript
|
|
1141
|
+
import { Module } from '@nestjs/common';
|
|
1142
|
+
import { ClientsModule, Transport } from '@nestjs/microservices';
|
|
1143
|
+
|
|
1144
|
+
@Module({
|
|
1145
|
+
imports: [
|
|
1146
|
+
ClientsModule.register([
|
|
1147
|
+
{
|
|
1148
|
+
name: 'MATH_SERVICE',
|
|
1149
|
+
transport: Transport.TCP,
|
|
1150
|
+
options: {
|
|
1151
|
+
host: '127.0.0.1',
|
|
1152
|
+
port: 8877,
|
|
1153
|
+
},
|
|
1154
|
+
},
|
|
1155
|
+
]),
|
|
1156
|
+
],
|
|
1157
|
+
controllers: [AppController],
|
|
1158
|
+
})
|
|
1159
|
+
export class AppModule {}
|
|
1160
|
+
|
|
1161
|
+
// Use in controller
|
|
1162
|
+
import { Controller, Get, Inject } from '@nestjs/common';
|
|
1163
|
+
import { ClientProxy } from '@nestjs/microservices';
|
|
1164
|
+
import { Observable } from 'rxjs';
|
|
1165
|
+
|
|
1166
|
+
@Controller()
|
|
1167
|
+
export class AppController {
|
|
1168
|
+
constructor(@Inject('MATH_SERVICE') private client: ClientProxy) {}
|
|
1169
|
+
|
|
1170
|
+
@Get('sum')
|
|
1171
|
+
accumulate(): Observable {
|
|
1172
|
+
const pattern = { cmd: 'sum' };
|
|
1173
|
+
const payload = [1, 2, 3, 4, 5];
|
|
1174
|
+
return this.client.send(pattern, payload);
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
@Get('notify')
|
|
1178
|
+
async notify() {
|
|
1179
|
+
this.client.emit('user_created', { id: '123', email: 'user@example.com' });
|
|
1180
|
+
return { message: 'Notification sent' };
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
```
|
|
1184
|
+
|
|
1185
|
+
### Redis Transport
|
|
1186
|
+
|
|
1187
|
+
```typescript
|
|
1188
|
+
// Microservice
|
|
1189
|
+
const app = await NestFactory.createMicroservice(
|
|
1190
|
+
AppModule,
|
|
1191
|
+
{
|
|
1192
|
+
transport: Transport.REDIS,
|
|
1193
|
+
options: {
|
|
1194
|
+
host: 'localhost',
|
|
1195
|
+
port: 6379,
|
|
1196
|
+
},
|
|
1197
|
+
},
|
|
1198
|
+
);
|
|
1199
|
+
|
|
1200
|
+
// Client
|
|
1201
|
+
ClientsModule.register([
|
|
1202
|
+
{
|
|
1203
|
+
name: 'REDIS_SERVICE',
|
|
1204
|
+
transport: Transport.REDIS,
|
|
1205
|
+
options: {
|
|
1206
|
+
host: 'localhost',
|
|
1207
|
+
port: 6379,
|
|
1208
|
+
},
|
|
1209
|
+
},
|
|
1210
|
+
])
|
|
1211
|
+
```
|
|
1212
|
+
|
|
1213
|
+
## GraphQL
|
|
1214
|
+
|
|
1215
|
+
### Apollo GraphQL Setup
|
|
1216
|
+
|
|
1217
|
+
```typescript
|
|
1218
|
+
import { Module } from '@nestjs/common';
|
|
1219
|
+
import { GraphQLModule } from '@nestjs/graphql';
|
|
1220
|
+
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
|
|
1221
|
+
import { join } from 'path';
|
|
1222
|
+
|
|
1223
|
+
@Module({
|
|
1224
|
+
imports: [
|
|
1225
|
+
GraphQLModule.forRoot({
|
|
1226
|
+
driver: ApolloDriver,
|
|
1227
|
+
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
|
|
1228
|
+
sortSchema: true,
|
|
1229
|
+
playground: true,
|
|
1230
|
+
}),
|
|
1231
|
+
],
|
|
1232
|
+
})
|
|
1233
|
+
export class AppModule {}
|
|
1234
|
+
```
|
|
1235
|
+
|
|
1236
|
+
### GraphQL Resolvers (Code First)
|
|
1237
|
+
|
|
1238
|
+
```typescript
|
|
1239
|
+
import { Resolver, Query, Mutation, Args, ID } from '@nestjs/graphql';
|
|
1240
|
+
import { ObjectType, Field, Int } from '@nestjs/graphql';
|
|
1241
|
+
|
|
1242
|
+
@ObjectType()
|
|
1243
|
+
export class Cat {
|
|
1244
|
+
@Field(() => ID)
|
|
1245
|
+
id: string;
|
|
1246
|
+
|
|
1247
|
+
@Field()
|
|
1248
|
+
name: string;
|
|
1249
|
+
|
|
1250
|
+
@Field(() => Int)
|
|
1251
|
+
age: number;
|
|
1252
|
+
|
|
1253
|
+
@Field()
|
|
1254
|
+
breed: string;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
@Resolver(() => Cat)
|
|
1258
|
+
export class CatsResolver {
|
|
1259
|
+
constructor(private catsService: CatsService) {}
|
|
1260
|
+
|
|
1261
|
+
@Query(() => [Cat], { name: 'cats' })
|
|
1262
|
+
async findAll() {
|
|
1263
|
+
return this.catsService.findAll();
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
@Query(() => Cat, { name: 'cat' })
|
|
1267
|
+
async findOne(@Args('id', { type: () => ID }) id: string) {
|
|
1268
|
+
return this.catsService.findOne(id);
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
@Mutation(() => Cat)
|
|
1272
|
+
async createCat(
|
|
1273
|
+
@Args('name') name: string,
|
|
1274
|
+
@Args('age', { type: () => Int }) age: number,
|
|
1275
|
+
@Args('breed') breed: string,
|
|
1276
|
+
) {
|
|
1277
|
+
return this.catsService.create({ name, age, breed });
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
@Mutation(() => Boolean)
|
|
1281
|
+
async removeCat(@Args('id', { type: () => ID }) id: string) {
|
|
1282
|
+
await this.catsService.remove(id);
|
|
1283
|
+
return true;
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
```
|
|
1287
|
+
|
|
1288
|
+
### GraphQL Input Types
|
|
1289
|
+
|
|
1290
|
+
```typescript
|
|
1291
|
+
import { InputType, Field, Int } from '@nestjs/graphql';
|
|
1292
|
+
|
|
1293
|
+
@InputType()
|
|
1294
|
+
export class CreateCatInput {
|
|
1295
|
+
@Field()
|
|
1296
|
+
name: string;
|
|
1297
|
+
|
|
1298
|
+
@Field(() => Int)
|
|
1299
|
+
age: number;
|
|
1300
|
+
|
|
1301
|
+
@Field()
|
|
1302
|
+
breed: string;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
@Mutation(() => Cat)
|
|
1306
|
+
async createCat(@Args('input') input: CreateCatInput) {
|
|
1307
|
+
return this.catsService.create(input);
|
|
1308
|
+
}
|
|
1309
|
+
```
|
|
1310
|
+
|
|
1311
|
+
## OpenAPI/Swagger Documentation
|
|
1312
|
+
|
|
1313
|
+
### Basic Swagger Setup
|
|
1314
|
+
|
|
1315
|
+
```typescript
|
|
1316
|
+
import { NestFactory } from '@nestjs/core';
|
|
1317
|
+
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
|
1318
|
+
import { AppModule } from './app.module';
|
|
1319
|
+
|
|
1320
|
+
async function bootstrap() {
|
|
1321
|
+
const app = await NestFactory.create(AppModule);
|
|
1322
|
+
|
|
1323
|
+
const config = new DocumentBuilder()
|
|
1324
|
+
.setTitle('Cats API')
|
|
1325
|
+
.setDescription('The cats API documentation')
|
|
1326
|
+
.setVersion('1.0')
|
|
1327
|
+
.addTag('cats')
|
|
1328
|
+
.addBearerAuth()
|
|
1329
|
+
.build();
|
|
1330
|
+
|
|
1331
|
+
const documentFactory = () => SwaggerModule.createDocument(app, config);
|
|
1332
|
+
SwaggerModule.setup('api', app, documentFactory);
|
|
1333
|
+
|
|
1334
|
+
await app.listen(3000);
|
|
1335
|
+
}
|
|
1336
|
+
bootstrap();
|
|
1337
|
+
// Access at http://localhost:3000/api
|
|
1338
|
+
```
|
|
1339
|
+
|
|
1340
|
+
### API Decorators
|
|
1341
|
+
|
|
1342
|
+
```typescript
|
|
1343
|
+
import { ApiTags, ApiOperation, ApiResponse, ApiProperty, ApiBearerAuth } from '@nestjs/swagger';
|
|
1344
|
+
|
|
1345
|
+
export class CreateCatDto {
|
|
1346
|
+
@ApiProperty({ example: 'Fluffy', description: 'The name of the cat' })
|
|
1347
|
+
name: string;
|
|
1348
|
+
|
|
1349
|
+
@ApiProperty({ example: 3, description: 'The age of the cat' })
|
|
1350
|
+
age: number;
|
|
1351
|
+
|
|
1352
|
+
@ApiProperty({ example: 'Persian', description: 'The breed of the cat' })
|
|
1353
|
+
breed: string;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
@ApiTags('cats')
|
|
1357
|
+
@Controller('cats')
|
|
1358
|
+
export class CatsController {
|
|
1359
|
+
@Post()
|
|
1360
|
+
@ApiOperation({ summary: 'Create a new cat' })
|
|
1361
|
+
@ApiResponse({ status: 201, description: 'The cat has been successfully created.', type: Cat })
|
|
1362
|
+
@ApiResponse({ status: 400, description: 'Bad Request.' })
|
|
1363
|
+
@ApiBearerAuth()
|
|
1364
|
+
async create(@Body() createCatDto: CreateCatDto) {
|
|
1365
|
+
return this.catsService.create(createCatDto);
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
@Get()
|
|
1369
|
+
@ApiOperation({ summary: 'Get all cats' })
|
|
1370
|
+
@ApiResponse({ status: 200, description: 'Return all cats.', type: [Cat] })
|
|
1371
|
+
async findAll() {
|
|
1372
|
+
return this.catsService.findAll();
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
```
|
|
1376
|
+
|
|
1377
|
+
## Configuration
|
|
1378
|
+
|
|
1379
|
+
### Environment Configuration
|
|
1380
|
+
|
|
1381
|
+
```typescript
|
|
1382
|
+
import { Module } from '@nestjs/common';
|
|
1383
|
+
import { ConfigModule, ConfigService } from '@nestjs/config';
|
|
1384
|
+
|
|
1385
|
+
@Module({
|
|
1386
|
+
imports: [
|
|
1387
|
+
ConfigModule.forRoot({
|
|
1388
|
+
isGlobal: true,
|
|
1389
|
+
envFilePath: ['.env.local', '.env'],
|
|
1390
|
+
ignoreEnvFile: process.env.NODE_ENV === 'production',
|
|
1391
|
+
}),
|
|
1392
|
+
],
|
|
1393
|
+
})
|
|
1394
|
+
export class AppModule {}
|
|
1395
|
+
|
|
1396
|
+
// Use in service
|
|
1397
|
+
@Injectable()
|
|
1398
|
+
export class AppService {
|
|
1399
|
+
constructor(private configService: ConfigService) {}
|
|
1400
|
+
|
|
1401
|
+
getDatabaseConfig() {
|
|
1402
|
+
return {
|
|
1403
|
+
host: this.configService.get('DB_HOST'),
|
|
1404
|
+
port: this.configService.get('DB_PORT'),
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
```
|
|
1409
|
+
|
|
1410
|
+
### Custom Configuration Files
|
|
1411
|
+
|
|
1412
|
+
```typescript
|
|
1413
|
+
export default () => ({
|
|
1414
|
+
port: parseInt(process.env.PORT, 10) || 3000,
|
|
1415
|
+
database: {
|
|
1416
|
+
host: process.env.DATABASE_HOST || 'localhost',
|
|
1417
|
+
port: parseInt(process.env.DATABASE_PORT, 10) || 5432,
|
|
1418
|
+
username: process.env.DATABASE_USERNAME,
|
|
1419
|
+
password: process.env.DATABASE_PASSWORD,
|
|
1420
|
+
},
|
|
1421
|
+
jwt: {
|
|
1422
|
+
secret: process.env.JWT_SECRET,
|
|
1423
|
+
expiresIn: '7d',
|
|
1424
|
+
},
|
|
1425
|
+
});
|
|
1426
|
+
|
|
1427
|
+
// Import in module
|
|
1428
|
+
import configuration from './config/configuration';
|
|
1429
|
+
|
|
1430
|
+
@Module({
|
|
1431
|
+
imports: [
|
|
1432
|
+
ConfigModule.forRoot({
|
|
1433
|
+
load: [configuration],
|
|
1434
|
+
}),
|
|
1435
|
+
],
|
|
1436
|
+
})
|
|
1437
|
+
export class AppModule {}
|
|
1438
|
+
|
|
1439
|
+
// Access nested config
|
|
1440
|
+
const dbHost = this.configService.get('database.host');
|
|
1441
|
+
```
|
|
1442
|
+
|
|
1443
|
+
## Summary
|
|
1444
|
+
|
|
1445
|
+
NestJS provides a comprehensive framework for building scalable server-side applications with a focus on maintainability and testability. The framework's modular architecture, combined with dependency injection and decorators, enables developers to structure applications effectively. Core building blocks include controllers for handling HTTP requests, providers for business logic, modules for organization, and middleware for request processing. Advanced features like guards, interceptors, and pipes enable cross-cutting concerns such as authentication, logging, validation, and transformation.
|
|
1446
|
+
|
|
1447
|
+
The framework excels in enterprise applications through its support for microservices architecture with multiple transport layers, database integration via TypeORM and Sequelize, GraphQL API development, and comprehensive testing utilities. NestJS's decorator-based approach and TypeScript support provide excellent developer experience with type safety and IDE integration. The framework's extensibility allows integration with various libraries and tools while maintaining clean architecture principles. Whether building REST APIs, GraphQL servers, microservices, or WebSocket applications, NestJS provides the tools and patterns needed for production-ready applications with proper error handling, validation, logging, and documentation through OpenAPI/Swagger integration.
|