├── .commitlintrc.json
├── .github
└── workflows
│ ├── codeql-analysis.yml
│ └── nodejs-environment.yml
├── .gitignore
├── .husky
├── .gitignore
├── commit-msg
└── pre-commit
├── .npmignore
├── .prettierrc
├── .release-it.json
├── LICENSE
├── README.md
├── eslint.config.mjs
├── index.d.ts
├── index.js
├── index.ts
├── lib
├── common
│ ├── index.ts
│ ├── mysql.decorators.ts
│ └── mysql.utils.ts
├── exceptions
│ └── circular-dependency.exception.ts
├── index.ts
├── interfaces
│ ├── index.ts
│ ├── mysql-module-async-options.interface.ts
│ ├── mysql-options-factory.interface.ts
│ └── mysql-options.interface.ts
├── mysql-core.module.ts
├── mysql.constants.ts
└── mysql.module.ts
├── package-lock.json
├── package.json
├── renovate.json
├── tests
├── data.sql
├── docker-compose.yml
├── e2e
│ ├── mysql-app-user.spec.ts
│ ├── mysql-async-class.spec.ts
│ ├── mysql-async-existing.spec.ts
│ └── mysql.spec.ts
├── jest-e2e.json
└── src
│ ├── app-async.module.ts
│ ├── app.module.ts
│ ├── apps
│ ├── app-multi-database
│ │ ├── .env.example
│ │ ├── app
│ │ │ ├── app.module.ts
│ │ │ ├── main.ts
│ │ │ ├── post
│ │ │ │ ├── dto
│ │ │ │ │ ├── create-post.dto.ts
│ │ │ │ │ └── update-post.dto.ts
│ │ │ │ ├── interfaces
│ │ │ │ │ └── post.interface.ts
│ │ │ │ ├── post.controller.ts
│ │ │ │ ├── post.module.ts
│ │ │ │ └── post.service.ts
│ │ │ └── users
│ │ │ │ ├── dto
│ │ │ │ ├── create-user.dto.ts
│ │ │ │ └── update-user.dto.ts
│ │ │ │ ├── interfaces
│ │ │ │ └── user.interface.ts
│ │ │ │ ├── users.controller.ts
│ │ │ │ ├── users.module.ts
│ │ │ │ └── users.service.ts
│ │ ├── data.sql
│ │ └── docker-compose.yml
│ └── app-mysql
│ │ ├── .env.example
│ │ ├── app
│ │ ├── app.module.ts
│ │ ├── main.ts
│ │ └── users
│ │ │ ├── dto
│ │ │ ├── create-user.dto.ts
│ │ │ └── update-user.dto.ts
│ │ │ ├── interfaces
│ │ │ └── user.interface.ts
│ │ │ ├── users.controller.ts
│ │ │ ├── users.module.ts
│ │ │ └── users.service.ts
│ │ ├── data.sql
│ │ └── docker-compose.yml
│ ├── async-class-options.module.ts
│ ├── async-existing-options.module.ts
│ └── database.module.ts
├── tsconfig.build.json
└── tsconfig.json
/.commitlintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["@commitlint/config-angular"],
3 | "rules": {
4 | "subject-case": [
5 | 2,
6 | "always",
7 | ["sentence-case", "start-case", "pascal-case", "upper-case", "lower-case"]
8 | ],
9 | "type-enum": [
10 | 2,
11 | "always",
12 | [
13 | "build",
14 | "chore",
15 | "ci",
16 | "docs",
17 | "feat",
18 | "fix",
19 | "perf",
20 | "refactor",
21 | "revert",
22 | "style",
23 | "test",
24 | "sample"
25 | ]
26 | ]
27 | }
28 | }
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | name: "CodeQL"
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 | schedule:
9 | - cron: '0 0 * * *'
10 |
11 | jobs:
12 | analyze:
13 | name: Analyze
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - name: Checkout repository
18 | uses: actions/checkout@v4
19 | with:
20 | fetch-depth: 0
21 |
22 | - name: Initialize CodeQL
23 | uses: github/codeql-action/init@v3
24 | with:
25 | languages: javascript
26 |
27 | - name: Autobuild
28 | uses: github/codeql-action/autobuild@v3
29 |
30 | - name: Perform CodeQL Analysis
31 | uses: github/codeql-action/analyze@v3
32 |
--------------------------------------------------------------------------------
/.github/workflows/nodejs-environment.yml:
--------------------------------------------------------------------------------
1 | name: Node.js CI
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | strategy:
15 | matrix:
16 | node-version: [20.x, 22.x, 24.x]
17 |
18 | steps:
19 | - uses: actions/checkout@v4
20 | - name: Use Node.js ${{ matrix.node-version }}
21 | uses: actions/setup-node@v4
22 | with:
23 | node-version: ${{ matrix.node-version }}
24 | - run: npm ci
25 | - run: npm run build --if-present
26 |
27 | integration-test:
28 |
29 | runs-on: ubuntu-latest
30 |
31 | strategy:
32 | matrix:
33 | node-version: [20.x, 22.x, 24.x]
34 | needs: [build]
35 |
36 | steps:
37 | - uses: actions/checkout@v4
38 | - name: Use Node.js ${{ matrix.node-version }}
39 | uses: actions/setup-node@v4
40 | with:
41 | node-version: ${{ matrix.node-version }}
42 | - name: Install Docker
43 | run: |
44 | sudo apt-get update
45 | curl -fsSL https://get.docker.com -o get-docker.sh
46 | sudo sh get-docker.sh
47 | sudo usermod -aG docker $USER
48 | sudo apt-get install -y docker-compose
49 | - name: Start Docker-Compose
50 | run: cd tests && docker-compose up -d
51 | - name: Install npm
52 | uses: actions/setup-node@v4
53 | with:
54 | node-version: ${{ matrix.node-version }}
55 | - run: npm install
56 | - name: Run tests e2e
57 | run: npm run test:e2e
58 | - name: Stop Docker-Compose
59 | run: cd tests && docker-compose down -v
60 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | /node_modules
3 |
4 | # IDE
5 | /.idea
6 | /.awcache
7 | /.vscode
8 | tags
9 |
10 | # misc
11 | npm-debug.log
12 | .DS_Store
13 |
14 | # tests
15 | /test
16 | /coverage
17 | /.nyc_output
18 |
19 | # dist
20 | dist
21 |
22 | #enviroments
23 | .env
24 |
--------------------------------------------------------------------------------
/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | npx --no-install commitlint --edit $1
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | npx lint-staged
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # source
2 | lib
3 | index.ts
4 | package-lock.json
5 | tslint.json
6 | tsconfig.json
7 | .prettierrc
8 |
9 | # misc
10 | .commitlintrc.json
11 | .release-it.json
12 | .eslintignore
13 | .eslintrc.js
14 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all"
4 | }
5 |
--------------------------------------------------------------------------------
/.release-it.json:
--------------------------------------------------------------------------------
1 | {
2 | "git": {
3 | "commitMessage": "chore(): release v${version}"
4 | },
5 | "github": {
6 | "release": true
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022-2025 Tony133
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | [circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
6 | [circleci-url]: https://circleci.com/gh/nestjs/nest
7 |
8 | A progressive Node.js framework for building efficient and scalable server-side applications.
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
24 |
25 | ## Description
26 |
27 | MySQL module for Nest framework (node.js) 😻
28 |
29 | ## Installation
30 |
31 | First install the module via `yarn` or `npm` or `pnpm` and do not forget to install the driver package as well:
32 |
33 |
34 | ```bash
35 | $ npm i --save nest-mysql mysql2
36 | ```
37 | or
38 |
39 | ```bash
40 | $ yarn add nest-mysql mysql2
41 | ```
42 |
43 | or
44 |
45 | ```bash
46 | $ pnpm add nest-mysql mysql2
47 | ```
48 |
49 | ## Table of Contents
50 |
51 | - [Description](#description)
52 | - [Table of Contents](#table-of-contents)
53 | - [Usage](#usage)
54 | - [MySqlModule](#mysqlmodule)
55 | - [MultiConnectionsDatabase](#multi-connections-database)
56 | - [ExampleOfUse](#example-of-use)
57 |
58 | ## Usage
59 |
60 | ### MySqlModule
61 |
62 | MySqlModule is the primary entry point for this package and can be used synchronously
63 |
64 | ```typescript
65 | @Module({
66 | imports: [
67 | MysqlModule.forRoot({
68 | host: 'localhost',
69 | database: 'test2',
70 | password: 'root',
71 | user: 'root',
72 | port: 3306,
73 | }),
74 | ],
75 | })
76 | ```
77 |
78 | or asynchronously
79 |
80 | ```typescript
81 | @Module({
82 | imports: [
83 | MysqlModule.forRootAsync({
84 | useFactory: () => ({
85 | host: 'localhost',
86 | database: 'test',
87 | password: 'root',
88 | user: 'root',
89 | port: 3306,
90 | }),
91 | }),
92 | ],
93 | })
94 | ```
95 |
96 | ## Example of use
97 |
98 | UsersService:
99 |
100 | ```typescript
101 | import { Injectable } from '@nestjs/common';
102 | import { InjectClient } from 'nest-mysql';
103 | import { Connection } from 'mysql2';
104 | import { User } from '../interfaces/user.interface';
105 |
106 | @Injectable()
107 | export class UsersService {
108 | constructor(@InjectClient() private readonly connection: Connection) {}
109 |
110 | async findAll(): Promise {
111 | const users = await this.connection.query('SELECT * FROM users');
112 | const results = Object.assign([{}], users[0]);
113 |
114 | return results;
115 | }
116 | }
117 | ```
118 |
119 | UsersController:
120 |
121 | ```typescript
122 | import { Controller, Get } from '@nestjs/common';
123 | import { UsersService } from './users.service';
124 | import { User } from '../interfaces/user.interface';
125 |
126 | @Controller()
127 | export class UsersController {
128 | constructor(private readonly usersService: UsersService) {}
129 |
130 | @Get()
131 | async getAllUsers(): Promise {
132 | return await this.usersService.findAll();
133 | }
134 | }
135 | ```
136 |
137 | ## Multi Connections Database
138 |
139 | ```typescript
140 | @Module({
141 | imports: [
142 | MysqlModule.forRootAsync(
143 | {
144 | useFactory: () => ({
145 | host: 'localhost',
146 | database: 'test1',
147 | password: 'root',
148 | user: 'root',
149 | port: 3306,
150 | }),
151 | },
152 | 'db1Connection',
153 | ),
154 | MysqlModule.forRootAsync(
155 | {
156 | useFactory: () => ({
157 | host: 'localhost',
158 | database: 'test2',
159 | password: 'root',
160 | user: 'root',
161 | port: 3307,
162 | }),
163 | },
164 | 'db2Connection',
165 | ),
166 | ],
167 | controllers: [],
168 | providers: [],
169 | })
170 | export class AppModule {}
171 | ```
172 |
173 | Usage example with Multi Connection
174 |
175 | PostService:
176 |
177 | ```typescript
178 | import { Pool } from 'mysql2';
179 | import { InjectConnection } from 'nest-mysql';
180 | import { CreatePostDto } from './dto/create-post.dto';
181 | import { Post } from '../interfaces/post.interface';
182 |
183 | @Injectable()
184 | export class PostService {
185 | constructor(
186 | @InjectConnection('db2Connection')
187 | private dbConnection: Pool,
188 | ) {}
189 |
190 | public async findAll(): Promise {
191 | const posts = await this.dbConnection.query('SELECT * FROM posts');
192 | const results = Object.assign([{}], posts[0]);
193 |
194 | return results;
195 | }
196 |
197 | public async create(createPostDto: CreatePostDto): Promise {
198 | try {
199 | const post = await this.dbConnection.query(
200 | 'INSERT INTO posts (title, description) VALUES (?, ?)',
201 | [createPostDto.title, createPostDto.description],
202 | );
203 | return post;
204 | } catch (err) {
205 | throw new HttpException(err, HttpStatus.BAD_REQUEST);
206 | }
207 | }
208 | }
209 | ```
210 |
211 | UsersService:
212 |
213 | ```typescript
214 | import { Pool } from 'mysql2';
215 | import { InjectConnection } from 'nest-mysql';
216 | import { CreateUserDto } from './dto/create-user.dto';
217 | import { User } from '../interfaces/user.interface';
218 |
219 | @Injectable()
220 | export class UsersService {
221 | constructor(
222 | @InjectConnection('db1Connection')
223 | private dbConnection: Pool,
224 | ) {}
225 |
226 | public async findAll(): Promise {
227 | const users = await this.dbConnection.query('SELECT * FROM users');
228 | const results = Object.assign([{}], users[0]);
229 |
230 | return results;
231 | }
232 |
233 | public async create(createUserDto: CreateUserDto): Promise {
234 | try {
235 | const user = await this.dbConnection.query(
236 | 'INSERT INTO users (firstName, lastName) VALUES (?, ?)',
237 | [createUserDto.firstName, createUserDto.lastName],
238 | );
239 | return user;
240 | } catch (err) {
241 | throw new HttpException(err, HttpStatus.BAD_REQUEST);
242 | }
243 | }
244 | }
245 | ```
246 |
247 | For more information on `node-mysql` see [here](https://github.com/sidorares/node-mysql2)
248 |
249 | ## Contribute
250 | Feel free to help this library, I'm quite busy with also another Nestjs packages, but the community will appreciate the effort of improving this library. Make sure you follow the guidelines
251 |
252 | ## Stay in touch
253 |
254 | - Author - [Tony133](https://github.com/Tony133)
255 | - Framework - [https://nestjs.com](https://nestjs.com/)
256 |
257 | ## License
258 |
259 | [MIT licensed](LICENSE)
260 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | //@ts-check
2 | import eslint from '@eslint/js';
3 | import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
4 | import globals from 'globals';
5 | import tseslint from 'typescript-eslint';
6 |
7 | export default tseslint.config(
8 | {
9 | ignores: ['tests/**'],
10 | },
11 | eslint.configs.recommended,
12 | ...tseslint.configs.recommendedTypeChecked,
13 | eslintPluginPrettierRecommended,
14 | {
15 | languageOptions: {
16 | globals: {
17 | ...globals.node,
18 | ...globals.jest,
19 | },
20 | ecmaVersion: 5,
21 | sourceType: 'module',
22 | parserOptions: {
23 | projectService: true,
24 | tsconfigRootDir: import.meta.dirname,
25 | },
26 | },
27 | },
28 | {
29 | rules: {
30 | '@typescript-eslint/no-explicit-any': 'off',
31 | '@typescript-eslint/no-unsafe-assignment': 'off',
32 | '@typescript-eslint/no-unsafe-call': 'off',
33 | '@typescript-eslint/no-unsafe-member-access': 'off',
34 | '@typescript-eslint/no-unsafe-function-type': 'off',
35 | '@typescript-eslint/no-unsafe-argument': 'off',
36 | '@typescript-eslint/no-unsafe-return': 'off',
37 | '@typescript-eslint/no-unused-expressions': 'off',
38 | '@typescript-eslint/await-thenable': 'off',
39 | '@typescript-eslint/unbound-method': 'off',
40 | '@typescript-eslint/no-unnecessary-type-assertion': 'off',
41 | },
42 | },
43 | );
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | export * from './dist';
2 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | function __export(m) {
3 | for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
4 | }
5 | exports.__esModule = true;
6 | __export(require("./dist"));
7 |
--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------
1 | export * from './dist';
2 |
--------------------------------------------------------------------------------
/lib/common/index.ts:
--------------------------------------------------------------------------------
1 | export * from './mysql.decorators';
2 | export * from './mysql.utils';
3 |
--------------------------------------------------------------------------------
/lib/common/mysql.decorators.ts:
--------------------------------------------------------------------------------
1 | import { Inject } from '@nestjs/common';
2 | import { MysqlModuleOptions } from '../interfaces/mysql-options.interface';
3 | import { getConnectionToken } from './mysql.utils';
4 |
5 | export const InjectClient = (connection?: string) => {
6 | return Inject(getConnectionToken(connection));
7 | };
8 |
9 | export const InjectPool = (connection?: string) => {
10 | return Inject(getConnectionToken(connection));
11 | };
12 |
13 | export const InjectConnection: (
14 | connection?: MysqlModuleOptions | string,
15 | ) => ParameterDecorator = (connection?: MysqlModuleOptions | string) =>
16 | Inject(getConnectionToken(connection));
17 |
--------------------------------------------------------------------------------
/lib/common/mysql.utils.ts:
--------------------------------------------------------------------------------
1 | import { MysqlModuleOptions } from '../interfaces';
2 | import { DEFAULT_CONNECTION_NAME } from '../mysql.constants';
3 | import { Observable } from 'rxjs';
4 | import { delay, retryWhen, scan } from 'rxjs/operators';
5 | import { randomUUID } from 'node:crypto';
6 | import { Logger } from '@nestjs/common';
7 | import { CircularDependencyException } from '../exceptions/circular-dependency.exception';
8 |
9 | const logger = new Logger('MysqlModule');
10 |
11 | /**
12 | * This function generates an injection token for an Database
13 | * @param {Function} This parameter can either be a Databases
14 | * @param {string} [connection='default'] Connection name
15 | * @returns {string} The Entity injection token
16 | */
17 | export function getDbToken(
18 | database: Function,
19 | connection: MysqlModuleOptions | string = DEFAULT_CONNECTION_NAME,
20 | ) {
21 | if (database === null || database === undefined) {
22 | throw new CircularDependencyException('@InjectClient()');
23 | }
24 | const connectionPrefix = getConnectionPrefix(connection);
25 | return `${connectionPrefix}${database.name}`;
26 | }
27 |
28 | export function getConnectionToken(
29 | connection: MysqlModuleOptions | string = DEFAULT_CONNECTION_NAME,
30 | ): string | Function {
31 | if (typeof connection === 'string') {
32 | return connection;
33 | }
34 | return `${connection.name || DEFAULT_CONNECTION_NAME}`;
35 | }
36 |
37 | /**
38 | * This function returns a Connection prefix based on the connection name
39 | * @param {MysqlModuleOptions | string} [connection='default'] This optional parameter is either
40 | * a MysqlModuleOptions or a string.
41 | * @returns {string | Function} The Connection injection token.
42 | */
43 | export function getConnectionPrefix(
44 | connection: MysqlModuleOptions | string = DEFAULT_CONNECTION_NAME,
45 | ): string {
46 | if (connection === DEFAULT_CONNECTION_NAME) {
47 | return '';
48 | }
49 | if (typeof connection === 'string') {
50 | return connection + '_';
51 | }
52 | if (connection.name === DEFAULT_CONNECTION_NAME || !connection.name) {
53 | return '';
54 | }
55 | return connection.name + '_';
56 | }
57 |
58 | export function handleRetry(
59 | retryAttempts = 9,
60 | retryDelay = 3000,
61 | ): (source: Observable) => Observable {
62 | return (source: Observable) =>
63 | source.pipe(
64 | retryWhen((e) =>
65 | e.pipe(
66 | scan((errorCount, error: Error) => {
67 | logger.error(
68 | `Unable to connect to the database. Retrying (${
69 | errorCount + 1
70 | })...`,
71 | error.stack,
72 | );
73 | if (errorCount + 1 >= retryAttempts) {
74 | throw error;
75 | }
76 | return errorCount + 1;
77 | }, 0),
78 | delay(retryDelay),
79 | ),
80 | ),
81 | );
82 | }
83 |
84 | export function getConnectionName(options: MysqlModuleOptions) {
85 | return options && options.name ? options.name : DEFAULT_CONNECTION_NAME;
86 | }
87 |
88 | export const generateString = () => randomUUID();
89 |
--------------------------------------------------------------------------------
/lib/exceptions/circular-dependency.exception.ts:
--------------------------------------------------------------------------------
1 | export class CircularDependencyException extends Error {
2 | constructor(context?: string) {
3 | const ctx = context ? ` inside ${context}` : ``;
4 | super(
5 | `A circular dependency has been detected${ctx}. Please, make sure that each side of a bidirectional relationships are decorated with "forwardRef()". Also, try to eliminate barrel files because they can lead to an unexpected behavior too.`,
6 | );
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/lib/index.ts:
--------------------------------------------------------------------------------
1 | export * from './common';
2 | export * from './interfaces';
3 | export * from './mysql.module';
4 |
--------------------------------------------------------------------------------
/lib/interfaces/index.ts:
--------------------------------------------------------------------------------
1 | export * from './mysql-options.interface';
2 | export * from './mysql-options-factory.interface';
3 | export * from './mysql-module-async-options.interface';
4 |
--------------------------------------------------------------------------------
/lib/interfaces/mysql-module-async-options.interface.ts:
--------------------------------------------------------------------------------
1 | import { ModuleMetadata, Type } from '@nestjs/common/interfaces';
2 | import { MysqlModuleOptions } from './mysql-options.interface';
3 | import { MysqlOptionsFactory } from './mysql-options-factory.interface';
4 |
5 | export interface MysqlModuleAsyncOptions
6 | extends Pick {
7 | name?: string;
8 | inject?: any[];
9 | useClass?: Type;
10 | useExisting?: Type;
11 | useFactory?: (
12 | ...args: any[]
13 | ) => Promise | MysqlModuleOptions;
14 | }
15 |
--------------------------------------------------------------------------------
/lib/interfaces/mysql-options-factory.interface.ts:
--------------------------------------------------------------------------------
1 | import { MysqlModuleOptions } from './mysql-options.interface';
2 |
3 | export interface MysqlOptionsFactory {
4 | createMysqlOptions(
5 | connectionName?: string,
6 | ): Promise | MysqlModuleOptions;
7 | }
8 |
--------------------------------------------------------------------------------
/lib/interfaces/mysql-options.interface.ts:
--------------------------------------------------------------------------------
1 | export interface MysqlModuleOptions {
2 | name?: string;
3 | host?: string;
4 | database?: string;
5 | password?: string;
6 | socketPath?: string;
7 | localAddress?: string;
8 | user?: string;
9 | port?: number;
10 | charset?: string;
11 | retryAttempts?: number;
12 | retryDelay?: number;
13 | waitForConnections?: boolean;
14 | connectionLimit?: number;
15 | queueLimit?: number;
16 | connectTimeout?: number;
17 | timezone?: string;
18 | ssl?: string | object;
19 | insecureAuth?: boolean;
20 | debug?: boolean;
21 | typeCast?: boolean;
22 | trace?: boolean;
23 | stringifyObjects?: boolean;
24 | supportBigNumbers?: boolean;
25 | bigNumberStrings?: boolean;
26 | dateStrings?: boolean;
27 | multipleStatements?: boolean;
28 | localInfile?: boolean;
29 | pool?: boolean;
30 | }
31 |
--------------------------------------------------------------------------------
/lib/mysql-core.module.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Global,
3 | Module,
4 | DynamicModule,
5 | Provider,
6 | Type,
7 | OnApplicationShutdown,
8 | Inject,
9 | } from '@nestjs/common';
10 | import {
11 | MysqlModuleAsyncOptions,
12 | MysqlModuleOptions,
13 | MysqlOptionsFactory,
14 | } from './interfaces';
15 | import { getConnectionToken, handleRetry } from './common/mysql.utils';
16 | import { MYSQL_MODULE_OPTIONS } from './mysql.constants';
17 | import { ModuleRef } from '@nestjs/core';
18 | import { defer, lastValueFrom } from 'rxjs';
19 | import * as mysql from 'mysql2/promise';
20 |
21 | @Global()
22 | @Module({})
23 | export class MysqlCoreModule implements OnApplicationShutdown {
24 | constructor(
25 | @Inject(MYSQL_MODULE_OPTIONS)
26 | private readonly options: MysqlModuleOptions,
27 | private readonly moduleRef: ModuleRef,
28 | ) {}
29 |
30 | public static forRoot(
31 | options: MysqlModuleOptions,
32 | connection?: string,
33 | ): DynamicModule {
34 | const knexModuleOptions = {
35 | provide: MYSQL_MODULE_OPTIONS,
36 | useValue: options,
37 | };
38 |
39 | const connectionProvider: Provider = {
40 | provide: getConnectionToken(connection),
41 | useFactory: async () => await this.createConnectionFactory(options),
42 | };
43 |
44 | return {
45 | module: MysqlCoreModule,
46 | providers: [connectionProvider, knexModuleOptions],
47 | exports: [connectionProvider],
48 | };
49 | }
50 |
51 | public static forRootAsync(
52 | options: MysqlModuleAsyncOptions,
53 | connection: string,
54 | ): DynamicModule {
55 | const connectionProvider: Provider = {
56 | provide: getConnectionToken(connection),
57 | useFactory: async (options: MysqlModuleOptions) => {
58 | return await this.createConnectionFactory(options);
59 | },
60 | inject: [MYSQL_MODULE_OPTIONS],
61 | };
62 |
63 | return {
64 | module: MysqlCoreModule,
65 | imports: options.imports,
66 | providers: [...this.createAsyncProviders(options), connectionProvider],
67 | exports: [connectionProvider],
68 | };
69 | }
70 |
71 | async onApplicationShutdown(): Promise {
72 | const connection = this.moduleRef.get(
73 | getConnectionToken(this.options as MysqlModuleOptions) as Type,
74 | );
75 | connection && (await connection.end);
76 | }
77 |
78 | public static createAsyncProviders(
79 | options: MysqlModuleAsyncOptions,
80 | ): Provider[] {
81 | if (options.useExisting || options.useFactory) {
82 | return [this.createAsyncOptionsProvider(options)];
83 | }
84 |
85 | const useClass = options.useClass as Type;
86 | return [
87 | this.createAsyncOptionsProvider(options),
88 | {
89 | provide: useClass,
90 | useClass,
91 | },
92 | ];
93 | }
94 |
95 | public static createAsyncOptionsProvider(
96 | options: MysqlModuleAsyncOptions,
97 | ): Provider {
98 | if (options.useFactory) {
99 | return {
100 | provide: MYSQL_MODULE_OPTIONS,
101 | useFactory: options.useFactory,
102 | inject: options.inject || [],
103 | };
104 | }
105 |
106 | // `as Type` is a workaround for microsoft/TypeScript#31603
107 | const inject = [
108 | (options.useClass || options.useExisting) as Type,
109 | ];
110 |
111 | return {
112 | provide: MYSQL_MODULE_OPTIONS,
113 | useFactory: async (
114 | optionsFactory: MysqlOptionsFactory,
115 | ): Promise => {
116 | return await optionsFactory.createMysqlOptions();
117 | },
118 | inject,
119 | };
120 | }
121 |
122 | private static async createConnectionFactory(
123 | options: MysqlModuleOptions,
124 | ): Promise {
125 | return lastValueFrom(
126 | defer(async () => {
127 | if (options.pool === true) {
128 | const clientPool = mysql.createPool(options);
129 | return clientPool;
130 | } else {
131 | const client = mysql.createConnection(options);
132 | return client;
133 | }
134 | }).pipe(handleRetry(options.retryAttempts, options.retryDelay)),
135 | );
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/lib/mysql.constants.ts:
--------------------------------------------------------------------------------
1 | export const MYSQL_MODULE_OPTIONS = 'MysqlModuleOptions';
2 | export const MYSQL_MODULE_ID = 'MysqlModuleId';
3 | export const DEFAULT_CONNECTION_NAME = 'default';
4 |
--------------------------------------------------------------------------------
/lib/mysql.module.ts:
--------------------------------------------------------------------------------
1 | import { DynamicModule, Module } from '@nestjs/common';
2 | import { MysqlCoreModule } from './mysql-core.module';
3 | import { MysqlModuleAsyncOptions, MysqlModuleOptions } from './interfaces';
4 |
5 | @Module({})
6 | export class MysqlModule {
7 | public static forRoot(
8 | options: MysqlModuleOptions,
9 | connection?: string,
10 | ): DynamicModule {
11 | return {
12 | module: MysqlModule,
13 | imports: [MysqlCoreModule.forRoot(options, connection)],
14 | };
15 | }
16 |
17 | public static forRootAsync(
18 | options: MysqlModuleAsyncOptions,
19 | connection?: string,
20 | ): DynamicModule {
21 | return {
22 | module: MysqlModule,
23 | imports: [MysqlCoreModule.forRootAsync(options, connection)],
24 | };
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nest-mysql",
3 | "version": "0.0.20",
4 | "description": "Mysql module for Nest framework (node.js) 😻",
5 | "author": "Tony133",
6 | "license": "MIT",
7 | "directories": {
8 | "lib": "lib",
9 | "test": "tests"
10 | },
11 | "scripts": {
12 | "build": "rm -rf dist && tsc -p tsconfig.json",
13 | "format": "prettier --write \"**/*.ts\"",
14 | "lint": "eslint 'lib/**/*.ts' --fix",
15 | "prepublish:npm": "npm run build",
16 | "publish:npm": "npm publish --access public",
17 | "prepublish:next": "npm run build",
18 | "publish:next": "npm publish --access public --tag next",
19 | "test:e2e": "jest --config ./tests/jest-e2e.json --detectOpenHandles --forceExit",
20 | "test:e2e:dev": "jest --config ./tests/jest-e2e.json --runInBand --watch",
21 | "prerelease": "npm run build",
22 | "release": "release-it",
23 | "prepare": "husky"
24 | },
25 | "devDependencies": {
26 | "@commitlint/cli": "19.8.1",
27 | "@commitlint/config-angular": "19.8.1",
28 | "@eslint/eslintrc": "3.3.1",
29 | "@eslint/js": "9.28.0",
30 | "@nestjs/common": "11.1.3",
31 | "@nestjs/config": "4.0.2",
32 | "@nestjs/core": "11.1.3",
33 | "@nestjs/platform-fastify": "11.1.3",
34 | "@nestjs/testing": "11.1.3",
35 | "@types/jest": "29.5.14",
36 | "@types/node": "22.15.30",
37 | "@types/supertest": "6.0.3",
38 | "eslint": "9.28.0",
39 | "eslint-config-prettier": "10.1.5",
40 | "eslint-plugin-import": "2.31.0",
41 | "eslint-plugin-prettier": "5.4.1",
42 | "globals": "16.2.0",
43 | "husky": "9.1.7",
44 | "jest": "29.7.0",
45 | "mysql2": "3.14.1",
46 | "prettier": "3.5.3",
47 | "reflect-metadata": "0.2.2",
48 | "release-it": "19.0.3",
49 | "rxjs": "7.8.2",
50 | "supertest": "^7.0.0",
51 | "ts-jest": "29.3.4",
52 | "ts-node": "10.9.2",
53 | "tsconfig-paths": "4.2.0",
54 | "typescript": "5.8.3",
55 | "typescript-eslint": "8.33.1"
56 | },
57 | "peerDependencies": {
58 | "@nestjs/common": "^9.0.0 || ^10.0.0 || ^11.0.0",
59 | "@nestjs/core": "^9.0.0 || ^10.0.0 || ^11.0.0",
60 | "mysql2": "^2.3.3 || ^3.0.0",
61 | "reflect-metadata": "^0.1.13 || ^0.2.0",
62 | "rxjs": "^6.6.3 || ^7.2.0"
63 | },
64 | "lint-staged": {
65 | "**/*.{ts,json}": []
66 | },
67 | "repository": {
68 | "type": "git",
69 | "url": "https://github.com/tony133/nestjs-mysql"
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "semanticCommits": true,
3 | "packageRules": [{
4 | "depTypeList": ["devDependencies"],
5 | "automerge": true
6 | }],
7 | "extends": [
8 | "config:base"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/tests/data.sql:
--------------------------------------------------------------------------------
1 | CREATE DATABASE IF NOT EXISTS test;
2 |
3 | USE test;
4 |
5 | CREATE TABLE IF NOT EXISTS users (
6 | id INT(11) AUTO_INCREMENT,
7 | firstName VARCHAR(100) NOT NULL,
8 | lastName VARCHAR(50) NOT NULL,
9 | PRIMARY KEY (id)
10 | );
11 |
--------------------------------------------------------------------------------
/tests/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | mysql:
3 | image: mysql:8.3
4 | environment:
5 | MYSQL_ROOT_USER: root
6 | MYSQL_ROOT_PASSWORD: root
7 | MYSQL_DATABASE: test
8 | volumes:
9 | - db-data:/var/lib/mysql
10 | - ./data.sql:/docker-entrypoint-initdb.d/data.sql
11 |
12 | ports:
13 | - "3306:3306"
14 | restart: always
15 |
16 | volumes:
17 | db-data:
18 |
19 |
--------------------------------------------------------------------------------
/tests/e2e/mysql-app-user.spec.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from '@nestjs/common';
2 | import { TestingModule, Test } from '@nestjs/testing';
3 | import * as request from 'supertest';
4 | import { UsersModule } from '../src/apps/app-mysql/app/users/users.module';
5 | import { AppModule } from '../src/apps/app-mysql/app/app.module';
6 | import {
7 | FastifyAdapter,
8 | NestFastifyApplication,
9 | } from '@nestjs/platform-fastify';
10 |
11 | describe('[Feature] User - /user', () => {
12 | let app: NestFastifyApplication;
13 |
14 | beforeAll(async () => {
15 | const moduleFixture: TestingModule = await Test.createTestingModule({
16 | imports: [AppModule, UsersModule],
17 | }).compile();
18 |
19 | app = moduleFixture.createNestApplication(
20 | new FastifyAdapter(),
21 | );
22 | await app.init();
23 | await app.getHttpAdapter().getInstance().ready();
24 | });
25 |
26 | it('should create a new user [POST /users]', () => {
27 | return request(app.getHttpServer())
28 | .post('/users')
29 | .set('Accept', 'application/json')
30 | .send({
31 | firstName: 'firstName',
32 | lastName: 'lastName',
33 | })
34 | .then(({ body }) => {
35 | expect(body[0]).toEqual({
36 | fieldCount: 0,
37 | changedRows: 0,
38 | affectedRows: 1,
39 | insertId: 1,
40 | info: '',
41 | serverStatus: 2,
42 | warningStatus: 0,
43 | });
44 | expect(HttpStatus.CREATED);
45 | });
46 | });
47 |
48 | it('should get all users [GET /users]', () => {
49 | return request(app.getHttpServer())
50 | .get('/users')
51 | .expect(HttpStatus.OK)
52 | .set('Accept', 'application/json')
53 | .then(({ body }) => {
54 | expect(body).toEqual([
55 | {
56 | id: 1,
57 | firstName: 'firstName',
58 | lastName: 'lastName',
59 | },
60 | ]);
61 | });
62 | });
63 |
64 | it('should get a user [GET /users/:id]', () => {
65 | return request(app.getHttpServer())
66 | .get('/users/1')
67 | .expect(HttpStatus.OK)
68 | .set('Accept', 'application/json')
69 | .then(({ body }) => {
70 | expect(body).toEqual([
71 | {
72 | id: 1,
73 | firstName: 'firstName',
74 | lastName: 'lastName',
75 | },
76 | ]);
77 | });
78 | });
79 |
80 | it('should update a user [PUT /users/:id]', () => {
81 | return request(app.getHttpServer())
82 | .put('/users/1')
83 | .expect(HttpStatus.OK)
84 | .send({
85 | firstName: 'firstName update',
86 | lastName: 'lastName update',
87 | })
88 | .then(({ body }) => {
89 | expect(body[0]).toEqual({
90 | fieldCount: 0,
91 | affectedRows: 1,
92 | insertId: 0,
93 | info: 'Rows matched: 1 Changed: 1 Warnings: 0',
94 | serverStatus: 2,
95 | warningStatus: 0,
96 | changedRows: 1,
97 | });
98 | });
99 | });
100 |
101 | it('should delete a user by id [DELETE /users/:id]', () => {
102 | return request(app.getHttpServer())
103 | .delete('/users/1')
104 | .expect(HttpStatus.OK);
105 | });
106 |
107 | afterAll(async () => {
108 | await app.close();
109 | });
110 | });
111 |
--------------------------------------------------------------------------------
/tests/e2e/mysql-async-class.spec.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from '@nestjs/common';
2 | import { Test } from '@nestjs/testing';
3 | import { AsyncOptionsClassModule } from '../src/async-class-options.module';
4 | import * as request from 'supertest';
5 | import {
6 | FastifyAdapter,
7 | NestFastifyApplication,
8 | } from '@nestjs/platform-fastify';
9 |
10 | describe('Mysql (async configuration)', () => {
11 | let app: NestFastifyApplication;
12 |
13 | beforeAll(async () => {
14 | const moduleFixture = await Test.createTestingModule({
15 | imports: [AsyncOptionsClassModule],
16 | }).compile();
17 |
18 | app = moduleFixture.createNestApplication(
19 | new FastifyAdapter(),
20 | );
21 | await app.init();
22 | await app.getHttpAdapter().getInstance().ready();
23 | });
24 |
25 | it(`should return created entity`, () => {
26 | return request(app.getHttpServer())
27 | .post('/users')
28 | .expect(HttpStatus.CREATED)
29 | .set('Accept', 'application/json')
30 | .send({
31 | firstName: 'firstName',
32 | lastName: 'lastName',
33 | })
34 | .expect(({ body }) => {
35 | expect(body[0]).toEqual({
36 | fieldCount: 0,
37 | changedRows: 0,
38 | affectedRows: 1,
39 | insertId: body[0].insertId,
40 | info: '',
41 | serverStatus: 2,
42 | warningStatus: 0,
43 | });
44 | });
45 | });
46 |
47 | afterAll(async () => {
48 | await app.close();
49 | });
50 | });
51 |
--------------------------------------------------------------------------------
/tests/e2e/mysql-async-existing.spec.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from '@nestjs/common';
2 | import { Test } from '@nestjs/testing';
3 | import { AsyncOptionsExistingModule } from '../src/async-existing-options.module';
4 | import * as request from 'supertest';
5 | import {
6 | FastifyAdapter,
7 | NestFastifyApplication,
8 | } from '@nestjs/platform-fastify';
9 |
10 | describe('Mysql (async configuration)', () => {
11 | let app: NestFastifyApplication;
12 |
13 | beforeAll(async () => {
14 | const moduleFixture = await Test.createTestingModule({
15 | imports: [AsyncOptionsExistingModule],
16 | }).compile();
17 |
18 | app = moduleFixture.createNestApplication(
19 | new FastifyAdapter(),
20 | );
21 | await app.init();
22 | await app.getHttpAdapter().getInstance().ready();
23 | });
24 |
25 | it(`should return created entity`, () => {
26 | return request(app.getHttpServer())
27 | .post('/users')
28 | .expect(HttpStatus.CREATED)
29 | .set('Accept', 'application/json')
30 | .send({
31 | firstName: 'firstName',
32 | lastName: 'lastName',
33 | })
34 | .expect(({ body }) => {
35 | expect(body[0]).toEqual({
36 | fieldCount: 0,
37 | changedRows: 0,
38 | affectedRows: 1,
39 | insertId: body[0].insertId,
40 | info: '',
41 | serverStatus: 2,
42 | warningStatus: 0,
43 | });
44 | });
45 | });
46 |
47 | afterAll(async () => {
48 | await app.close();
49 | });
50 | });
51 |
--------------------------------------------------------------------------------
/tests/e2e/mysql.spec.ts:
--------------------------------------------------------------------------------
1 | import { Test } from '@nestjs/testing';
2 | import { ApplicationModule } from '../src/app.module';
3 | import * as request from 'supertest';
4 | import {
5 | FastifyAdapter,
6 | NestFastifyApplication,
7 | } from '@nestjs/platform-fastify';
8 |
9 | describe('Mysql', () => {
10 | let app: NestFastifyApplication;
11 |
12 | beforeAll(async () => {
13 | const moduleFixture = await Test.createTestingModule({
14 | imports: [ApplicationModule],
15 | }).compile();
16 |
17 | app = moduleFixture.createNestApplication(
18 | new FastifyAdapter(),
19 | );
20 | await app.init();
21 | await app.getHttpAdapter().getInstance().ready();
22 | });
23 |
24 | it(`should return created entity`, () => {
25 | return request(app.getHttpServer())
26 | .post('/users')
27 | .expect(201)
28 | .set('Accept', 'application/json')
29 | .send({
30 | firstName: 'firstName',
31 | lastName: 'lastName',
32 | })
33 | .expect(({ body }) => {
34 | expect(body[0]).toEqual({
35 | fieldCount: 0,
36 | changedRows: 0,
37 | affectedRows: 1,
38 | insertId: body[0].insertId,
39 | info: '',
40 | serverStatus: 2,
41 | warningStatus: 0,
42 | });
43 | });
44 | });
45 |
46 | afterAll(async () => {
47 | await app.close();
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/tests/jest-e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "moduleFileExtensions": ["js", "json", "ts"],
3 | "rootDir": ".",
4 | "testEnvironment": "node",
5 | "testRegex": ".spec.ts$",
6 | "transform": {
7 | "^.+\\.(t|j)s$": "ts-jest"
8 | }
9 | }
--------------------------------------------------------------------------------
/tests/src/app-async.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { AppModule } from './apps/app-mysql/app/app.module';
3 | import { DatabaseModule } from './database.module';
4 |
5 | @Module({
6 | imports: [DatabaseModule.forRoot(), AppModule],
7 | })
8 | export class AsyncApplicationModule {}
9 |
--------------------------------------------------------------------------------
/tests/src/app.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { MysqlModule } from '../../lib/mysql.module';
3 | import { UsersModule } from './apps/app-mysql/app/users/users.module';
4 |
5 | @Module({
6 | imports: [
7 | MysqlModule.forRoot({
8 | host: '127.0.0.1',
9 | database: 'test',
10 | password: 'root',
11 | user: 'root',
12 | port: 3306,
13 | }),
14 | UsersModule,
15 | ],
16 | })
17 | export class ApplicationModule {}
18 |
--------------------------------------------------------------------------------
/tests/src/apps/app-multi-database/.env.example:
--------------------------------------------------------------------------------
1 | MYSQL_HOST_CONNECT_DB_ONE="localhost"
2 | MYSQL_DB_CONNECT_DB_ONE="test1"
3 | MYSQL_PASSWORD_CONNECT_DB_ONE="root"
4 | MYSQL_USER_CONNECT_DB_ONE="root"
5 | MYSQL_PORT_CONNECT_DB_ONE=3306
6 |
7 | MYSQL_HOST_CONNECT_DB_TWO="localhost"
8 | MYSQL_DB_CONNECT_DB_TWO="test2"
9 | MYSQL_PASSWORD_CONNECT_DB_TWO="root"
10 | MYSQL_USER_CONNECT_DB_TWO="root"
11 | MYSQL_PORT_CONNECT_DB_TWO=3307
--------------------------------------------------------------------------------
/tests/src/apps/app-multi-database/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { ConfigModule, ConfigService } from '@nestjs/config';
3 | import { MysqlModule } from '../../../../../lib';
4 | import { PostModule } from './post/post.module';
5 | import { UsersModule } from './users/users.module';
6 |
7 | @Module({
8 | imports: [
9 | MysqlModule.forRootAsync(
10 | {
11 | imports: [ConfigModule],
12 | inject: [ConfigService],
13 | useFactory: (config: ConfigService) => ({
14 | host: config.get('MYSQL_HOST_CONNECT_DB_ONE'),
15 | database: config.get('MYSQL_DB_CONNECT_DB_ONE'),
16 | password: config.get('MYSQL_PASSWORD_CONNECT_DB_ONE'),
17 | user: config.get('MYSQL_USER_CONNECT_DB_ONE'),
18 | port: config.get('MYSQL_PORT_CONNECT_DB_ONE'),
19 | }),
20 | },
21 | 'db1Connection',
22 | ),
23 | MysqlModule.forRootAsync(
24 | {
25 | imports: [ConfigModule],
26 | inject: [ConfigService],
27 | useFactory: (config: ConfigService) => ({
28 | host: config.get('MYSQL_HOST_CONNECT_DB_TWO'),
29 | database: config.get('MYSQL_DB_CONNECT_DB_TWO'),
30 | password: config.get('MYSQL_PASSWORD_CONNECT_DB_TWO'),
31 | user: config.get('MYSQL_USER_CONNECT_DB_TWO'),
32 | port: config.get('MYSQL_PORT_CONNECT_DB_TWO'),
33 | }),
34 | },
35 | 'db2Connection',
36 | ),
37 | UsersModule,
38 | PostModule,
39 | ],
40 | controllers: [],
41 | providers: [],
42 | })
43 | export class AppModule {}
44 |
--------------------------------------------------------------------------------
/tests/src/apps/app-multi-database/app/main.ts:
--------------------------------------------------------------------------------
1 | import { NestFactory } from '@nestjs/core';
2 | import { AppModule } from './app.module';
3 | import {
4 | NestFastifyApplication,
5 | FastifyAdapter,
6 | } from '@nestjs/platform-fastify';
7 |
8 | async function bootstrap() {
9 | const app = await NestFactory.create(
10 | AppModule,
11 | new FastifyAdapter(),
12 | );
13 |
14 | await app.listen(3000);
15 | }
16 | bootstrap();
17 |
--------------------------------------------------------------------------------
/tests/src/apps/app-multi-database/app/post/dto/create-post.dto.ts:
--------------------------------------------------------------------------------
1 | export class CreatePostDto {
2 | readonly title: string;
3 | readonly description: string;
4 | }
5 |
--------------------------------------------------------------------------------
/tests/src/apps/app-multi-database/app/post/dto/update-post.dto.ts:
--------------------------------------------------------------------------------
1 | export class UpdatePostDto {
2 | readonly title: string;
3 | readonly description: string;
4 | }
5 |
--------------------------------------------------------------------------------
/tests/src/apps/app-multi-database/app/post/interfaces/post.interface.ts:
--------------------------------------------------------------------------------
1 | export class Post {
2 | id: number;
3 | title: string;
4 | description: string;
5 | }
6 |
--------------------------------------------------------------------------------
/tests/src/apps/app-multi-database/app/post/post.controller.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Controller,
3 | Get,
4 | Post,
5 | Body,
6 | Patch,
7 | Param,
8 | Delete,
9 | } from '@nestjs/common';
10 | import { PostService } from './post.service';
11 | import { CreatePostDto } from './dto/create-post.dto';
12 | import { UpdatePostDto } from './dto/update-post.dto';
13 |
14 | @Controller('posts')
15 | export class PostController {
16 | constructor(private readonly postService: PostService) {}
17 |
18 | @Post()
19 | create(@Body() createPostDto: CreatePostDto) {
20 | return this.postService.create(createPostDto);
21 | }
22 |
23 | @Get()
24 | findAll() {
25 | return this.postService.findAll();
26 | }
27 |
28 | @Get(':id')
29 | findOne(@Param('id') id: string) {
30 | return this.postService.findOne(id);
31 | }
32 |
33 | @Patch(':id')
34 | update(@Param('id') id: string, @Body() updatePostDto: UpdatePostDto) {
35 | return this.postService.update(+id, updatePostDto);
36 | }
37 |
38 | @Delete(':id')
39 | remove(@Param('id') id: string) {
40 | return this.postService.remove(id);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tests/src/apps/app-multi-database/app/post/post.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { PostService } from './post.service';
3 | import { PostController } from './post.controller';
4 |
5 | @Module({
6 | imports: [],
7 | controllers: [PostController],
8 | providers: [PostService],
9 | })
10 | export class PostModule {}
11 |
--------------------------------------------------------------------------------
/tests/src/apps/app-multi-database/app/post/post.service.ts:
--------------------------------------------------------------------------------
1 | import {
2 | BadRequestException,
3 | HttpException,
4 | HttpStatus,
5 | Injectable,
6 | } from '@nestjs/common';
7 | import { Pool } from 'mysql2';
8 | import { CreatePostDto } from './dto/create-post.dto';
9 | import { UpdatePostDto } from './dto/update-post.dto';
10 | import { NotFoundException } from '@nestjs/common';
11 | import { InjectConnection } from '../../../../../../lib';
12 |
13 | @Injectable()
14 | export class PostService {
15 | constructor(
16 | @InjectConnection('db2Connection')
17 | private dbConnection: Pool,
18 | ) {}
19 |
20 | public async findAll(): Promise {
21 | const posts = await this.dbConnection.query('SELECT * FROM posts');
22 | const results = Object.assign([{}], posts[0]);
23 |
24 | return results;
25 | }
26 |
27 | public async findOne(id: string): Promise {
28 | if (!id) {
29 | throw new BadRequestException();
30 | }
31 |
32 | const post = await this.dbConnection.query(
33 | 'SELECT * FROM posts WHERE id=?',
34 | [id],
35 | );
36 |
37 | if (!post) {
38 | throw new NotFoundException();
39 | }
40 |
41 | const result = Object.assign([{}], post[0]);
42 |
43 | return result;
44 | }
45 |
46 | public async create(createPostDto: CreatePostDto): Promise {
47 | try {
48 | const post = await this.dbConnection.query(
49 | 'INSERT INTO posts (title, description) VALUES (?, ?)',
50 | [createPostDto.title, createPostDto.description],
51 | );
52 | return post;
53 | } catch (err) {
54 | throw new HttpException(err, HttpStatus.BAD_REQUEST);
55 | }
56 | }
57 |
58 | public async update(id: number, updateUserDto: UpdatePostDto): Promise {
59 | try {
60 | const { title, description } = updateUserDto;
61 |
62 | const post = await this.dbConnection.query(
63 | 'UPDATE posts SET title=?, descriptions=? WHERE id=?',
64 | [title, description, id],
65 | );
66 | return post;
67 | } catch (err) {
68 | throw new HttpException(err, HttpStatus.BAD_REQUEST);
69 | }
70 | }
71 |
72 | public async remove(id: string): Promise {
73 | if (!id) {
74 | throw new BadRequestException();
75 | }
76 |
77 | const post = await this.dbConnection.query('DELETE FROM posts WHERE id=?', [
78 | id,
79 | ]);
80 | return post;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/tests/src/apps/app-multi-database/app/users/dto/create-user.dto.ts:
--------------------------------------------------------------------------------
1 | export class CreateUserDto {
2 | firstName: string;
3 | lastName: string;
4 | }
5 |
--------------------------------------------------------------------------------
/tests/src/apps/app-multi-database/app/users/dto/update-user.dto.ts:
--------------------------------------------------------------------------------
1 | export class UpdateUserDto {
2 | firstName: string;
3 | lastName: string;
4 | }
5 |
--------------------------------------------------------------------------------
/tests/src/apps/app-multi-database/app/users/interfaces/user.interface.ts:
--------------------------------------------------------------------------------
1 | export class User {
2 | id: number;
3 | firstName: string;
4 | lastName: string;
5 | }
6 |
--------------------------------------------------------------------------------
/tests/src/apps/app-multi-database/app/users/users.controller.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Controller,
3 | Get,
4 | Post,
5 | Body,
6 | Put,
7 | Param,
8 | Delete,
9 | } from '@nestjs/common';
10 | import { UsersService } from './users.service';
11 | import { CreateUserDto } from './dto/create-user.dto';
12 | import { UpdateUserDto } from './dto/update-user.dto';
13 |
14 | @Controller('users')
15 | export class UsersController {
16 | constructor(private readonly usersService: UsersService) {}
17 |
18 | @Post()
19 | create(@Body() createUserDto: CreateUserDto) {
20 | return this.usersService.create(createUserDto);
21 | }
22 |
23 | @Get()
24 | findAll() {
25 | return this.usersService.findAll();
26 | }
27 |
28 | @Get(':id')
29 | findOne(@Param('id') id: string) {
30 | return this.usersService.findOne(id);
31 | }
32 |
33 | @Put(':id')
34 | update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
35 | return this.usersService.update(+id, updateUserDto);
36 | }
37 |
38 | @Delete(':id')
39 | remove(@Param('id') id: string) {
40 | return this.usersService.remove(id);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tests/src/apps/app-multi-database/app/users/users.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { UsersService } from './users.service';
3 | import { UsersController } from './users.controller';
4 |
5 | @Module({
6 | controllers: [UsersController],
7 | providers: [UsersService],
8 | })
9 | export class UsersModule {}
10 |
--------------------------------------------------------------------------------
/tests/src/apps/app-multi-database/app/users/users.service.ts:
--------------------------------------------------------------------------------
1 | import {
2 | BadRequestException,
3 | HttpException,
4 | HttpStatus,
5 | Injectable,
6 | NotFoundException,
7 | } from '@nestjs/common';
8 | import { Pool } from 'mysql2';
9 | import { InjectConnection } from '../../../../../../lib';
10 | import { CreateUserDto } from './dto/create-user.dto';
11 | import { UpdateUserDto } from './dto/update-user.dto';
12 |
13 | @Injectable()
14 | export class UsersService {
15 | constructor(
16 | @InjectConnection('db1Connection')
17 | private dbConnection: Pool,
18 | ) {}
19 |
20 | public async findAll(): Promise {
21 | const users = await this.dbConnection.query('SELECT * FROM users');
22 | const results = Object.assign([{}], users[0]);
23 |
24 | return results;
25 | }
26 |
27 | public async findOne(id: string): Promise {
28 | if (!id) {
29 | throw new BadRequestException();
30 | }
31 |
32 | const user = await this.dbConnection.query(
33 | 'SELECT * FROM users WHERE id=?',
34 | [id],
35 | );
36 |
37 | if (!user) {
38 | throw new NotFoundException();
39 | }
40 |
41 | const result = Object.assign([{}], user[0]);
42 |
43 | return result;
44 | }
45 |
46 | public async create(createUserDto: CreateUserDto): Promise {
47 | try {
48 | const user = await this.dbConnection.query(
49 | 'INSERT INTO users (firstName, lastName) VALUES (?, ?)',
50 | [createUserDto.firstName, createUserDto.lastName],
51 | );
52 | return user;
53 | } catch (err) {
54 | throw new HttpException(err, HttpStatus.BAD_REQUEST);
55 | }
56 | }
57 |
58 | public async update(id: number, updateUserDto: UpdateUserDto): Promise {
59 | try {
60 | const { firstName, lastName } = updateUserDto;
61 |
62 | const user = await this.dbConnection.query(
63 | 'UPDATE users SET firstName=?, lastName=? WHERE id=?',
64 | [firstName, lastName, id],
65 | );
66 | return user;
67 | } catch (err) {
68 | throw new HttpException(err, HttpStatus.BAD_REQUEST);
69 | }
70 | }
71 |
72 | public async remove(id: string): Promise {
73 | if (!id) {
74 | throw new BadRequestException();
75 | }
76 |
77 | const user = await this.dbConnection.query('DELETE FROM users WHERE id=?', [
78 | id,
79 | ]);
80 | return user;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/tests/src/apps/app-multi-database/data.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE users (
2 | id INT(11) AUTO_INCREMENT,
3 | firstName VARCHAR(100) NOT NULL,
4 | lastName VARCHAR(50) NOT NULL,
5 | PRIMARY KEY (id)
6 | );
7 |
8 | CREATE TABLE posts (
9 | id INT(11) AUTO_INCREMENT,
10 | title VARCHAR(100) NOT NULL,
11 | description VARCHAR(150) NOT NULL,
12 | PRIMARY KEY (id)
13 | );
14 |
--------------------------------------------------------------------------------
/tests/src/apps/app-multi-database/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 |
3 | services:
4 | db1:
5 | image: mysql:8.0
6 | restart: always
7 | environment:
8 | MYSQL_ROOT_PASSWORD: root
9 | MYSQL_DATABASE: test1
10 | ports:
11 | - "3306:3306"
12 | db2:
13 | image: mysql:8.0
14 | restart: always
15 | environment:
16 | MYSQL_ROOT_PASSWORD: root
17 | MYSQL_DATABASE: test2
18 | ports:
19 | - "3307:3306"
20 |
21 |
--------------------------------------------------------------------------------
/tests/src/apps/app-mysql/.env.example:
--------------------------------------------------------------------------------
1 | MYSQL_HOST="localhost"
2 | MYSQL_DB="test"
3 | MYSQL_PASSWORD="root"
4 | MYSQL_USER="root"
5 | MYSQL_PORT=3306
--------------------------------------------------------------------------------
/tests/src/apps/app-mysql/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { MysqlModule } from '../../../../../lib';
3 | import { UsersModule } from './users/users.module';
4 | import { ConfigModule, ConfigService } from '@nestjs/config';
5 |
6 | @Module({
7 | imports: [
8 | MysqlModule.forRootAsync({
9 | imports: [ConfigModule],
10 | inject: [ConfigService],
11 | useFactory: (config: ConfigService) => ({
12 | host: config.get('MYSQL_HOST', 'localhost'),
13 | database: config.get('MYSQL_DB', 'test'),
14 | password: config.get('MYSQL_PASSWORD', 'root'),
15 | user: config.get('MYSQL_USER', 'root'),
16 | port: config.get('MYSQL_PORT', 3306),
17 | }),
18 | }),
19 | UsersModule,
20 | ],
21 | })
22 | export class AppModule {}
23 |
--------------------------------------------------------------------------------
/tests/src/apps/app-mysql/app/main.ts:
--------------------------------------------------------------------------------
1 | import { NestFactory } from '@nestjs/core';
2 | import { AppModule } from './app.module';
3 | import {
4 | NestFastifyApplication,
5 | FastifyAdapter,
6 | } from '@nestjs/platform-fastify';
7 |
8 | async function bootstrap() {
9 | const app = await NestFactory.create(
10 | AppModule,
11 | new FastifyAdapter(),
12 | );
13 |
14 | await app.listen(3000);
15 | }
16 | bootstrap();
17 |
--------------------------------------------------------------------------------
/tests/src/apps/app-mysql/app/users/dto/create-user.dto.ts:
--------------------------------------------------------------------------------
1 | export class CreateUserDto {
2 | firstName: string;
3 | lastName: string;
4 | }
5 |
--------------------------------------------------------------------------------
/tests/src/apps/app-mysql/app/users/dto/update-user.dto.ts:
--------------------------------------------------------------------------------
1 | export class UpdateUserDto {
2 | firstName: string;
3 | lastName: string;
4 | }
5 |
--------------------------------------------------------------------------------
/tests/src/apps/app-mysql/app/users/interfaces/user.interface.ts:
--------------------------------------------------------------------------------
1 | export interface User {
2 | id: number;
3 | firstName: string;
4 | lastName: string;
5 | }
6 |
--------------------------------------------------------------------------------
/tests/src/apps/app-mysql/app/users/users.controller.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Controller,
3 | Get,
4 | Post,
5 | Body,
6 | Put,
7 | Param,
8 | Delete,
9 | } from '@nestjs/common';
10 | import { UsersService } from './users.service';
11 | import { CreateUserDto } from './dto/create-user.dto';
12 | import { UpdateUserDto } from './dto/update-user.dto';
13 |
14 | @Controller('users')
15 | export class UsersController {
16 | constructor(private readonly usersService: UsersService) {}
17 |
18 | @Post()
19 | create(@Body() createUserDto: CreateUserDto) {
20 | return this.usersService.create(createUserDto);
21 | }
22 |
23 | @Get()
24 | findAll() {
25 | return this.usersService.findAll();
26 | }
27 |
28 | @Get(':id')
29 | findOne(@Param('id') id: string) {
30 | return this.usersService.findOne(id);
31 | }
32 |
33 | @Put(':id')
34 | update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
35 | return this.usersService.update(+id, updateUserDto);
36 | }
37 |
38 | @Delete(':id')
39 | remove(@Param('id') id: string) {
40 | return this.usersService.remove(id);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tests/src/apps/app-mysql/app/users/users.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { UsersService } from './users.service';
3 | import { UsersController } from './users.controller';
4 |
5 | @Module({
6 | controllers: [UsersController],
7 | providers: [UsersService],
8 | })
9 | export class UsersModule {}
10 |
--------------------------------------------------------------------------------
/tests/src/apps/app-mysql/app/users/users.service.ts:
--------------------------------------------------------------------------------
1 | import {
2 | BadRequestException,
3 | HttpException,
4 | HttpStatus,
5 | Injectable,
6 | NotFoundException,
7 | } from '@nestjs/common';
8 | import { InjectClient } from '../../../../../../lib';
9 | import { CreateUserDto } from './dto/create-user.dto';
10 | import { UpdateUserDto } from './dto/update-user.dto';
11 | import { User } from './interfaces/user.interface';
12 | import { Connection } from 'mysql2';
13 |
14 | @Injectable()
15 | export class UsersService {
16 | constructor(@InjectClient() private readonly connection: Connection) {}
17 |
18 | public async findAll(): Promise {
19 | const users = await this.connection.query('SELECT * FROM users');
20 | const results = Object.assign([{}], users[0]);
21 |
22 | return results;
23 | }
24 |
25 | public async findOne(id: string): Promise {
26 | if (!id) {
27 | throw new BadRequestException();
28 | }
29 |
30 | const user = await this.connection.query('SELECT * FROM users WHERE id=?', [
31 | id,
32 | ]);
33 |
34 | if (!user) {
35 | throw new NotFoundException();
36 | }
37 | const result = Object.assign([{}], user[0]);
38 |
39 | return result;
40 | }
41 |
42 | public async create(createUserDto: CreateUserDto): Promise {
43 | try {
44 | const { firstName, lastName } = createUserDto;
45 | const user = await this.connection.query(
46 | 'INSERT INTO users (firstName, lastName) VALUES (?, ?)',
47 | [firstName, lastName],
48 | );
49 | return user;
50 | } catch (err) {
51 | throw new HttpException(err, HttpStatus.BAD_REQUEST);
52 | }
53 | }
54 |
55 | public async update(id: number, updateUserDto: UpdateUserDto): Promise {
56 | try {
57 | const { firstName, lastName } = updateUserDto;
58 |
59 | const user = await this.connection.query(
60 | 'UPDATE users SET firstName=?, lastName=? WHERE id=?',
61 | [firstName, lastName, id],
62 | );
63 | return user;
64 | } catch (err) {
65 | throw new HttpException(err, HttpStatus.BAD_REQUEST);
66 | }
67 | }
68 |
69 | public async remove(id: string): Promise {
70 | if (!id) {
71 | throw new BadRequestException();
72 | }
73 |
74 | const user = await this.connection.query('DELETE FROM users WHERE id=?', [
75 | id,
76 | ]);
77 | return user;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/tests/src/apps/app-mysql/data.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE users (
2 | id INT(11) AUTO_INCREMENT,
3 | firstName VARCHAR(100) NOT NULL,
4 | lastName VARCHAR(50) NOT NULL,
5 | PRIMARY KEY (id)
6 | );
7 |
--------------------------------------------------------------------------------
/tests/src/apps/app-mysql/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 |
3 | services:
4 | mysql:
5 | image: mysql:8.0
6 | restart: always
7 | environment:
8 | MYSQL_ROOT_USER: root
9 | MYSQL_ROOT_PASSWORD: root
10 | MYSQL_DATABASE: nest
11 | ports:
12 | - "3306:3306"
13 |
--------------------------------------------------------------------------------
/tests/src/async-class-options.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { MysqlOptionsFactory, MysqlModuleOptions, MysqlModule } from '../../lib';
3 | import { UsersModule } from './apps/app-mysql/app/users/users.module';
4 |
5 | class ConfigService implements MysqlOptionsFactory {
6 | createMysqlOptions(): MysqlModuleOptions {
7 | return {
8 | host: '127.0.0.1',
9 | database: 'test',
10 | password: 'root',
11 | user: 'root',
12 | port: 3306,
13 | };
14 | }
15 | }
16 |
17 | @Module({
18 | imports: [
19 | MysqlModule.forRootAsync({
20 | useClass: ConfigService,
21 | }),
22 | MysqlModule.forRoot({
23 | host: '127.0.0.1',
24 | database: 'test',
25 | password: 'root',
26 | user: 'root',
27 | port: 3306,
28 | }),
29 | UsersModule,
30 | ],
31 | })
32 | export class AsyncOptionsClassModule {}
33 |
--------------------------------------------------------------------------------
/tests/src/async-existing-options.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { UsersModule } from '../src/apps/app-mysql/app/users/users.module';
3 | import { MysqlModule } from '../../lib/mysql.module';
4 | import { MysqlModuleOptions, MysqlOptionsFactory } from '../../lib';
5 |
6 | class ConfigService implements MysqlOptionsFactory {
7 | createMysqlOptions(): MysqlModuleOptions {
8 | return {
9 | host: '127.0.0.1',
10 | database: 'test',
11 | password: 'root',
12 | user: 'root',
13 | port: 3306,
14 | };
15 | }
16 | }
17 |
18 | @Module({
19 | providers: [ConfigService],
20 | exports: [ConfigService],
21 | })
22 | class ConfigModule {}
23 |
24 | @Module({
25 | imports: [
26 | MysqlModule.forRootAsync({
27 | imports: [ConfigModule],
28 | useExisting: ConfigService,
29 | }),
30 | MysqlModule.forRoot({
31 | host: '127.0.0.1',
32 | database: 'test',
33 | password: 'root',
34 | user: 'root',
35 | port: 3306,
36 | }),
37 | UsersModule,
38 | ],
39 | })
40 | export class AsyncOptionsExistingModule {}
41 |
--------------------------------------------------------------------------------
/tests/src/database.module.ts:
--------------------------------------------------------------------------------
1 | import { DynamicModule, Module } from '@nestjs/common';
2 | import { MysqlModule } from '../../lib';
3 |
4 | @Module({})
5 | export class DatabaseModule {
6 | static async forRoot(): Promise {
7 | await new Promise((resolve) => setTimeout(resolve, 1000));
8 | return {
9 | module: DatabaseModule,
10 | imports: [
11 | MysqlModule.forRootAsync(
12 | {
13 | useFactory: () => ({
14 | name: 'db1Connection',
15 | host: '127.0.0.1',
16 | database: 'test',
17 | password: 'root',
18 | user: 'root',
19 | port: 3306,
20 | }),
21 | },
22 | 'db1Connection',
23 | ),
24 | MysqlModule.forRootAsync(
25 | {
26 | useFactory: () => ({
27 | name: 'db2Connection',
28 | host: '127.0.0.1',
29 | database: 'test',
30 | password: 'root',
31 | user: 'root',
32 | port: 3306,
33 | }),
34 | },
35 | 'db2Connection',
36 | ),
37 | ],
38 | };
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
4 | }
5 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "declaration": true,
5 | "removeComments": false,
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "target": "ES2021",
9 | "sourceMap": false,
10 | "outDir": "./dist",
11 | "baseUrl": "./",
12 | "noLib": false,
13 | "noImplicitAny": false,
14 | "skipLibCheck": true
15 | },
16 | "include": [
17 | "lib/**/*"
18 | ],
19 | "exclude": [
20 | "node_modules",
21 | "**/*.spec.ts",
22 | "tests",
23 | ]
24 | }
--------------------------------------------------------------------------------