nodejs-quickstart-structure 1.4.3 → 1.7.5

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 (45) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +18 -20
  3. package/bin/index.js +1 -0
  4. package/docs/generateCase.md +127 -61
  5. package/docs/generatorFlow.md +15 -3
  6. package/docs/releaseNoteRule.md +42 -0
  7. package/lib/generator.js +46 -314
  8. package/lib/modules/app-setup.js +96 -0
  9. package/lib/modules/caching-setup.js +56 -0
  10. package/lib/modules/config-files.js +109 -0
  11. package/lib/modules/database-setup.js +111 -0
  12. package/lib/modules/kafka-setup.js +112 -0
  13. package/lib/modules/project-setup.js +31 -0
  14. package/lib/prompts.js +12 -4
  15. package/package.json +4 -3
  16. package/templates/clean-architecture/js/src/index.js.ejs +19 -6
  17. package/templates/clean-architecture/js/src/infrastructure/log/logger.js +16 -2
  18. package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.js.ejs +18 -6
  19. package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +2 -0
  20. package/templates/clean-architecture/ts/src/config/swagger.ts.ejs +1 -2
  21. package/templates/clean-architecture/ts/src/index.ts.ejs +27 -19
  22. package/templates/clean-architecture/ts/src/infrastructure/log/logger.ts +16 -2
  23. package/templates/clean-architecture/ts/src/infrastructure/repositories/userRepository.ts.ejs +17 -6
  24. package/templates/common/.env.example.ejs +39 -0
  25. package/templates/common/Dockerfile +3 -0
  26. package/templates/common/Jenkinsfile.ejs +41 -0
  27. package/templates/common/README.md.ejs +113 -106
  28. package/templates/common/caching/clean/js/CreateUser.js.ejs +25 -0
  29. package/templates/common/caching/clean/js/GetAllUsers.js.ejs +33 -0
  30. package/templates/common/caching/clean/ts/createUser.ts.ejs +23 -0
  31. package/templates/common/caching/clean/ts/getAllUsers.ts.ejs +30 -0
  32. package/templates/common/caching/js/redisClient.js.ejs +71 -0
  33. package/templates/common/caching/ts/redisClient.ts.ejs +76 -0
  34. package/templates/common/docker-compose.yml.ejs +156 -116
  35. package/templates/common/package.json.ejs +13 -2
  36. package/templates/mvc/js/src/controllers/userController.js.ejs +35 -3
  37. package/templates/mvc/js/src/index.js.ejs +26 -17
  38. package/templates/mvc/js/src/utils/logger.js +16 -6
  39. package/templates/mvc/ts/src/config/swagger.ts.ejs +1 -2
  40. package/templates/mvc/ts/src/controllers/userController.ts.ejs +35 -3
  41. package/templates/mvc/ts/src/index.ts.ejs +27 -18
  42. package/templates/mvc/ts/src/utils/logger.ts +16 -2
  43. package/templates/mvc/js/src/config/database.js +0 -12
  44. /package/templates/db/mysql/{V1__Initial_Setup.sql → V1__Initial_Setup.sql.ejs} +0 -0
  45. /package/templates/db/postgres/{V1__Initial_Setup.sql → V1__Initial_Setup.sql.ejs} +0 -0
@@ -1,4 +1,5 @@
1
1
  import winston from 'winston';
2
+ import 'winston-daily-rotate-file';
2
3
 
3
4
  const logger = winston.createLogger({
4
5
  level: 'info',
@@ -8,8 +9,21 @@ const logger = winston.createLogger({
8
9
  ),
9
10
  defaultMeta: { service: 'user-service' },
10
11
  transports: [
11
- new winston.transports.File({ filename: 'error.log', level: 'error' }),
12
- new winston.transports.File({ filename: 'combined.log' }),
12
+ new winston.transports.DailyRotateFile({
13
+ filename: 'logs/error-%DATE%.log',
14
+ datePattern: 'YYYY-MM-DD',
15
+ zippedArchive: true,
16
+ maxSize: '20m',
17
+ maxFiles: '14d',
18
+ level: 'error',
19
+ }),
20
+ new winston.transports.DailyRotateFile({
21
+ filename: 'logs/combined-%DATE%.log',
22
+ datePattern: 'YYYY-MM-DD',
23
+ zippedArchive: true,
24
+ maxSize: '20m',
25
+ maxFiles: '14d',
26
+ }),
13
27
  ],
14
28
  });
15
29
 
@@ -3,25 +3,36 @@ import UserModel from '@/infrastructure/database/models/User';
3
3
 
4
4
  export class UserRepository {
5
5
  async save(user: UserEntity): Promise<UserEntity> {
6
+ <%_ if (database === 'MongoDB') { -%>
6
7
  const newUser = await UserModel.create({ name: user.name, email: user.email });
7
- <% if (database === 'MongoDB') { %> return { id: newUser._id.toString(), name: newUser.name, email: newUser.email };
8
- <% } else { %> return { id: newUser.id, name: newUser.name, email: newUser.email };
9
- <% } -%>
8
+ return { id: newUser._id.toString(), name: newUser.name, email: newUser.email };
9
+ <%_ } else if (database === 'None') { -%>
10
+ const newUser = { id: String(UserModel.mockData.length + 1), name: user.name, email: user.email };
11
+ UserModel.mockData.push(newUser);
12
+ return newUser;
13
+ <%_ } else { -%>
14
+ const newUser = await UserModel.create({ name: user.name, email: user.email });
15
+ return { id: newUser.id, name: newUser.name, email: newUser.email };
16
+ <%_ } -%>
10
17
  }
11
18
 
12
19
  async getUsers(): Promise<UserEntity[]> {
13
- <% if (database === 'MongoDB') { %> const users = await UserModel.find();
20
+ <%_ if (database === 'MongoDB') { -%>
21
+ const users = await UserModel.find();
14
22
  return users.map(user => ({
15
23
  id: user._id.toString(),
16
24
  name: user.name,
17
25
  email: user.email
18
26
  }));
19
- <% } else { %> const users = await UserModel.findAll();
27
+ <%_ } else if (database === 'None') { -%>
28
+ return UserModel.mockData;
29
+ <%_ } else { -%>
30
+ const users = await UserModel.findAll();
20
31
  return users.map(user => ({
21
32
  id: user.id,
22
33
  name: user.name,
23
34
  email: user.email
24
35
  }));
25
- <% } -%>
36
+ <%_ } -%>
26
37
  }
27
38
  }
@@ -0,0 +1,39 @@
1
+ # Application
2
+ PORT=3000
3
+ NODE_ENV=development
4
+
5
+ <%_ if (database !== 'None') { -%>
6
+ # Database
7
+ <%_ if (database === 'MySQL') { -%>
8
+ DB_HOST=localhost
9
+ DB_PORT=3306
10
+ DB_USER=root
11
+ DB_PASSWORD=root
12
+ DB_NAME=<%= dbName %>
13
+ <%_ } -%>
14
+ <%_ if (database === 'PostgreSQL') { -%>
15
+ DB_HOST=localhost
16
+ DB_PORT=5432
17
+ DB_USER=postgres
18
+ DB_PASSWORD=root
19
+ DB_NAME=<%= dbName %>
20
+ <%_ } -%>
21
+ <%_ if (database === 'MongoDB') { -%>
22
+ DB_URI=mongodb://localhost:27017/<%= dbName %>
23
+ <%_ } -%>
24
+ <%_ } -%>
25
+
26
+
27
+ <%_ if (communication === 'Kafka') { -%>
28
+ # Communication
29
+ KAFKA_BROKER=localhost:9092
30
+ KAFKA_CLIENT_ID=<%= projectName %>
31
+ KAFKA_GROUP_ID=<%= projectName %>-group
32
+ <%_ } -%>
33
+
34
+ <%_ if (caching === 'Redis') { -%>
35
+ # Caching
36
+ REDIS_HOST=localhost
37
+ REDIS_PORT=6379
38
+ REDIS_PASSWORD=
39
+ <%_ } -%>
@@ -45,6 +45,9 @@ COPY --from=builder /app/src/views ./dist/views
45
45
 
46
46
  EXPOSE 3000
47
47
 
48
+ # Create logs directory and give permissions to node user
49
+ RUN mkdir -p logs && chown -R node:node logs
50
+
48
51
  USER node
49
52
 
50
53
  CMD ["npm", "start"]
@@ -24,6 +24,47 @@ pipeline {
24
24
  sh 'npm test'
25
25
  }
26
26
  }
27
+
28
+ // stage('Build') {
29
+ // steps {
30
+ // sh 'npm run build'
31
+ // }
32
+ // }
33
+
34
+ // stage('SonarQube Analysis') {
35
+ // environment {
36
+ // scannerHome = tool 'SonarScanner'
37
+ // }
38
+ // steps {
39
+ // withSonarQubeEnv('SonarQube') {
40
+ // sh "${scannerHome}/bin/sonar-scanner"
41
+ // }
42
+ // }
43
+ // }
44
+
45
+ // stage('Security Scan') {
46
+ // steps {
47
+ // sh 'npm audit --audit-level=high'
48
+ // }
49
+ // }
50
+
51
+ // stage('Docker Build & Push') {
52
+ // steps {
53
+ // script {
54
+ // docker.withRegistry('https://registry.hub.docker.com', 'docker-hub-credentials') {
55
+ // def appImage = docker.build("my-image:${env.BUILD_ID}")
56
+ // appImage.push()
57
+ // appImage.push("latest")
58
+ // }
59
+ // }
60
+ // }
61
+ // }
62
+
63
+ // stage('Deploy to Staging') {
64
+ // steps {
65
+ // sh './scripts/deploy.sh staging'
66
+ // }
67
+ // }
27
68
  }
28
69
 
29
70
  post {
@@ -1,107 +1,114 @@
1
- # <%= projectName %>
2
-
3
- ![Node.js](https://img.shields.io/badge/Node.js-18%2B-green.svg)
4
- ![License](https://img.shields.io/badge/License-ISC-blue.svg)
5
- <% if (language === 'TypeScript') { %>![TypeScript](https://img.shields.io/badge/Language-TypeScript-blue.svg)<% } else { %>![JavaScript](https://img.shields.io/badge/Language-JavaScript-yellow.svg)<% } %>
6
-
7
- A production-ready Node.js microservice generated with **<%= architecture %>** and **<%= database %>**.
8
- This project comes pre-configured with industry-standard tooling for **Code Quality**, **Testing**, and **Security**.
9
-
10
- ## 🚀 Key Features
11
-
12
- - **Architecture**: <%= architecture %> (<% if (architecture === 'Clean Architecture') { %>Domain, UseCases, Infrastructure<% } else { %>MVC Pattern<% } %>).
13
- - **Database**: <%= database %> <% if (database !== 'MongoDB') { %>with **Flyway** migrations<% } else { %>with **Mongoose** schemas<% } %>.
14
- - **Security**: Helmet, CORS, Rate Limiting, HPP.
15
- - **Quality**: Eslint, Prettier, Husky, Lint-Staged.
16
- - **Testing**: Jest (Unit & Integration).
17
- - **DevOps**: Multi-stage Docker build, CI/CD ready.
18
-
19
- ## 🔄 CI/CD Pipeline
20
- <% if (ciProvider === 'GitHub Actions') { -%>
21
- This project includes a **GitHub Actions** workflow located in `.github/workflows/ci.yml`.
22
- It automatically runs:
23
- - Linting
24
- - Tests
25
- - Builds
26
- <% } else if (ciProvider === 'Jenkins') { -%>
27
- This project includes a **Jenkinsfile** for comprehensive CI/CD.
28
- Pipeline stages:
29
- - Install Dependencies
30
- - Lint
31
- - Test
32
- <% } else { -%>
33
- CI/CD is not currently configured, but the project is ready for integration.
34
- <% } -%>
35
-
36
- ## 🛠️ Getting Started
37
-
38
- ### 1. Prerequisites
39
- - Node.js (v18+)
40
- - Docker & Docker Compose
41
-
42
- ### 2. Quick Start
43
- ```bash
44
- # Initialize Git (Required for Husky)
45
- git init
46
-
47
- # Install dependencies
48
- npm install
49
-
50
- # Setup Git Hooks (Husky)
51
- npm run prepare
52
-
53
- # Start Infrastructure (DB, etc.)
54
- docker-compose up -d
55
-
56
- # Run Development Server
57
- npm run dev
58
- ```
59
-
60
- ### 3. Development Standards
61
- Ensure your code meets quality standards before committing:
62
-
63
- ```bash
64
- # Run Linter
65
- npm run lint
66
-
67
- # Run Tests
68
- npm test
69
-
70
- # Format Code
71
- npm run format
72
- ```
73
-
74
- ## 📂 Project Structure
75
-
76
- The project follows **<%= architecture %>** principles.
77
- <% if (communication === 'Kafka') { -%>
78
- Microservices communication handled via **Kafka**.
79
- <% } else { -%>
80
- API is exposed via **REST**.
81
- A Swagger UI for API documentation is available at:
82
- - **URL**: `http://localhost:3000/api-docs` (Dynamic based on PORT)
83
- <% } -%>
84
-
85
- ## 📝 Logging
86
- This project uses **Winston** for structured logging.
87
- - **Development**: Logs are printed to the console.
88
- - **Production**: Logs are saved to files:
89
- - `error.log`: Only error level logs.
90
- - `combined.log`: All logs.
91
-
92
- ## 🐳 Docker Deployment
93
- This project uses a **Multi-Stage Dockerfile** for optimized production images.
94
-
95
- ```bash
96
- # Build Production Image
97
- docker build -t <%= projectName %> .
98
-
99
- # Run Container
100
- docker run -p 3000:3000 -e DATABASE_URL=... <%= projectName %>
101
- ```
102
-
103
- ## 🔒 Security Features
104
- - **Helmet**: Sets secure HTTP headers.
105
- - **CORS**: Configured for cross-origin requests.
106
- - **Rate Limiting**: Protects against DDoS / Brute-force.
1
+ # <%= projectName %>
2
+
3
+ ![Node.js](https://img.shields.io/badge/Node.js-18%2B-green.svg)
4
+ ![License](https://img.shields.io/badge/License-ISC-blue.svg)
5
+ <% if (language === 'TypeScript') { %>![TypeScript](https://img.shields.io/badge/Language-TypeScript-blue.svg)<% } else { %>![JavaScript](https://img.shields.io/badge/Language-JavaScript-yellow.svg)<% } %>
6
+
7
+ A production-ready Node.js microservice generated with **<%= architecture %>** and **<%= database %>**.
8
+ This project comes pre-configured with industry-standard tooling for **Code Quality**, **Testing**, and **Security**.
9
+
10
+ ## 🚀 Key Features
11
+
12
+ - **Architecture**: <%= architecture %> (<% if (architecture === 'Clean Architecture') { %>Domain, UseCases, Infrastructure<% } else { %>MVC Pattern<% } %>).
13
+ - **Database**: <%= database %> <% if (database !== 'MongoDB') { %>with **Flyway** migrations<% } else { %>with **Mongoose** schemas<% } %>.
14
+ - **Security**: Helmet, CORS, Rate Limiting, HPP.
15
+ - **Quality**: Eslint, Prettier, Husky, Lint-Staged.
16
+ - **Testing**: Jest (Unit & Integration).
17
+ - **DevOps**: Multi-stage Docker build, CI/CD ready.
18
+
19
+ ## 🔄 CI/CD Pipeline
20
+ <% if (ciProvider === 'GitHub Actions') { -%>
21
+ This project includes a **GitHub Actions** workflow located in `.github/workflows/ci.yml`.
22
+ It automatically runs:
23
+ - Linting
24
+ - Tests
25
+ - Builds
26
+ <% } else if (ciProvider === 'Jenkins') { -%>
27
+ This project includes a **Jenkinsfile** for comprehensive CI/CD.
28
+ Pipeline stages:
29
+ - Install Dependencies
30
+ - Lint
31
+ - Test
32
+ <% } else { -%>
33
+ CI/CD is not currently configured, but the project is ready for integration.
34
+ <% } -%>
35
+
36
+ ## 🛠️ Getting Started
37
+
38
+ ### 1. Prerequisites
39
+ - Node.js (v18+)
40
+ - Docker & Docker Compose
41
+
42
+ ### 2. Quick Start
43
+ ```bash
44
+ # Initialize Git (Required for Husky)
45
+ git init
46
+
47
+ # Install dependencies
48
+ npm install
49
+
50
+ # Setup Git Hooks (Husky)
51
+ npm run prepare
52
+
53
+ # Start Infrastructure (DB, etc.)
54
+ docker-compose up -d
55
+
56
+ # Run Development Server
57
+ npm run dev
58
+ ```
59
+
60
+ ### 3. Development Standards
61
+ Ensure your code meets quality standards before committing:
62
+
63
+ ```bash
64
+ # Run Linter
65
+ npm run lint
66
+
67
+ # Run Tests
68
+ npm test
69
+
70
+ # Format Code
71
+ npm run format
72
+ ```
73
+
74
+ ## 📂 Project Structure
75
+
76
+ The project follows **<%= architecture %>** principles.
77
+ <% if (communication === 'Kafka') { -%>
78
+ Microservices communication handled via **Kafka**.
79
+ <% } else { -%>
80
+ API is exposed via **REST**.
81
+ A Swagger UI for API documentation is available at:
82
+ - **URL**: `http://localhost:3000/api-docs` (Dynamic based on PORT)
83
+ <% } -%>
84
+
85
+ <% if (caching === 'Redis') { -%>
86
+ ## Caching
87
+ This project uses **Redis** for caching.
88
+ - **Client**: `ioredis`
89
+ - **Connection**: Configured via `REDIS_HOST`, `REDIS_PORT`, `REDIS_PASSWORD` in `.env`.
90
+ <% } -%>
91
+
92
+ ## 📝 Logging
93
+ This project uses **Winston** for structured logging.
94
+ - **Development**: Logs are printed to the console.
95
+ - **Production**: Logs are saved to files:
96
+ - `error.log`: Only error level logs.
97
+ - `combined.log`: All logs.
98
+
99
+ ## 🐳 Docker Deployment
100
+ This project uses a **Multi-Stage Dockerfile** for optimized production images.
101
+
102
+ ```bash
103
+ # Build Production Image
104
+ docker build -t <%= projectName %> .
105
+
106
+ # Run Container
107
+ docker run -p 3000:3000 -e DATABASE_URL=... <%= projectName %>
108
+ ```
109
+
110
+ ## 🔒 Security Features
111
+ - **Helmet**: Sets secure HTTP headers.
112
+ - **CORS**: Configured for cross-origin requests.
113
+ - **Rate Limiting**: Protects against DDoS / Brute-force.
107
114
  - **HPP**: Prevents HTTP Parameter Pollution attacks.
@@ -0,0 +1,25 @@
1
+ const User = require('../domain/models/User');
2
+ const redis = require('../infrastructure/caching/redisClient');
3
+ const logger = require('../infrastructure/log/logger');
4
+
5
+ class CreateUser {
6
+ constructor(userRepository) {
7
+ this.userRepository = userRepository;
8
+ }
9
+
10
+ async execute(name, email) {
11
+ const user = new User(null, name, email);
12
+ const savedUser = await this.userRepository.save(user);
13
+
14
+ try {
15
+ await redis.del('users:all');
16
+ logger.info('Invalidated users:all cache');
17
+ } catch (error) {
18
+ logger.error('Redis error (del):', error);
19
+ }
20
+
21
+ return savedUser;
22
+ }
23
+ }
24
+
25
+ module.exports = CreateUser;
@@ -0,0 +1,33 @@
1
+ const redis = require('../infrastructure/caching/redisClient');
2
+ const logger = require('../infrastructure/log/logger');
3
+
4
+ class GetAllUsers {
5
+ constructor(userRepository) {
6
+ this.userRepository = userRepository;
7
+ }
8
+
9
+ async execute() {
10
+ const cacheKey = 'users:all';
11
+ try {
12
+ const cachedUsers = await redis.get(cacheKey);
13
+ if (cachedUsers) {
14
+ logger.info('Serving users from cache');
15
+ return cachedUsers;
16
+ }
17
+ } catch (error) {
18
+ logger.error('Redis error (get):', error);
19
+ }
20
+
21
+ const users = await this.userRepository.getUsers();
22
+
23
+ try {
24
+ await redis.set(cacheKey, users, 60);
25
+ } catch (error) {
26
+ logger.error('Redis error (set):', error);
27
+ }
28
+
29
+ return users;
30
+ }
31
+ }
32
+
33
+ module.exports = GetAllUsers;
@@ -0,0 +1,23 @@
1
+ import { User } from '@/domain/user';
2
+
3
+ import { UserRepository } from '@/infrastructure/repositories/UserRepository';
4
+ import redis from '@/infrastructure/caching/redisClient';
5
+ import logger from '@/infrastructure/log/logger';
6
+
7
+ export default class CreateUser {
8
+ constructor(private userRepository: UserRepository) {}
9
+
10
+ async execute(name: string, email: string) {
11
+ const user = new User(null, name, email);
12
+ const savedUser = await this.userRepository.save(user);
13
+
14
+ try {
15
+ await redis.del('users:all');
16
+ logger.info('Invalidated users:all cache');
17
+ } catch (error) {
18
+ logger.error('Redis error (del):', error);
19
+ }
20
+
21
+ return savedUser;
22
+ }
23
+ }
@@ -0,0 +1,30 @@
1
+ import { UserRepository } from '@/infrastructure/repositories/UserRepository';
2
+ import redis from '@/infrastructure/caching/redisClient';
3
+ import logger from '@/infrastructure/log/logger';
4
+
5
+ export default class GetAllUsers {
6
+ constructor(private userRepository: UserRepository) {}
7
+
8
+ async execute() {
9
+ const cacheKey = 'users:all';
10
+ try {
11
+ const cachedUsers = await redis.get(cacheKey);
12
+ if (cachedUsers) {
13
+ logger.info('Serving users from cache');
14
+ return cachedUsers;
15
+ }
16
+ } catch (error) {
17
+ logger.error('Redis error (get):', error);
18
+ }
19
+
20
+ const users = await this.userRepository.getUsers();
21
+
22
+ try {
23
+ await redis.set(cacheKey, users, 60);
24
+ } catch (error) {
25
+ logger.error('Redis error (set):', error);
26
+ }
27
+
28
+ return users;
29
+ }
30
+ }
@@ -0,0 +1,71 @@
1
+ const Redis = require('ioredis');
2
+ require('dotenv').config();
3
+ const logger = require('<%- loggerPath %>');
4
+
5
+ class RedisService {
6
+ constructor() {
7
+ this.client = new Redis({
8
+ host: process.env.REDIS_HOST || 'localhost',
9
+ port: Number(process.env.REDIS_PORT) || 6379,
10
+ password: process.env.REDIS_PASSWORD || undefined,
11
+ retryStrategy: (times) => Math.min(times * 50, 2000),
12
+ });
13
+
14
+ this.client.on('connect', () => {
15
+ logger.info('Redis connected');
16
+ });
17
+
18
+ this.client.on('error', (err) => {
19
+ logger.error('Redis error:', err);
20
+ });
21
+ }
22
+
23
+ static getInstance() {
24
+ if (!RedisService.instance) {
25
+ RedisService.instance = new RedisService();
26
+ }
27
+ return RedisService.instance;
28
+ }
29
+
30
+ async get(key) {
31
+ try {
32
+ const data = await this.client.get(key);
33
+ return data ? JSON.parse(data) : null;
34
+ } catch (error) {
35
+ logger.error(`Redis get error for key ${key}:`, error);
36
+ return null;
37
+ }
38
+ }
39
+
40
+ async set(key, value, ttl) {
41
+ try {
42
+ const data = JSON.stringify(value);
43
+ if (ttl) {
44
+ await this.client.set(key, data, 'EX', ttl);
45
+ } else {
46
+ await this.client.set(key, data);
47
+ }
48
+ } catch (error) {
49
+ logger.error(`Redis set error for key ${key}:`, error);
50
+ }
51
+ }
52
+
53
+ async del(key) {
54
+ try {
55
+ await this.client.del(key);
56
+ } catch (error) {
57
+ logger.error(`Redis del error for key ${key}:`, error);
58
+ }
59
+ }
60
+
61
+ async getOrSet(key, fetcher, ttl = 3600) {
62
+ const cached = await this.get(key);
63
+ if (cached) return cached;
64
+
65
+ const data = await fetcher();
66
+ if (data) await this.set(key, data, ttl);
67
+ return data;
68
+ }
69
+ }
70
+
71
+ module.exports = RedisService.getInstance();
@@ -0,0 +1,76 @@
1
+ import Redis from 'ioredis';
2
+ import dotenv from 'dotenv';
3
+ import logger from '<%- loggerPath %>';
4
+
5
+ dotenv.config();
6
+
7
+ class RedisService {
8
+ private client: Redis;
9
+ private static instance: RedisService;
10
+
11
+ private constructor() {
12
+ this.client = new Redis({
13
+ host: process.env.REDIS_HOST || 'localhost',
14
+ port: Number(process.env.REDIS_PORT) || 6379,
15
+ password: process.env.REDIS_PASSWORD || undefined,
16
+ retryStrategy: (times) => Math.min(times * 50, 2000),
17
+ });
18
+
19
+ this.client.on('connect', () => {
20
+ logger.info('Redis connected');
21
+ });
22
+
23
+ this.client.on('error', (err) => {
24
+ logger.error('Redis error:', err);
25
+ });
26
+ }
27
+
28
+ public static getInstance(): RedisService {
29
+ if (!RedisService.instance) {
30
+ RedisService.instance = new RedisService();
31
+ }
32
+ return RedisService.instance;
33
+ }
34
+
35
+ public async get<T>(key: string): Promise<T | null> {
36
+ try {
37
+ const data = await this.client.get(key);
38
+ return data ? JSON.parse(data) : null;
39
+ } catch (error) {
40
+ logger.error(`Redis get error for key ${key}:`, error);
41
+ return null;
42
+ }
43
+ }
44
+
45
+ public async set(key: string, value: unknown, ttl?: number): Promise<void> {
46
+ try {
47
+ const data = JSON.stringify(value);
48
+ if (ttl) {
49
+ await this.client.set(key, data, 'EX', ttl);
50
+ } else {
51
+ await this.client.set(key, data);
52
+ }
53
+ } catch (error) {
54
+ logger.error(`Redis set error for key ${key}:`, error);
55
+ }
56
+ }
57
+
58
+ public async del(key: string): Promise<void> {
59
+ try {
60
+ await this.client.del(key);
61
+ } catch (error) {
62
+ logger.error(`Redis del error for key ${key}:`, error);
63
+ }
64
+ }
65
+
66
+ public async getOrSet<T>(key: string, fetcher: () => Promise<T>, ttl: number = 3600): Promise<T> {
67
+ const cached = await this.get<T>(key);
68
+ if (cached) return cached;
69
+
70
+ const data = await fetcher();
71
+ if (data) await this.set(key, data, ttl);
72
+ return data;
73
+ }
74
+ }
75
+
76
+ export default RedisService.getInstance();