nodejs-quickstart-structure 2.2.0 → 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 (47) hide show
  1. package/CHANGELOG.md +15 -0
  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 +22 -3
  8. package/package.json +1 -1
  9. package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +2 -0
  10. package/templates/clean-architecture/js/src/usecases/CreateUser.js.ejs +5 -2
  11. package/templates/clean-architecture/ts/src/index.ts.ejs +2 -0
  12. package/templates/clean-architecture/ts/src/usecases/createUser.ts.ejs +4 -2
  13. package/templates/clean-architecture/ts/src/utils/httpCodes.ts +1 -0
  14. package/templates/common/auth/js/controllers/authController.js.ejs +42 -12
  15. package/templates/common/auth/js/controllers/authController.spec.js.ejs +95 -5
  16. package/templates/common/auth/js/services/jwtService.js.ejs +3 -3
  17. package/templates/common/auth/js/services/socialAuthService.spec.js.ejs +0 -2
  18. package/templates/common/auth/ts/controllers/authController.spec.ts.ejs +29 -7
  19. package/templates/common/auth/ts/controllers/authController.ts.ejs +34 -5
  20. package/templates/common/caching/clean/js/CreateUser.js.ejs +4 -2
  21. package/templates/common/caching/clean/ts/createUser.ts.ejs +4 -2
  22. package/templates/common/eslint.config.mjs.ejs +4 -1
  23. package/templates/common/kafka/js/services/kafkaService.js.ejs +1 -1
  24. package/templates/common/package.json.ejs +2 -0
  25. package/templates/common/terraform/main.tf +52 -0
  26. package/templates/common/terraform/modules/cache/main.tf +41 -0
  27. package/templates/common/terraform/modules/cache/outputs.tf +7 -0
  28. package/templates/common/terraform/modules/cache/variables.tf +4 -0
  29. package/templates/common/terraform/modules/compute/main.tf +69 -0
  30. package/templates/common/terraform/modules/compute/outputs.tf +7 -0
  31. package/templates/common/terraform/modules/compute/variables.tf +20 -0
  32. package/templates/common/terraform/modules/database/main.tf +57 -0
  33. package/templates/common/terraform/modules/database/outputs.tf +16 -0
  34. package/templates/common/terraform/modules/database/variables.tf +27 -0
  35. package/templates/common/terraform/modules/security/main.tf +130 -0
  36. package/templates/common/terraform/modules/security/outputs.tf +15 -0
  37. package/templates/common/terraform/modules/security/variables.tf +12 -0
  38. package/templates/common/terraform/modules/vpc/main.tf +134 -0
  39. package/templates/common/terraform/modules/vpc/outputs.tf +19 -0
  40. package/templates/common/terraform/modules/vpc/variables.tf +45 -0
  41. package/templates/common/terraform/outputs.tf +29 -0
  42. package/templates/common/terraform/provider.tf +17 -0
  43. package/templates/common/terraform/variables.tf +33 -0
  44. package/templates/mvc/js/src/index.js.ejs +2 -0
  45. package/templates/mvc/js/src/utils/httpCodes.js +1 -0
  46. package/templates/mvc/ts/src/index.ts.ejs +2 -0
  47. package/templates/mvc/ts/src/utils/httpCodes.ts +1 -0
@@ -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
+ }
@@ -1,6 +1,7 @@
1
1
  const { env } = require('./config/env');
2
2
  const express = require('express');
3
3
  const cors = require('cors');
4
+ const cookieParser = require('cookie-parser');
4
5
  <%_ if (communication === 'REST APIs' || communication === 'Kafka') { -%>const apiRoutes = require('./routes/api');<%_ } %>
5
6
  const healthRoutes = require('./routes/healthRoute');
6
7
  <%_ if (communication === 'Kafka') { -%>const { connectKafka, sendMessage } = require('./services/kafkaService');<%_ } -%>
@@ -28,6 +29,7 @@ const morgan = require('morgan');
28
29
  const { errorMiddleware } = require('./utils/errorMiddleware');
29
30
 
30
31
  app.use(cors());
32
+ app.use(cookieParser());
31
33
  app.use(express.json());
32
34
  app.use(express.urlencoded({ extended: true }));
33
35
  app.use(morgan('combined', { stream: { write: message => logger.info(message.trim()) } }));
@@ -3,6 +3,7 @@ const HTTP_STATUS = {
3
3
  CREATED: 201,
4
4
  BAD_REQUEST: 400,
5
5
  UNAUTHORIZED: 401,
6
+ FORBIDDEN: 403,
6
7
  NOT_FOUND: 404,
7
8
  INTERNAL_SERVER_ERROR: 500
8
9
  };
@@ -7,6 +7,7 @@ import cors from 'cors';
7
7
  import helmet from 'helmet';
8
8
  import hpp from 'hpp';
9
9
  import rateLimit from 'express-rate-limit';
10
+ import cookieParser from 'cookie-parser';
10
11
  import logger from '@/utils/logger';
11
12
  import morgan from 'morgan';
12
13
  import { errorMiddleware } from '@/utils/errorMiddleware';
@@ -52,6 +53,7 @@ app.use(cors({ origin: '*', methods: ['GET', 'POST', 'PUT', 'DELETE'] }));
52
53
  const limiter = rateLimit({ windowMs: 10 * 60 * 1000, max: 100 });
53
54
  app.use(limiter);
54
55
 
56
+ app.use(cookieParser());
55
57
  app.use(express.json());
56
58
  app.use(express.urlencoded({ extended: true }));
57
59
  app.use(morgan('combined', { stream: { write: (message) => logger.info(message.trim()) } }));
@@ -3,6 +3,7 @@ export const HTTP_STATUS = {
3
3
  CREATED: 201,
4
4
  BAD_REQUEST: 400,
5
5
  UNAUTHORIZED: 401,
6
+ FORBIDDEN: 403,
6
7
  NOT_FOUND: 404,
7
8
  INTERNAL_SERVER_ERROR: 500
8
9
  } as const;