nodejs-quickstart-structure 2.2.1 → 2.3.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.
Files changed (31) hide show
  1. package/CHANGELOG.md +7 -1
  2. package/README.md +14 -13
  3. package/bin/index.js +2 -1
  4. package/lib/generator.js +10 -1
  5. package/lib/modules/project-setup.js +41 -0
  6. package/lib/modules/terraform-setup.js +131 -0
  7. package/lib/prompts.js +16 -1
  8. package/package.json +1 -1
  9. package/templates/clean-architecture/js/src/usecases/CreateUser.js.ejs +5 -2
  10. package/templates/clean-architecture/ts/src/usecases/createUser.ts.ejs +4 -2
  11. package/templates/common/caching/clean/js/CreateUser.js.ejs +4 -2
  12. package/templates/common/caching/clean/ts/createUser.ts.ejs +4 -2
  13. package/templates/common/terraform/main.tf +52 -0
  14. package/templates/common/terraform/modules/cache/main.tf +41 -0
  15. package/templates/common/terraform/modules/cache/outputs.tf +7 -0
  16. package/templates/common/terraform/modules/cache/variables.tf +4 -0
  17. package/templates/common/terraform/modules/compute/main.tf +69 -0
  18. package/templates/common/terraform/modules/compute/outputs.tf +7 -0
  19. package/templates/common/terraform/modules/compute/variables.tf +20 -0
  20. package/templates/common/terraform/modules/database/main.tf +57 -0
  21. package/templates/common/terraform/modules/database/outputs.tf +16 -0
  22. package/templates/common/terraform/modules/database/variables.tf +27 -0
  23. package/templates/common/terraform/modules/security/main.tf +130 -0
  24. package/templates/common/terraform/modules/security/outputs.tf +15 -0
  25. package/templates/common/terraform/modules/security/variables.tf +12 -0
  26. package/templates/common/terraform/modules/vpc/main.tf +134 -0
  27. package/templates/common/terraform/modules/vpc/outputs.tf +19 -0
  28. package/templates/common/terraform/modules/vpc/variables.tf +45 -0
  29. package/templates/common/terraform/outputs.tf +29 -0
  30. package/templates/common/terraform/provider.tf +17 -0
  31. package/templates/common/terraform/variables.tf +33 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
2
-
2
+
3
+ ## [2.3.0] - 2026-05-21
4
+
5
+ ### Added
6
+ - **Terraform Infrastructure Integration (IaC)**: Integrated modular, production-ready AWS Terraform templates with Multi-AZ VPC network isolation, WAF, ALB, RDS, and ElastiCache.
7
+ - **Expanded Validation Matrix**: Scaled the mathematical validation matrix to **23,760+ unique project scenarios** (representing a 3x expansion), ensuring 100% template rendering accuracy across all permutations including the new Infrastructure options.
8
+
3
9
  ## [2.2.1] - 2026-05-12
4
10
 
5
11
  ### Added
package/README.md CHANGED
@@ -17,7 +17,7 @@ A powerful ecosystem to scaffold production-ready Node.js microservices with bui
17
17
  - [What's New](#whats-new-in-v21-the-authentication-release)
18
18
  - [Key Features](#key-features)
19
19
  - [Professional Standards](#professional-standards)
20
- - [7,920+ Project Combinations](#7920-project-combinations)
20
+ - [23,760+ Project Combinations](#23760-project-combinations)
21
21
  - [Configuration Options](#configuration-options)
22
22
  - [Generated Project Structure](#generated-project-structure)
23
23
  - [Documentation](#documentation)
@@ -27,8 +27,8 @@ A powerful ecosystem to scaffold production-ready Node.js microservices with bui
27
27
 
28
28
  | **Path A: Next-Gen Web UI** (Recommended) | **Path B: Interactive CLI** |
29
29
  | :--- | :--- |
30
- | <a href="https://paudang.github.io/nodejs-quickstart-structure/#configurator"><img src="docs/public/v2-preview.png" width="100%" alt="UI Preview"></a> | <img src="docs/demo.gif" width="100%" alt="CLI Demo"> |
31
- | [Try Visual Configurator →](https://paudang.github.io/nodejs-quickstart-structure/#configurator) | [See CLI Commands ↓](#path-b-interactive-cli) |
30
+ | <a href="https://nodejs-quickstart-generator.netlify.app/#configurator"><img src="docs/public/v2-preview.png" width="100%" alt="UI Preview"></a> | <img src="docs/demo.gif" width="100%" alt="CLI Demo"> |
31
+ | [Try Visual Configurator →](https://nodejs-quickstart-generator.netlify.app/#configurator) | [See CLI Commands ↓](#path-b-interactive-cli) |
32
32
  | **Visual Preview**: Real-time folder simulation. | **Fast & Direct**: Quickly scaffold in terminal. |
33
33
  | **Zero-Prompt**: Paste a tailored command. | **AI-Ready**: Generates `.cursorrules`. |
34
34
 
@@ -50,12 +50,12 @@ nodejs-quickstart init
50
50
 
51
51
  ---
52
52
 
53
- ## What's New in v2.2 (The Social Auth Release)
53
+ ## What's New in v2.3 (The Infrastructure & Terraform Release)
54
54
 
55
- The v2.2.0 release brings enterprise-grade identity management to your microservices:
55
+ The v2.3.0 release brings professional-grade Infrastructure as Code (IaC) to your generated projects:
56
56
 
57
- - **OAuth2 Social Login**: Seamlessly integrate **Google** and **GitHub** authentication with automatic user provisioning and JWT session linking.
58
- - **Massive Matrix Expansion**: Now supporting **7,920+ unique project scenarios**, mathematically validated for template consistency.
57
+ - **Terraform Infrastructure Integration (IaC)**: Support for modular, production-ready AWS Terraform scaffolding with Multi-AZ VPC network isolation, WAF, application load balancers, database layers, and ElastiCache.
58
+ - **Massive Matrix Expansion**: Now supporting **23,760+ unique project scenarios**, mathematically validated for template consistency.
59
59
 
60
60
  ---
61
61
 
@@ -85,14 +85,14 @@ We don't just generate boilerplate; we generate **production-ready** foundations
85
85
 
86
86
  ---
87
87
 
88
- ## 7,920+ Project Combinations
88
+ ## 23,760+ Project Combinations
89
89
 
90
90
  The CLI supports a massive number of configurations to fit your exact needs:
91
91
 
92
- - **720 Core Combinations**:
93
- - **MVC Architecture**: 540 variants (Languages × View Engines × Databases × Communication Patterns × Caching × Auth)
94
- - **Clean Architecture**: 180 variants (Languages × Databases × Communication Patterns × Caching × Auth)
95
- - **7,920+ Total Scenarios**:
92
+ - **2,160 Core Combinations**:
93
+ - **MVC Architecture**: 1,620 variants (Languages × View Engines × Databases × Communication Patterns × Caching × Auth × Infrastructure)
94
+ - **Clean Architecture**: 540 variants (Languages × Databases × Communication Patterns × Caching × Auth × Infrastructure)
95
+ - **23,760+ Total Scenarios**:
96
96
  - Every combination can be generated across 5 CI/CD providers.
97
97
  - Optional **Enterprise-Grade Security Hardening** doubles the scenarios.
98
98
  - Every single scenario is verified to be compatible with our **80% Coverage Threshold** policy.
@@ -112,6 +112,7 @@ The CLI will guide you through:
112
112
  8. **Auth**: `None` | `JWT` | `OAuth2 (Google/GitHub) + JWT`
113
113
  9. **CI/CD**: `GitHub Actions` | `Jenkins` | `GitLab CI` | `CircleCI` | `Bitbucket Pipelines`
114
114
  10. **Security**: (Optional) Snyk & SonarCloud Hardening
115
+ 11. **Infrastructure**: (Optional IaC) `None` | `Standard` (Single EC2) | `Production` (WAF + ALB + Multi-AZ VPC)
115
116
 
116
117
  ---
117
118
 
@@ -139,7 +140,7 @@ Depending on your choices, the structure adapts. Here is a **TypeScript + Clean
139
140
 
140
141
  ## Documentation
141
142
 
142
- For full guides, architecture deep-dives, and feature references, visit our **[Official Documentation Site](https://paudang.github.io/nodejs-quickstart-structure/guide/getting-started.html)**.
143
+ For full guides, architecture deep-dives, and feature references, visit our **[Official Documentation Site](https://nodejs-quickstart-generator.netlify.app/guide/getting-started.html)**.
143
144
 
144
145
  ---
145
146
 
package/bin/index.js CHANGED
@@ -15,7 +15,7 @@ const program = new Command();
15
15
 
16
16
  program
17
17
  .name('nodejs-quickstart')
18
- .description('🚀 CLI to scaffold production-ready Node.js microservices.\n\nGenerates projects with:\n- MVC or Clean Architecture\n- REST, GraphQL or Kafka\n- MySQL, PostgreSQL, or MongoDB\n- Auth (None, JWT)\n- Docker, Flyway & Mongoose support')
18
+ .description('🚀 CLI to scaffold production-ready Node.js microservices.\n\nGenerates projects with:\n- MVC or Clean Architecture\n- REST, GraphQL or Kafka\n- MySQL, PostgreSQL, or MongoDB\n- Auth (None, JWT)\n- Terraform (Standard, Production)\n- Docker, Flyway & Mongoose support')
19
19
  .version(pkg.version, '-v, --version', 'Output the current version')
20
20
  .addHelpText('after', `\n${chalk.yellow('Example:')}\n $ nodejs-quickstart init ${chalk.gray('# Start the interactive setup')}\n`);
21
21
 
@@ -35,6 +35,7 @@ program
35
35
  .option('--caching <type>', 'Caching Layer (None/Redis/Memory Cache)')
36
36
  .option('--auth <modes...>', 'Authentication Modes (None, JWT)')
37
37
  .option('--social-auth <providers...>', 'Social Authentication Providers (None, Google, GitHub)')
38
+ .option('--terraform <tier>', 'Infrastructure Tier (None, Standard, Production)')
38
39
  .option('--advanced-options', 'Enable Advanced Options')
39
40
  .option('--no-advanced-options', 'Disable Advanced Options')
40
41
  .action(async (options) => {
package/lib/generator.js CHANGED
@@ -1,11 +1,12 @@
1
1
  import path from 'path';
2
2
  import { fileURLToPath } from 'url';
3
- import { setupProjectDirectory, copyBaseStructure, copyCommonFiles } from './modules/project-setup.js';
3
+ import { setupProjectDirectory, copyBaseStructure, copyCommonFiles, cleanGeneratedFiles } from './modules/project-setup.js';
4
4
  import { renderPackageJson, renderDockerCompose, renderReadme, renderDockerfile, renderProfessionalConfig, setupCiCd, renderTestSample, renderEnvExample, renderPm2Config, renderAiNativeFiles } from './modules/config-files.js';
5
5
  import { renderIndexFile, renderEnvConfig, renderErrorMiddleware, renderDynamicComponents, renderSwaggerConfig, setupViews as setupSrcViews, processAllTests } from './modules/app-setup.js';
6
6
  import { setupDatabase } from './modules/database-setup.js';
7
7
  import { setupKafka, setupViews } from './modules/kafka-setup.js';
8
8
  import { setupCaching } from './modules/caching-setup.js';
9
+ import { setupTerraform } from './modules/terraform-setup.js';
9
10
 
10
11
  const __filename = fileURLToPath(import.meta.url);
11
12
  const __dirname = path.dirname(__filename);
@@ -23,6 +24,7 @@ export const generateProject = async (config) => {
23
24
  database: 'None',
24
25
  includeSecurity: false,
25
26
  auth: ['None'],
27
+ terraform: 'None',
26
28
  ...config
27
29
  };
28
30
 
@@ -99,6 +101,12 @@ export const generateProject = async (config) => {
99
101
  // 16. PM2 Configuration
100
102
  await renderPm2Config(templatesDir, targetDir, config);
101
103
 
104
+ // 16a. Terraform Setup
105
+ await setupTerraform(templatesDir, targetDir, config);
106
+
107
+ // 16b. Auto-format generated files (clean trailing whitespaces, collapse EJS conditional blank lines)
108
+ await cleanGeneratedFiles(targetDir);
109
+
102
110
  // 17. Process All Tests
103
111
  await processAllTests(targetDir, config);
104
112
 
@@ -122,6 +130,7 @@ export const generateProject = async (config) => {
122
130
  ✅ Security: Helmet, CORS, Rate-Limiting added${config.includeSecurity ? '\n ✅ Enterprise Security: Snyk (SCA) & SonarCloud (SAST) integration' : ''}
123
131
  ✅ Testing: Jest setup for Unit/Integration tests
124
132
  ✅ Docker: Production-ready multi-stage build
133
+ ${config.terraform && config.terraform !== 'None' ? `✅ Infrastructure (IaC): Terraform (${config.terraform}) ready` : ''}
125
134
  ${config.ciProvider !== 'None' ? `✅ CI/CD: ${config.ciProvider} Workflow ready` : '❌ CI/CD: Skipped (User preferred)'}
126
135
 
127
136
  ----------------------------------------------------
@@ -31,3 +31,44 @@ export const copyCommonFiles = async (templatesDir, targetDir, language) => {
31
31
  await fs.copy(path.join(templatesDir, 'common', 'tsconfig.eslint.json'), path.join(targetDir, 'tsconfig.eslint.json'));
32
32
  }
33
33
  };
34
+
35
+ export const cleanGeneratedFiles = async (dir) => {
36
+ const items = await fs.readdir(dir);
37
+ for (const item of items) {
38
+ const fullPath = path.join(dir, item);
39
+ const stat = await fs.stat(fullPath);
40
+ if (stat.isDirectory()) {
41
+ if (item !== 'node_modules' && item !== '.git') {
42
+ await cleanGeneratedFiles(fullPath);
43
+ }
44
+ } else {
45
+ const ext = path.extname(item);
46
+ if (['.ts', '.js', '.json', '.yml', '.yaml', '.md', '.env', '.example', '.cursorrules'].includes(ext) || item === 'Dockerfile' || item === 'Jenkinsfile') {
47
+ let content = await fs.readFile(fullPath, 'utf-8');
48
+ const original = content;
49
+
50
+ content = content.replace(/\r\n/g, '\n');
51
+
52
+ // 1. Remove trailing spaces on lines
53
+ content = content.replace(/[ \t]+$/gm, '');
54
+
55
+ // 2. Collapse 3+ consecutive newlines to maximum 2 newlines (double spacing)
56
+ content = content.replace(/\n{3,}/g, '\n\n');
57
+
58
+ // 3. Remove blank lines immediately before closing braces/brackets, preserving indentation
59
+ content = content.replace(/\n\s*\n(\s*)([\}|\]])/g, '\n$1$2');
60
+
61
+ // 4. Remove blank lines immediately after opening braces/brackets, preserving indentation
62
+ content = content.replace(/({\s*)\n\s*\n(\s*)/g, '$1\n$2');
63
+ content = content.replace(/(\[\s*)\n\s*\n(\s*)/g, '$1\n$2');
64
+
65
+ // 5. Ensure exactly one trailing newline at the end of the file
66
+ content = content.trim() + '\n';
67
+
68
+ if (content !== original) {
69
+ await fs.writeFile(fullPath, content, 'utf-8');
70
+ }
71
+ }
72
+ }
73
+ }
74
+ };
@@ -0,0 +1,131 @@
1
+ import path from 'path';
2
+ import fs from 'fs-extra';
3
+ import chalk from 'chalk';
4
+
5
+ export const setupTerraform = async (templatesDir, targetDir, config) => {
6
+ const { terraform, projectName } = config;
7
+
8
+ if (!terraform || terraform === 'None') {
9
+ return;
10
+ }
11
+
12
+ const terraformSourceDir = path.join(templatesDir, 'common/terraform');
13
+ const terraformTargetDir = path.join(targetDir, 'terraform');
14
+
15
+ try {
16
+ // Ensure the source directory exists
17
+ if (!(await fs.pathExists(terraformSourceDir))) {
18
+ console.warn(chalk.yellow(`\n Terraform templates not found at ${terraformSourceDir}. Skipping...`));
19
+ return;
20
+ }
21
+
22
+ // Create target directory
23
+ await fs.ensureDir(terraformTargetDir);
24
+
25
+ // Copy everything first
26
+ await fs.copy(terraformSourceDir, terraformTargetDir);
27
+
28
+ // --- Customization ---
29
+
30
+ // 1. Customize variables.tf
31
+ const variablesPath = path.join(terraformTargetDir, 'variables.tf');
32
+ if (await fs.pathExists(variablesPath)) {
33
+ let content = await fs.readFile(variablesPath, 'utf8');
34
+ content = content.replace(/default\s*=\s*"nodejs-quickstart"/, `default = "${projectName}"`);
35
+
36
+ // Map database engine
37
+ if (config.database === 'PostgreSQL') {
38
+ content = content.replace(/variable "db_engine" {[\s\S]*?default\s*=\s*"mysql"/, 'variable "db_engine" {\n description = "Database engine (mysql or postgres)"\n default = "postgres"');
39
+ } else if (config.database === 'MySQL') {
40
+ content = content.replace(/default\s*=\s*"myappdb"/, `default = "${config.dbName || 'demo'}"`);
41
+ }
42
+
43
+ // Set is_production flag
44
+ if (terraform === 'Production') {
45
+ content = content.replace(/variable "is_production" \{[\s\S]*?default\s*=\s*false/, 'variable "is_production" {\n description = "Enable production-grade features (Multi-AZ, WAF, Scaling)"\n type = bool\n default = true');
46
+ }
47
+
48
+ await fs.writeFile(variablesPath, content);
49
+ }
50
+
51
+ // 2. Handle Database "None" case
52
+ if (config.database === 'None') {
53
+ // REMOVE database module call from main.tf
54
+ const mainTfPath = path.join(terraformTargetDir, 'main.tf');
55
+ if (await fs.pathExists(mainTfPath)) {
56
+ let content = await fs.readFile(mainTfPath, 'utf8');
57
+ // Remove the database module block entirely (from comment to closing brace)
58
+ content = content.replace(/# --- Data Layer \(RDS Isolated\) ---[\s\S]*?module "database" \{[\s\S]*?\}\n/g, '');
59
+ await fs.writeFile(mainTfPath, content);
60
+ }
61
+
62
+ // REMOVE database outputs from outputs.tf
63
+ const outputsTfPath = path.join(terraformTargetDir, 'outputs.tf');
64
+ if (await fs.pathExists(outputsTfPath)) {
65
+ let content = await fs.readFile(outputsTfPath, 'utf8');
66
+ // Remove individual database outputs
67
+ content = content.replace(/output "database_[\s\S]*?\}\n/g, '');
68
+ await fs.writeFile(outputsTfPath, content);
69
+ }
70
+
71
+ // DELETE the database module folder
72
+ const dbModuleDir = path.join(terraformTargetDir, 'modules/database');
73
+ if (await fs.pathExists(dbModuleDir)) {
74
+ await fs.remove(dbModuleDir);
75
+ }
76
+ }
77
+
78
+ // 3. Handle Caching case (Delete if NOT Redis)
79
+ if (config.caching !== 'Redis') {
80
+ // REMOVE cache module call from main.tf
81
+ const mainTfPath = path.join(terraformTargetDir, 'main.tf');
82
+ if (await fs.pathExists(mainTfPath)) {
83
+ let content = await fs.readFile(mainTfPath, 'utf8');
84
+ // Remove the cache module block entirely
85
+ content = content.replace(/# --- Cache Layer \(Redis ElastiCache\) ---[\s\S]*?module "cache" \{[\s\S]*?\}\n/g, '');
86
+ await fs.writeFile(mainTfPath, content);
87
+ }
88
+
89
+ // REMOVE redis output from outputs.tf
90
+ const outputsTfPath = path.join(terraformTargetDir, 'outputs.tf');
91
+ if (await fs.pathExists(outputsTfPath)) {
92
+ let content = await fs.readFile(outputsTfPath, 'utf8');
93
+ content = content.replace(/output "redis_endpoint" \{[\s\S]*?\}\n/g, '');
94
+ await fs.writeFile(outputsTfPath, content);
95
+ }
96
+
97
+ // DELETE the cache module folder
98
+ const cacheModuleDir = path.join(terraformTargetDir, 'modules/cache');
99
+ if (await fs.pathExists(cacheModuleDir)) {
100
+ await fs.remove(cacheModuleDir);
101
+ }
102
+ }
103
+ // If caching IS Redis, it's already uncommented in the template, so no action needed here.
104
+
105
+ // 4. Customize Compute module based on language
106
+ const computeMainPath = path.join(terraformTargetDir, 'modules/compute/main.tf');
107
+ if (await fs.pathExists(computeMainPath)) {
108
+ let content = await fs.readFile(computeMainPath, 'utf8');
109
+ const startHint = config.language === 'TypeScript'
110
+ ? 'npm run build && node dist/index.js'
111
+ : 'node src/index.js';
112
+
113
+ content = content.replace(/# docker run -d -p 3000:3000 my-node-app:latest/,
114
+ `# For ${config.language}: ${startHint}\n # docker run -d -p 3000:3000 my-node-app:latest`);
115
+ await fs.writeFile(computeMainPath, content);
116
+ }
117
+
118
+ // 5. Handle "Standard" tier (Optional: disable WAF for cost)
119
+ if (terraform === 'Standard') {
120
+ const mainTfPath = path.join(terraformTargetDir, 'main.tf');
121
+ if (await fs.pathExists(mainTfPath)) {
122
+ let content = await fs.readFile(mainTfPath, 'utf8');
123
+ // In Standard tier, we might want to tell the security module to skip WAF
124
+ // For now, we'll keep it simple, but we could add a variable 'enable_waf'
125
+ }
126
+ }
127
+
128
+ } catch (error) {
129
+ console.error(chalk.red(`\n Error setting up Terraform: ${error.message}`));
130
+ }
131
+ };
package/lib/prompts.js CHANGED
@@ -89,7 +89,7 @@ export const getProjectDetails = async (options = {}) => {
89
89
  {
90
90
  type: 'select',
91
91
  name: 'advancedOptions',
92
- message: 'Enable Advanced Options (Authentication, etc.)?',
92
+ message: 'Enable Advanced Options (Authentication, Terraform, etc.)?',
93
93
  choices: ['No', 'Yes'],
94
94
  default: 'No',
95
95
  when: options.advancedOptions === undefined
@@ -104,6 +104,21 @@ export const getProjectDetails = async (options = {}) => {
104
104
  const advanced = options.advancedOptions !== undefined ? options.advancedOptions : answers.advancedOptions;
105
105
  return (advanced === 'Yes' || advanced === true) && !options.auth;
106
106
  }
107
+ },
108
+ {
109
+ type: 'select',
110
+ name: 'terraform',
111
+ message: 'Select Infrastructure (IaC - Terraform):',
112
+ choices: [
113
+ { name: 'None (No infrastructure files)', value: 'None' },
114
+ { name: 'Standard (Single EC2 - Cost Efficient)', value: 'Standard' },
115
+ { name: 'Production (WAF + ALB + Private Subnets - High Availability)', value: 'Production' }
116
+ ],
117
+ default: 'None',
118
+ when: (answers) => {
119
+ const advanced = options.advancedOptions !== undefined ? options.advancedOptions : answers.advancedOptions;
120
+ return (advanced === 'Yes' || advanced === true) && !options.terraform;
121
+ }
107
122
  }
108
123
  ];
109
124
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodejs-quickstart-structure",
3
- "version": "2.2.1",
3
+ "version": "2.3.0",
4
4
  "type": "module",
5
5
  "description": "The ultimate nodejs quickstart structure CLI to scaffold Node.js microservices with MVC or Clean Architecture",
6
6
  "main": "bin/index.js",
@@ -13,12 +13,15 @@ class CreateUser {
13
13
  }
14
14
 
15
15
  async execute(name, email, password) {
16
+ <%_ if (auth.includes('JWT')) { -%>
16
17
  let finalPassword = password;
17
- <%_ if (auth.includes('JWT')) { -%>
18
18
  if (password) {
19
19
  finalPassword = await bcrypt.hash(password, 10);
20
- }<%_ } -%>
20
+ }
21
21
  const user = new User(null, name, email, finalPassword);
22
+ <%_ } else { -%>
23
+ const user = new User(null, name, email, password);
24
+ <%_ } -%>
22
25
  const savedUser = await this.userRepository.save(user);
23
26
  <%_ if (caching !== 'None') { -%>
24
27
  try {
@@ -14,13 +14,15 @@ export default class CreateUser {
14
14
  constructor(private userRepository: UserRepository) {}
15
15
 
16
16
  async execute(name: string, email: string, password?: string) {
17
- let finalPassword = password;
18
17
  <% if (auth.includes('JWT')) { -%>
18
+ let finalPassword = password;
19
19
  if (password) {
20
20
  finalPassword = await bcrypt.hash(password, 10);
21
21
  }
22
- <% } -%>
23
22
  const user = new User(null, name, email, finalPassword);
23
+ <% } else { -%>
24
+ const user = new User(null, name, email, password);
25
+ <% } -%>
24
26
  const savedUser = await this.userRepository.save(user);
25
27
  <% if (caching !== 'None') { -%>
26
28
  try {
@@ -15,13 +15,15 @@ class CreateUser {
15
15
  }
16
16
 
17
17
  async execute(name, email, password) {
18
- let finalPassword = password;
19
18
  <% if (auth.includes('JWT')) { -%>
19
+ let finalPassword = password;
20
20
  if (password) {
21
21
  finalPassword = await bcrypt.hash(password, 10);
22
22
  }
23
- <% } -%>
24
23
  const user = new User(null, name, email, finalPassword);
24
+ <% } else { -%>
25
+ const user = new User(null, name, email, password);
26
+ <% } -%>
25
27
  const savedUser = await this.userRepository.save(user);
26
28
 
27
29
  try {
@@ -14,13 +14,15 @@ export default class CreateUser {
14
14
  constructor(private userRepository: UserRepository) {}
15
15
 
16
16
  async execute(name: string, email: string, password?: string) {
17
- let finalPassword = password;
18
17
  <% if (auth.includes('JWT')) { -%>
18
+ let finalPassword = password;
19
19
  if (password) {
20
20
  finalPassword = await bcrypt.hash(password, 10);
21
21
  }
22
- <% } -%>
23
22
  const user = new User(null, name, email, finalPassword);
23
+ <% } else { -%>
24
+ const user = new User(null, name, email, password);
25
+ <% } -%>
24
26
  const savedUser = await this.userRepository.save(user);
25
27
 
26
28
  try {
@@ -0,0 +1,52 @@
1
+ # --- Network Layer ---
2
+ module "vpc" {
3
+ source = "./modules/vpc"
4
+ project_name = var.project_name
5
+ environment = var.environment
6
+ is_production = var.is_production
7
+ }
8
+
9
+ # --- Security Layer (WAF, ALB, SGs) ---
10
+ module "security" {
11
+ source = "./modules/security"
12
+ project_name = var.project_name
13
+ environment = var.environment
14
+ vpc_id = module.vpc.vpc_id
15
+ public_subnet_ids = module.vpc.public_subnet_ids
16
+ enable_waf = var.is_production
17
+ }
18
+
19
+ # --- Data Layer (RDS Isolated) ---
20
+ module "database" {
21
+ source = "./modules/database"
22
+ project_name = var.project_name
23
+ environment = var.environment
24
+ vpc_id = module.vpc.vpc_id
25
+ isolated_subnet_ids = module.vpc.isolated_subnet_ids
26
+ app_sg_id = module.security.app_sg_id
27
+ db_engine = var.db_engine
28
+ db_name = var.db_name
29
+ db_user = var.db_user
30
+ multi_az = var.is_production
31
+ }
32
+
33
+ # --- Cache Layer (Redis ElastiCache) ---
34
+ module "cache" {
35
+ source = "./modules/cache"
36
+ project_name = var.project_name
37
+ vpc_id = module.vpc.vpc_id
38
+ private_subnet_ids = module.vpc.private_subnet_ids
39
+ app_sg_id = module.security.app_sg_id
40
+ }
41
+
42
+ # --- Compute Layer (App EC2) ---
43
+ module "compute" {
44
+ source = "./modules/compute"
45
+ project_name = var.project_name
46
+ environment = var.environment
47
+ vpc_id = module.vpc.vpc_id
48
+ private_subnet_ids = module.vpc.private_subnet_ids
49
+ app_sg_id = module.security.app_sg_id
50
+ target_group_arn = module.security.alb_target_group_arn
51
+ instance_count = var.is_production ? 2 : 1
52
+ }
@@ -0,0 +1,41 @@
1
+ resource "aws_security_group" "redis_sg" {
2
+ name = "${var.project_name}-redis-sg"
3
+ description = "Allow Redis traffic from App"
4
+ vpc_id = var.vpc_id
5
+
6
+ ingress {
7
+ from_port = 6379
8
+ to_port = 6379
9
+ protocol = "tcp"
10
+ security_groups = [var.app_sg_id]
11
+ }
12
+
13
+ egress {
14
+ from_port = 0
15
+ to_port = 0
16
+ protocol = "-1"
17
+ cidr_blocks = ["0.0.0.0/0"]
18
+ }
19
+
20
+ tags = { Name = "${var.project_name}-redis-sg" }
21
+ }
22
+
23
+ resource "aws_elasticache_subnet_group" "main" {
24
+ name = "${var.project_name}-redis-subnet-group"
25
+ subnet_ids = var.private_subnet_ids
26
+
27
+ tags = { Name = "${var.project_name}-redis-subnet-group" }
28
+ }
29
+
30
+ resource "aws_elasticache_cluster" "main" {
31
+ cluster_id = "${var.project_name}-redis"
32
+ engine = "redis"
33
+ node_type = "cache.t3.micro"
34
+ num_cache_nodes = 1
35
+ parameter_group_name = "default.redis7"
36
+ port = 6379
37
+ subnet_group_name = aws_elasticache_subnet_group.main.name
38
+ security_group_ids = [aws_security_group.redis_sg.id]
39
+
40
+ tags = { Name = "${var.project_name}-redis" }
41
+ }
@@ -0,0 +1,7 @@
1
+ output "redis_endpoint" {
2
+ value = aws_elasticache_cluster.main.cache_nodes[0].address
3
+ }
4
+
5
+ output "redis_port" {
6
+ value = aws_elasticache_cluster.main.port
7
+ }
@@ -0,0 +1,4 @@
1
+ variable "project_name" {}
2
+ variable "vpc_id" {}
3
+ variable "private_subnet_ids" { type = list(string) }
4
+ variable "app_sg_id" {}
@@ -0,0 +1,69 @@
1
+ # --- IAM Role for Systems Manager (SSM) ---
2
+ # This allows SSH-less access to the private instance
3
+ resource "aws_iam_role" "ssm_role" {
4
+ name = "${var.project_name}-ssm-role"
5
+
6
+ assume_role_policy = jsonencode({
7
+ Version = "2012-10-17"
8
+ Statement = [{
9
+ Action = "sts:AssumeRole"
10
+ Effect = "Allow"
11
+ Principal = { Service = "ec2.amazonaws.com" }
12
+ }]
13
+ })
14
+ }
15
+
16
+ resource "aws_iam_role_policy_attachment" "ssm_policy" {
17
+ role = aws_iam_role.ssm_role.name
18
+ policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
19
+ }
20
+
21
+ resource "aws_iam_instance_profile" "ssm_profile" {
22
+ name = "${var.project_name}-ssm-profile"
23
+ role = aws_iam_role.ssm_role.name
24
+ }
25
+
26
+ # --- EC2 Instance ---
27
+ data "aws_ami" "latest" {
28
+ most_recent = true
29
+ owners = ["amazon"]
30
+ filter {
31
+ name = "name"
32
+ values = ["amzn2-ami-hvm-*"]
33
+ }
34
+ }
35
+
36
+ resource "aws_instance" "app" {
37
+ count = var.instance_count
38
+ ami = data.aws_ami.latest.id
39
+ instance_type = var.instance_type
40
+ subnet_id = var.private_subnet_ids[count.index % length(var.private_subnet_ids)]
41
+ vpc_security_group_ids = [var.app_sg_id]
42
+ iam_instance_profile = aws_iam_instance_profile.ssm_profile.name
43
+
44
+ user_data = <<-EOF
45
+ #!/bin/bash
46
+ yum update -y
47
+ yum install -y docker
48
+ systemctl start docker
49
+ systemctl enable docker
50
+
51
+ # Add user to docker group
52
+ usermod -aG docker ec2-user
53
+
54
+ # Note: For production, you would pull your image and run it here
55
+ # docker run -d -p 3000:3000 my-node-app:latest
56
+ EOF
57
+
58
+ tags = {
59
+ Name = "${var.project_name}-app-server-${count.index + 1}"
60
+ }
61
+ }
62
+
63
+ # Attach to ALB Target Group
64
+ resource "aws_lb_target_group_attachment" "app" {
65
+ count = var.instance_count
66
+ target_group_arn = var.target_group_arn
67
+ target_id = aws_instance.app[count.index].id
68
+ port = 3000
69
+ }
@@ -0,0 +1,7 @@
1
+ output "instance_id" {
2
+ value = aws_instance.app[*].id
3
+ }
4
+
5
+ output "private_ip" {
6
+ value = aws_instance.app[*].private_ip
7
+ }
@@ -0,0 +1,20 @@
1
+ variable "project_name" { type = string }
2
+ variable "environment" { type = string }
3
+ variable "vpc_id" { type = string }
4
+ variable "private_subnet_ids" { type = list(string) }
5
+ variable "app_sg_id" { type = string }
6
+ variable "instance_count" {
7
+ type = number
8
+ default = 1
9
+ }
10
+ variable "target_group_arn" { type = string }
11
+
12
+ variable "instance_type" {
13
+ default = "t3.micro"
14
+ }
15
+
16
+ variable "ami_id" {
17
+ description = "Amazon Linux 2023 AMI"
18
+ type = string
19
+ default = "ami-051f8b211046e76c0" # Thay đổi tùy region
20
+ }
@@ -0,0 +1,57 @@
1
+ # Security Group for RDS (Only from App SG)
2
+ resource "aws_security_group" "db_sg" {
3
+ name = "${var.project_name}-db-sg"
4
+ description = "Allow traffic from App only"
5
+ vpc_id = var.vpc_id
6
+
7
+ ingress {
8
+ from_port = var.db_engine == "mysql" ? 3306 : 5432
9
+ to_port = var.db_engine == "mysql" ? 3306 : 5432
10
+ protocol = "tcp"
11
+ security_groups = [var.app_sg_id]
12
+ }
13
+
14
+ egress {
15
+ from_port = 0
16
+ to_port = 0
17
+ protocol = "-1"
18
+ cidr_blocks = ["0.0.0.0/0"]
19
+ }
20
+
21
+ tags = { Name = "${var.project_name}-db-sg" }
22
+ }
23
+
24
+ # Subnet Group for RDS
25
+ resource "aws_db_subnet_group" "main" {
26
+ name = "${var.project_name}-db-subnet-group"
27
+ subnet_ids = var.isolated_subnet_ids
28
+
29
+ tags = { Name = "${var.project_name}-db-subnet-group" }
30
+ }
31
+
32
+ # Generate random password
33
+ resource "random_password" "db_password" {
34
+ length = 16
35
+ special = true
36
+ override_special = "!#$%&*()-_=+[]{}<>:?"
37
+ }
38
+
39
+ # RDS Instance
40
+ resource "aws_db_instance" "main" {
41
+ identifier = "${var.project_name}-db"
42
+ allocated_storage = 20
43
+ engine = var.db_engine
44
+ instance_class = var.db_instance_class
45
+ db_name = var.db_name
46
+ username = var.db_user
47
+ password = random_password.db_password.result
48
+ db_subnet_group_name = aws_db_subnet_group.main.name
49
+ vpc_security_group_ids = [aws_security_group.db_sg.id]
50
+ skip_final_snapshot = true
51
+ multi_az = var.multi_az
52
+
53
+ # Encryption at rest (Security Hardening)
54
+ storage_encrypted = true
55
+
56
+ tags = { Name = "${var.project_name}-db" }
57
+ }
@@ -0,0 +1,16 @@
1
+ output "db_endpoint" {
2
+ value = aws_db_instance.main.endpoint
3
+ }
4
+
5
+ output "db_name" {
6
+ value = aws_db_instance.main.db_name
7
+ }
8
+
9
+ output "db_user" {
10
+ value = aws_db_instance.main.username
11
+ }
12
+
13
+ output "db_password" {
14
+ value = random_password.db_password.result
15
+ sensitive = true
16
+ }
@@ -0,0 +1,27 @@
1
+ variable "project_name" { type = string }
2
+ variable "environment" { type = string }
3
+ variable "vpc_id" { type = string }
4
+ variable "isolated_subnet_ids" { type = list(string) }
5
+ variable "app_sg_id" { type = string }
6
+ variable "multi_az" {
7
+ type = bool
8
+ default = false
9
+ }
10
+
11
+ variable "db_engine" {
12
+ description = "Database engine (mysql, postgres)"
13
+ type = string
14
+ default = "mysql"
15
+ }
16
+
17
+ variable "db_instance_class" {
18
+ default = "db.t3.micro"
19
+ }
20
+
21
+ variable "db_name" {
22
+ default = "myappdb"
23
+ }
24
+
25
+ variable "db_user" {
26
+ default = "admin"
27
+ }
@@ -0,0 +1,130 @@
1
+ # --- Security Groups ---
2
+
3
+ # ALB Security Group (Public access)
4
+ resource "aws_security_group" "alb_sg" {
5
+ name = "${var.project_name}-alb-sg"
6
+ description = "Allow HTTP/HTTPS from everywhere"
7
+ vpc_id = var.vpc_id
8
+
9
+ ingress {
10
+ from_port = 80
11
+ to_port = 80
12
+ protocol = "tcp"
13
+ cidr_blocks = ["0.0.0.0/0"]
14
+ }
15
+
16
+ egress {
17
+ from_port = 0
18
+ to_port = 0
19
+ protocol = "-1"
20
+ cidr_blocks = ["0.0.0.0/0"]
21
+ }
22
+
23
+ tags = { Name = "${var.project_name}-alb-sg" }
24
+ }
25
+
26
+ # App Security Group (Only from ALB)
27
+ resource "aws_security_group" "app_sg" {
28
+ name = "${var.project_name}-app-sg"
29
+ description = "Allow traffic only from ALB"
30
+ vpc_id = var.vpc_id
31
+
32
+ ingress {
33
+ from_port = var.app_port
34
+ to_port = var.app_port
35
+ protocol = "tcp"
36
+ security_groups = [aws_security_group.alb_sg.id]
37
+ }
38
+
39
+ egress {
40
+ from_port = 0
41
+ to_port = 0
42
+ protocol = "-1"
43
+ cidr_blocks = ["0.0.0.0/0"]
44
+ }
45
+
46
+ tags = { Name = "${var.project_name}-app-sg" }
47
+ }
48
+
49
+ # --- Load Balancer ---
50
+ resource "aws_lb" "main" {
51
+ name = "${var.project_name}-alb"
52
+ internal = false
53
+ load_balancer_type = "application"
54
+ security_groups = [aws_security_group.alb_sg.id]
55
+ subnets = var.public_subnet_ids
56
+
57
+ tags = { Name = "${var.project_name}-alb" }
58
+ }
59
+
60
+ resource "aws_lb_target_group" "app" {
61
+ name = "${var.project_name}-tg"
62
+ port = var.app_port
63
+ protocol = "HTTP"
64
+ vpc_id = var.vpc_id
65
+
66
+ health_check {
67
+ path = "/health"
68
+ healthy_threshold = 2
69
+ unhealthy_threshold = 10
70
+ }
71
+ }
72
+
73
+ resource "aws_lb_listener" "http" {
74
+ load_balancer_arn = aws_lb.main.arn
75
+ port = "80"
76
+ protocol = "HTTP"
77
+
78
+ default_action {
79
+ type = "forward"
80
+ target_group_arn = aws_lb_target_group.app.arn
81
+ }
82
+ }
83
+
84
+ # --- AWS WAF (Web Application Firewall) ---
85
+ # Simple WAF with Common Rules (SQLi, Linux, etc.)
86
+ resource "aws_wafv2_web_acl" "main" {
87
+ count = var.enable_waf ? 1 : 0
88
+ name = "${var.project_name}-waf"
89
+ description = "WAF for Node.js Application"
90
+ scope = "REGIONAL"
91
+
92
+ default_action {
93
+ allow {}
94
+ }
95
+
96
+ rule {
97
+ name = "AWSManagedRulesCommonRuleSet"
98
+ priority = 1
99
+
100
+ override_action {
101
+ none {}
102
+ }
103
+
104
+ statement {
105
+ managed_rule_group_statement {
106
+ name = "AWSManagedRulesCommonRuleSet"
107
+ vendor_name = "AWS"
108
+ }
109
+ }
110
+
111
+ visibility_config {
112
+ cloudwatch_metrics_enabled = true
113
+ metric_name = "WAFCommonRule"
114
+ sampled_requests_enabled = true
115
+ }
116
+ }
117
+
118
+ visibility_config {
119
+ cloudwatch_metrics_enabled = true
120
+ metric_name = "WAFMain"
121
+ sampled_requests_enabled = true
122
+ }
123
+ }
124
+
125
+ # Associate WAF with ALB
126
+ resource "aws_wafv2_web_acl_association" "main" {
127
+ count = var.enable_waf ? 1 : 0
128
+ resource_arn = aws_lb.main.arn
129
+ web_acl_arn = aws_wafv2_web_acl.main[0].arn
130
+ }
@@ -0,0 +1,15 @@
1
+ output "alb_dns_name" {
2
+ value = aws_lb.main.dns_name
3
+ }
4
+
5
+ output "alb_target_group_arn" {
6
+ value = aws_lb_target_group.app.arn
7
+ }
8
+
9
+ output "app_sg_id" {
10
+ value = aws_security_group.app_sg.id
11
+ }
12
+
13
+ output "alb_sg_id" {
14
+ value = aws_security_group.alb_sg.id
15
+ }
@@ -0,0 +1,12 @@
1
+ variable "project_name" { type = string }
2
+ variable "environment" { type = string }
3
+ variable "vpc_id" { type = string }
4
+ variable "public_subnet_ids" { type = list(string) }
5
+ variable "enable_waf" {
6
+ type = bool
7
+ default = false
8
+ }
9
+ variable "app_port" {
10
+ type = number
11
+ default = 3000
12
+ }
@@ -0,0 +1,134 @@
1
+ resource "aws_vpc" "main" {
2
+ cidr_block = var.vpc_cidr
3
+ enable_dns_hostnames = true
4
+ enable_dns_support = true
5
+
6
+ tags = {
7
+ Name = "${var.project_name}-vpc"
8
+ Environment = var.environment
9
+ }
10
+ }
11
+
12
+ # --- Internet Gateway ---
13
+ resource "aws_internet_gateway" "igw" {
14
+ vpc_id = aws_vpc.main.id
15
+
16
+ tags = {
17
+ Name = "${var.project_name}-igw"
18
+ Environment = var.environment
19
+ }
20
+ }
21
+
22
+ # --- Subnets ---
23
+
24
+ # Public Subnets (For ALB and NAT GW)
25
+ resource "aws_subnet" "public" {
26
+ count = length(var.public_subnets)
27
+ vpc_id = aws_vpc.main.id
28
+ cidr_block = var.public_subnets[count.index]
29
+ availability_zone = var.availability_zones[count.index]
30
+ map_public_ip_on_launch = true
31
+
32
+ tags = {
33
+ Name = "${var.project_name}-public-${count.index + 1}"
34
+ Type = "Public"
35
+ Environment = var.environment
36
+ }
37
+ }
38
+
39
+ # Private Subnets (For Application)
40
+ resource "aws_subnet" "private" {
41
+ count = length(var.private_subnets)
42
+ vpc_id = aws_vpc.main.id
43
+ cidr_block = var.private_subnets[count.index]
44
+ availability_zone = var.availability_zones[count.index]
45
+
46
+ tags = {
47
+ Name = "${var.project_name}-private-${count.index + 1}"
48
+ Type = "Private"
49
+ Environment = var.environment
50
+ }
51
+ }
52
+
53
+ # Isolated Subnets (For Database)
54
+ resource "aws_subnet" "isolated" {
55
+ count = length(var.isolated_subnets)
56
+ vpc_id = aws_vpc.main.id
57
+ cidr_block = var.isolated_subnets[count.index]
58
+ availability_zone = var.availability_zones[count.index]
59
+
60
+ tags = {
61
+ Name = "${var.project_name}-isolated-${count.index + 1}"
62
+ Type = "Isolated"
63
+ Environment = var.environment
64
+ }
65
+ }
66
+
67
+ # --- NAT Gateway (1 per AZ for Production, 1 total for Standard) ---
68
+ resource "aws_eip" "nat" {
69
+ count = var.is_production ? length(var.public_subnets) : 1
70
+ domain = "vpc"
71
+ tags = { Name = "${var.project_name}-nat-eip-${count.index + 1}" }
72
+ }
73
+
74
+ resource "aws_nat_gateway" "main" {
75
+ count = var.is_production ? length(var.public_subnets) : 1
76
+ allocation_id = aws_eip.nat[count.index].id
77
+ subnet_id = aws_subnet.public[count.index].id
78
+
79
+ tags = {
80
+ Name = "${var.project_name}-nat-gw-${count.index + 1}"
81
+ Environment = var.environment
82
+ }
83
+ }
84
+
85
+ # --- Routing Tables ---
86
+
87
+ # Public Route Table
88
+ resource "aws_route_table" "public" {
89
+ vpc_id = aws_vpc.main.id
90
+
91
+ route {
92
+ cidr_block = "0.0.0.0/0"
93
+ gateway_id = aws_internet_gateway.igw.id
94
+ }
95
+
96
+ tags = { Name = "${var.project_name}-public-rt" }
97
+ }
98
+
99
+ resource "aws_route_table_association" "public" {
100
+ count = length(var.public_subnets)
101
+ subnet_id = aws_subnet.public[count.index].id
102
+ route_table_id = aws_route_table.public.id
103
+ }
104
+
105
+ # Private Route Tables (1 per AZ for Production, 1 total for Standard)
106
+ resource "aws_route_table" "private" {
107
+ count = var.is_production ? length(var.private_subnets) : 1
108
+ vpc_id = aws_vpc.main.id
109
+
110
+ route {
111
+ cidr_block = "0.0.0.0/0"
112
+ nat_gateway_id = aws_nat_gateway.main[count.index].id
113
+ }
114
+
115
+ tags = { Name = "${var.project_name}-private-rt-${count.index + 1}" }
116
+ }
117
+
118
+ resource "aws_route_table_association" "private" {
119
+ count = length(var.private_subnets)
120
+ subnet_id = aws_subnet.private[count.index].id
121
+ route_table_id = var.is_production ? aws_route_table.private[count.index].id : aws_route_table.private[0].id
122
+ }
123
+
124
+ # Isolated Route Table (No internet route)
125
+ resource "aws_route_table" "isolated" {
126
+ vpc_id = aws_vpc.main.id
127
+ tags = { Name = "${var.project_name}-isolated-rt" }
128
+ }
129
+
130
+ resource "aws_route_table_association" "isolated" {
131
+ count = length(var.isolated_subnets)
132
+ subnet_id = aws_subnet.isolated[count.index].id
133
+ route_table_id = aws_route_table.isolated.id
134
+ }
@@ -0,0 +1,19 @@
1
+ output "vpc_id" {
2
+ value = aws_vpc.main.id
3
+ }
4
+
5
+ output "public_subnet_ids" {
6
+ value = aws_subnet.public[*].id
7
+ }
8
+
9
+ output "private_subnet_ids" {
10
+ value = aws_subnet.private[*].id
11
+ }
12
+
13
+ output "isolated_subnet_ids" {
14
+ value = aws_subnet.isolated[*].id
15
+ }
16
+
17
+ output "nat_gateway_ip" {
18
+ value = aws_nat_gateway.main[*].public_ip
19
+ }
@@ -0,0 +1,45 @@
1
+ variable "vpc_cidr" {
2
+ description = "CIDR block for the VPC"
3
+ type = string
4
+ default = "10.0.0.0/16"
5
+ }
6
+
7
+ variable "project_name" {
8
+ description = "Project name for tagging"
9
+ type = string
10
+ }
11
+
12
+ variable "environment" {
13
+ description = "Environment name (dev, staging, prod)"
14
+ type = string
15
+ default = "dev"
16
+ }
17
+
18
+ variable "availability_zones" {
19
+ description = "List of availability zones"
20
+ type = list(string)
21
+ default = ["us-east-1a", "us-east-1b"]
22
+ }
23
+
24
+ variable "public_subnets" {
25
+ description = "CIDR blocks for public subnets"
26
+ type = list(string)
27
+ default = ["10.0.1.0/24", "10.0.2.0/24"]
28
+ }
29
+
30
+ variable "private_subnets" {
31
+ description = "CIDR blocks for private subnets"
32
+ type = list(string)
33
+ default = ["10.0.10.0/24", "10.0.11.0/24"]
34
+ }
35
+
36
+ variable "isolated_subnets" {
37
+ description = "CIDR blocks for isolated subnets"
38
+ type = list(string)
39
+ default = ["10.0.20.0/24", "10.0.21.0/24"]
40
+ }
41
+ variable "is_production" {
42
+ description = "Enable Production features (Multi-NAT)"
43
+ type = bool
44
+ default = false
45
+ }
@@ -0,0 +1,29 @@
1
+ output "application_url" {
2
+ description = "URL of the application (Load Balancer)"
3
+ value = "http://${module.security.alb_dns_name}"
4
+ }
5
+
6
+ output "database_endpoint" {
7
+ value = module.database.db_endpoint
8
+ }
9
+
10
+ output "database_name" {
11
+ value = module.database.db_name
12
+ }
13
+
14
+ output "database_user" {
15
+ value = module.database.db_user
16
+ }
17
+
18
+ output "database_password" {
19
+ value = module.database.db_password
20
+ sensitive = true
21
+ }
22
+
23
+ output "nat_gateway_ip" {
24
+ value = module.vpc.nat_gateway_ip
25
+ }
26
+
27
+ output "redis_endpoint" {
28
+ value = module.cache.redis_endpoint
29
+ }
@@ -0,0 +1,17 @@
1
+ terraform {
2
+ required_version = ">= 1.0.0"
3
+ required_providers {
4
+ aws = {
5
+ source = "hashicorp/aws"
6
+ version = "~> 5.0"
7
+ }
8
+ random = {
9
+ source = "hashicorp/random"
10
+ version = "~> 3.0"
11
+ }
12
+ }
13
+ }
14
+
15
+ provider "aws" {
16
+ region = var.aws_region
17
+ }
@@ -0,0 +1,33 @@
1
+ variable "aws_region" {
2
+ description = "AWS region"
3
+ default = "us-east-1"
4
+ }
5
+
6
+ variable "project_name" {
7
+ description = "Name of the project"
8
+ default = "nodejs-quickstart"
9
+ }
10
+
11
+ variable "environment" {
12
+ description = "Environment (dev, staging, prod)"
13
+ default = "dev"
14
+ }
15
+
16
+ variable "db_engine" {
17
+ description = "Database engine (mysql or postgres)"
18
+ default = "mysql"
19
+ }
20
+
21
+ variable "db_name" {
22
+ default = "myappdb"
23
+ }
24
+
25
+ variable "db_user" {
26
+ default = "admin"
27
+ }
28
+
29
+ variable "is_production" {
30
+ description = "Enable production-grade features (Multi-AZ, WAF, Scaling)"
31
+ type = bool
32
+ default = false
33
+ }