Compare commits

..

2 Commits

Author SHA1 Message Date
Kamil Myśliwiec
e5612e0197 chore: merge 10.0.0 2023-06-12 10:58:30 +02:00
Kamil Myśliwiec
6c54448bf7 fix(fastify): validate middleware paths 2023-06-12 10:51:02 +02:00
1818 changed files with 653047 additions and 65449 deletions

View File

@@ -1,29 +1,13 @@
version: 2.1
parameters:
maintenance-node-version:
type: string
default: '20.18'
active-node-version:
type: string
default: '22.11'
current-node-version:
type: string
default: '24.1'
version: 2
aliases:
- &restore-cache
restore_cache:
key: dependency-cache-{{ checksum "package.json" }}
- &save-cache
save_cache:
key: dependency-cache-{{ checksum "package.json" }}
paths:
- ./node_modules
- &install-deps
run:
name: Install dependencies
command: npm install --legacy-peer-deps
command: npm ci --legacy-peer-deps
- &build-packages
run:
name: Build
@@ -33,71 +17,80 @@ aliases:
name: Test
command: npm run test
unit-tests-template: &unit-tests-template
working_directory: ~/nest
steps:
- checkout
- *restore-cache
- *install-deps
- *build-packages
- *run-unit-tests
jobs:
build:
working_directory: ~/nest
docker:
- image: cimg/node:<< pipeline.parameters.maintenance-node-version >>
- image: cimg/node:16.19
steps:
- checkout
- run:
name: Update NPM version
command: 'sudo npm install -g npm@^9'
- *restore-cache
- *install-deps
- *save-cache
- *build-packages
command: 'sudo npm install -g npm@^8'
- restore_cache:
key: dependency-cache-{{ checksum "package.json" }}
- run:
name: Install dependencies
command: npm ci --legacy-peer-deps
- save_cache:
key: dependency-cache-{{ checksum "package.json" }}
paths:
- ./node_modules
- run:
name: Build
command: npm run build
test:
parameters:
node-version:
type: string
test_node_16:
working_directory: ~/nest
docker:
- image: cimg/node:<< parameters.node-version >>
- image: cimg/node:16.19
steps:
- checkout
- *restore-cache
- *install-deps
- *build-packages
- when:
condition:
equal:
[
'<< parameters.node-version >>',
'<< pipeline.parameters.maintenance-node-version >>',
]
steps:
- run:
name: Test (coverage)
command: npm run test:cov
- run:
name: Collect coverage
command: npm run coverage
- store_artifacts:
path: coverage
- when:
condition:
not:
equal:
[
'<< parameters.node-version >>',
'<< pipeline.parameters.maintenance-node-version >>',
]
steps:
- *run-unit-tests
- run:
name: Test (coverage)
command: npm run test:cov
- run:
name: Collect coverage
command: npm run coverage
- store_artifacts:
path: coverage
test_node_18:
<<: *unit-tests-template
docker:
- image: cimg/node:18.14
test_node_19:
<<: *unit-tests-template
docker:
- image: cimg/node:19.8
lint:
working_directory: ~/nest
docker:
- image: cimg/node:<< pipeline.parameters.active-node-version >>
- image: circleci/node:16
steps:
- checkout
- *restore-cache
- *install-deps
- run:
name: Lint
command: npm run lint:ci
command: npm run lint
- run:
name: Lint commit
command: ./node_modules/.bin/commitlint-circle -c .commitlintrc.json
integration_tests:
working_directory: ~/nest
@@ -112,9 +105,9 @@ jobs:
- run:
name: Upgrade Node.js
command: |
nvm install << pipeline.parameters.maintenance-node-version >>
nvm install v16
node -v
nvm alias default << pipeline.parameters.maintenance-node-version >>
nvm alias default v16
- run:
name: Install Docker Compose
command: |
@@ -134,16 +127,28 @@ jobs:
name: Integration tests
command: npm run test:integration
codechecks_benchmarks:
working_directory: ~/nest
docker:
- image: cimg/node:16.19
steps:
- checkout
- *restore-cache
- *install-deps
- *build-packages
- run:
name: Install native wrk
command: .circleci/install-wrk.sh
- run:
name: Run codechecks with benchmarks
command: yarn codechecks:benchmarks
samples:
working_directory: ~/nest
docker:
- image: cimg/node:<< pipeline.parameters.maintenance-node-version >>
- image: redis:8-alpine
name: redis
- image: cimg/node:16.19
environment:
DISABLE_OPENCOLLECTIVE: 'true'
REDIS_HOST: redis
REDIS_PORT: 6379
- DISABLE_OPENCOLLECTIVE: true
steps:
- checkout
- *restore-cache
@@ -153,20 +158,16 @@ jobs:
command: npm run build:samples
workflows:
version: 2
build-and-test:
jobs:
- build
- test:
- test_node_16:
requires:
- build
- test_node_18:
requires:
- build
matrix:
parameters:
node-version:
[
'<< pipeline.parameters.maintenance-node-version >>',
'<< pipeline.parameters.active-node-version >>',
'<< pipeline.parameters.current-node-version >>',
]
- lint:
requires:
- build
@@ -176,3 +177,6 @@ workflows:
- samples:
requires:
- build
- codechecks_benchmarks:
requires:
- build

13
.circleci/install-wrk.sh Executable file
View File

@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")"
# based on https://medium.com/@felipedutratine/intelligent-benchmark-with-wrk-163986c1587f
cd /tmp/
sudo apt-get install build-essential libssl-dev git -y
git clone https://github.com/wg/wrk.git wrk
cd wrk
sudo make
# move the executable to somewhere in your PATH, ex:
sudo cp wrk /usr/local/bin

View File

@@ -37,8 +37,7 @@
"socket.io",
"ws",
"testing",
"websockets",
"release"
"websockets"
]
]
}

3
.eslintignore Normal file
View File

@@ -0,0 +1,3 @@
**/node_modules/**
*.d.ts
*.js

46
.eslintrc.js Normal file
View File

@@ -0,0 +1,46 @@
module.exports = {
root: true,
env: {
node: true,
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'prettier',
],
overrides: [
{
files: ['**/*.ts'],
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module',
},
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/ban-types': 'off',
},
},
{
files: ['**/*.spec.ts', 'integration/**/*.ts'],
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.spec.json',
sourceType: 'module',
},
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-empty-function': 'off',
},
}
]
};

View File

@@ -1,13 +1,12 @@
name: "\U0001F41B Bug Report"
description: "If something isn't working as expected \U0001F914"
labels: ["needs triage"]
type: bug
body:
- type: markdown
attributes:
value: |
## :warning: We use GitHub Issues to track bug reports, feature requests and regressions
If you are not sure that your issue is a bug, you could:
- read the [FAQ's common errors](https://docs.nestjs.com/faq/common-errors) page
@@ -39,19 +38,15 @@ body:
required: true
attributes:
label: "Minimum reproduction code"
description: "An URL to some Git repository/[StackBlitz](https://stackblitz.com/fork/github/nestjs/typescript-starter)/[CodeSandbox](https://codesandbox.io/s/github/nestjs/typescript-starter/tree/master) project that reproduces your issue. [Wtf is a minimum reproduction?](https://jmcdo29.github.io/wtf-is-a-minimum-reproduction)"
placeholder: "https://github.com/..."
description: |
An URL to some Git repository/[StackBlitz](https://stackblitz.com/fork/github/nestjs/typescript-starter)/[CodeSandbox](https://codesandbox.io/s/github/nestjs/typescript-starter/tree/master) project that reproduces your issue. [What is a minimum reproduction?](https://jmcdo29.github.io/wtf-is-a-minimum-reproduction)
> [!WARNING]
> We may close this Issue if we don't manage to reproduce the potential bug. [Read this](https://antfu.me/posts/why-reproductions-are-required) to understand why.
- type: textarea
attributes:
label: "Steps to reproduce"
description: |
How the issue manifests?
You could leave this blank if you already write this in your reproduction code
You could leave this blank if you alread write this in your reproduction code
placeholder: |
1. `npm ci`
2. `npm start:dev`
@@ -64,13 +59,46 @@ body:
label: "Expected behavior"
description: "A clear and concise description of what you expected to happened (or code)"
- type: markdown
attributes:
value: |
---
- type: checkboxes
validations:
required: true
attributes:
label: "Package"
description: |
Which package (or packages) do you think your issue is related to?
**Tip**: The first line of the stack trace can help you to figure out this
The package isn't listed below? Try to find its repository [here](https://github.com/orgs/nestjs/repositories) and open the issue there instead
options:
- label: "I don't know. Or some 3rd-party package"
- label: "<code>@nestjs/common</code>"
- label: "<code>@nestjs/core</code>"
- label: "<code>@nestjs/microservices</code>"
- label: "<code>@nestjs/platform-express</code>"
- label: "<code>@nestjs/platform-fastify</code>"
- label: "<code>@nestjs/platform-socket.io</code>"
- label: "<code>@nestjs/platform-ws</code>"
- label: "<code>@nestjs/testing</code>"
- label: "<code>@nestjs/websockets</code>"
- label: "Other (see below)"
- type: input
attributes:
label: "Other package"
description: "If your issue is related to some package that is not listed above nor under @nestjs org, write its name here"
- type: input
attributes:
label: "NestJS version"
description: |
Which exact version of `@nestjs/core` package are you using?
Which version of `@nestjs/core` are you using?
**Tip**: Make sure that all of yours `@nestjs/*` dependencies are in sync!
placeholder: "10.0.0"
placeholder: "8.1.3"
- type: textarea
validations:
@@ -89,7 +117,7 @@ body:
attributes:
label: "Node.js version"
description: "Which version of Node.js are you using?"
placeholder: "24.0.0"
placeholder: "14.17.6"
- type: checkboxes
validations:

View File

@@ -1,13 +1,12 @@
name: "\U0001F680 Feature Request"
description: "I have a suggestion \U0001F63B!"
labels: ["type: enhancement :wolf:", "needs triage"]
type: feature
body:
- type: markdown
attributes:
value: |
## :warning: We use GitHub Issues to track bug reports, feature requests and regressions
If you are not sure that your issue is a bug, you could:
- read the [FAQ's common errors](https://docs.nestjs.com/faq/common-errors) page

View File

@@ -1,13 +1,12 @@
name: "\U0001F4A5 Regression"
description: "Report an unexpected while upgrading your Nest application!"
labels: ["type: bug :sob:", "needs triage"]
type: bug
body:
- type: markdown
attributes:
value: |
## :warning: We use GitHub Issues to track bug reports, feature requests and regressions
If you are not sure that your issue is a bug, you could:
- read the [FAQ's common errors](https://docs.nestjs.com/faq/common-errors) page

View File

@@ -2,7 +2,6 @@ title: "perf: "
name: "\U0001F525 Suggestion for Improving Performance"
description: "I have a suggestion that might improve the performance of Nest \U00002728"
labels: ["type: enhancement :wolf:", "needs triage"]
type: task
body:
- type: checkboxes
attributes:

View File

@@ -21,7 +21,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v3
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
@@ -34,7 +34,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
uses: github/codeql-action/init@v2
with:
queries: +security-extended
# Override language selection by uncommenting this and choosing your languages
@@ -44,7 +44,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v4
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -58,4 +58,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
uses: github/codeql-action/analyze@v2

10
.gitignore vendored
View File

@@ -1,6 +1,3 @@
packages/*/package-lock.json
sample/**/package-lock.json
# dependencies
node_modules/
@@ -9,9 +6,6 @@ node_modules/
/.awcache
/.vscode
/.devcontainer
/.classpath
/.project
/.settings
*.code-workspace
# Vim
@@ -34,7 +28,6 @@ yarn-error.log
/packages/**/.npmignore
/packages/**/LICENSE
*.tsbuildinfo
/.nx/workspace-data
# example
/quick-start
@@ -51,5 +44,4 @@ yarn-error.log
build/config\.gypi
.npmrc
pnpm-lock.yaml
/.history
pnpm-lock.yaml

View File

@@ -1 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no-install commitlint --edit $1

View File

@@ -1 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged

View File

@@ -1,5 +1,3 @@
packages/**/*.d.ts
packages/**/*.js
.nyc_output
/.nx/workspace-data

View File

@@ -25,7 +25,7 @@ Stack Overflow is a much better place to ask questions since:
<!-- - there are thousands of people willing to help on Stack Overflow [maybe one day] -->
- questions and answers stay available for public viewing so your question / answer might help someone else.
- questions and answers stay available for public viewing so your question / answer might help someone else
- Stack Overflow's voting system assures that the best answers are prominently visible.
To save your and our time, we will systematically close all issues that are requests for general support and redirect people to Stack Overflow.
@@ -116,6 +116,7 @@ We cannot accept code without this.
1. In GitHub, send a pull request to `nestjs:master`.
- If we suggest changes then:
- Make the required updates.
- Re-run the Nest test suites to ensure tests are still passing.
- Rebase your branch and force push to your GitHub repository (this will update your Pull Request):
@@ -158,12 +159,12 @@ from the main (upstream) repository:
## <a name="development"></a> Development Setup
You will need [Node.js](https://nodejs.org) version >= 20.
You will need [Node.js](https://nodejs.org) version >= 10.13.0 (except for v13).
1. After cloning the repo, run:
```bash
$ npm ci --legacy-peer-deps # (or yarn install)
$ npm ci # (or yarn install)
```
2. In order to prepare your environment run `prepare.sh` shell script:
@@ -321,10 +322,8 @@ changes to be accepted, the CLA must be signed. It's a quick process, we promise
<!-- [coc]: https://github.com/angular/code-of-conduct/blob/master/CODE_OF_CONDUCT.md -->
[commit-message-format]: https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#
<!-- [individual-cla]: http://code.google.com/legal/individual-cla-v1.0.html -->
<!-- [corporate-cla]: http://code.google.com/legal/corporate-cla-v1.0.html -->
[dev-doc]: https://github.com/nestjs/nest/blob/master/docs/DEVELOPER.md
[github]: https://github.com/nestjs/nest
[stackoverflow]: https://stackoverflow.com/questions/tagged/nestjs

View File

@@ -1,6 +1,6 @@
(The MIT License)
Copyright (c) 2017-present Kamil Mysliwiec <https://kamilmysliwiec.com>
Copyright (c) 2017-2023 Kamil Mysliwiec <https://kamilmysliwiec.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

165
Readme.md
View File

@@ -1,5 +1,5 @@
<p align="center">
<a href="https://nestjs.com/" target="_blank"><img src="https://nestjs.com/img/logo-small.svg" width="120" alt="Nest Logo" /></a>
<a href="https://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="120" alt="Nest Logo" /></a>
</p>
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
@@ -8,7 +8,7 @@
<p align="center">A progressive <a href="https://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
<p align="center">
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
<a href="https://github.com/nestjs/nest/blob/master/LICENSE" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
@@ -23,21 +23,21 @@
## Description
Nest is a framework for building efficient, scalable <a href="https://nodejs.org" target="_blank">Node.js</a> server-side applications. It uses modern JavaScript, is built with <a href="https://www.typescriptlang.org" target="_blank">TypeScript</a> (preserves compatibility with pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).
Nest is a framework for building efficient, scalable <a href="http://nodejs.org" target="_blank">Node.js</a> server-side applications. It uses modern JavaScript, is built with <a href="http://www.typescriptlang.org" target="_blank">TypeScript</a> (preserves compatibility with pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).
<p>Under the hood, Nest makes use of <a href="https://expressjs.com/" target="_blank">Express</a>, but also provides compatibility with a wide range of other libraries, like <a href="https://github.com/fastify/fastify" target="_blank">Fastify</a>, allowing for easy use of the myriad of third-party plugins which are available.</p>
<p>Under the hood, Nest makes use of <a href="https://expressjs.com/" target="_blank">Express</a>, but also, provides compatibility with a wide range of other libraries, like e.g. <a href="https://github.com/fastify/fastify" target="_blank">Fastify</a>, allowing for easy use of the myriad third-party plugins which are available.</p>
## Philosophy
<p>In recent years, thanks to Node.js, JavaScript has become the “lingua franca” of the web for both front and backend applications, giving rise to awesome projects like <a href="https://angular.io/" target="_blank">Angular</a>, <a href="https://github.com/facebook/react" target="_blank">React</a>, and <a href="https://github.com/vuejs/vue" target="_blank">Vue</a>, which improve developer productivity and enable the construction of fast, testable, and extensible frontend applications. However, on the server-side, while there are a lot of superb libraries, helpers, and tools for Node, none of them effectively solve the main problem - the architecture.</p>
<p>Nest aims to provide an application architecture out of the box which allows for effortless creation of highly testable, scalable, and loosely coupled and easily maintainable applications. The architecture is heavily inspired by Angular.</p>
<p>In recent years, thanks to Node.js, JavaScript has become the “lingua franca” of the web for both front and backend applications, giving rise to awesome projects like <a href="https://angular.io/" target="_blank">Angular</a>, <a href="https://github.com/facebook/react" target="_blank">React</a> and <a href="https://github.com/vuejs/vue" target="_blank">Vue</a> which improve developer productivity and enable the construction of fast, testable, extensible frontend applications. However, on the server-side, while there are a lot of superb libraries, helpers and tools for Node, none of them effectively solve the main problem - the architecture.</p>
<p>Nest aims to provide an application architecture out of the box which allows for effortless creation of highly testable, scalable, loosely coupled and easily maintainable applications. The architecture is heavily inspired by Angular.</p>
## Getting started
- To check out the [guide](https://docs.nestjs.com), visit [docs.nestjs.com](https://docs.nestjs.com). :books:
- 要查看中文 [指南](readme_zh.md), 请访问 [docs.nestjs.cn](https://docs.nestjs.cn). :books:
- [가이드](readme_kr.md) 문서는 [docs.nestjs.com](https://docs.nestjs.com)에서 확인하실 수 있습니다. :books:
- [ガイド](readme_jp.md)は [docs.nestjs.com](https://docs.nestjs.com)でご確認ください。 :books:
* To check out the [guide](https://docs.nestjs.com), visit [docs.nestjs.com](https://docs.nestjs.com). :books:
* 要查看中文 [指南](readme_zh.md), 请访问 [docs.nestjs.cn](https://docs.nestjs.cn). :books:
* [가이드](readme_kr.md) 문서는 [docs.nestjs.com](https://docs.nestjs.com)에서 확인하실 수 있습니다. :books:
* [ガイド](readme_jp.md)は [docs.nestjs.com](https://docs.nestjs.com)でご確認ください。 :books:
## Questions
@@ -49,87 +49,96 @@ Please make sure to read the [Issue Reporting Checklist](https://github.com/nest
## Consulting
With official support, you can get expert help straight from the Nest core team. We provide dedicated technical support, migration strategies, advice on best practices (and design decisions), PR reviews, and team augmentation. Read more about [support here](https://enterprise.nestjs.com).
With official support, you can get expert help straight from Nest core team. We provide dedicated technical support, migration strategies, advice on best practices (and design decisions), PR reviews, and team augmentation. Read more about [support here](https://enterprise.nestjs.com).
## Support
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support from the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
#### Principal Sponsors
<table style="text-align:center;">
<tr>
<td><a href="https://trilon.io" target="_blank"><img src="https://nestjs.com/img/trilon.svg" width="200" valign="middle" /></a></td>
<td><a href="https://microsoft.com/" target="_blank"><img src="https://nestjs.com/img/logos/microsoft-logo.png" width="180" valign="middle" /></a></td>
<td><a href="https://mojam.co" target="_blank"><img src="https://nestjs.com/img/logos/mojam-logo.png" width="80" valign="middle" /></a></td>
<td><a href="https://valor-software.com/" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/valor-software.png" width="170" valign="middle" /></a></td>
<td><a href="https://serpapi.com/" target="_blank"><img src="https://nestjs.com/img/logos/serpapi-logo.png" width="150" valign="middle" /></a></td>
</tr>
</table>
<table style="text-align:center;"><tr>
<td><a href="https://trilon.io" target="_blank"><img src="https://nestjs.com/img/trilon.svg" width="200" valign="middle" /></a></td>
<td><a href="https://www.redhat.com" target="_blank"><img src="https://nestjs.com/img/red-hat-logo.svg" width="200" valign="middle" /></a></td>
<td>
<a href="https://github.com/Sanofi-IADC" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/sanofi.png" width="180" valign="middle" /></a></td>
<td>
<a href="https://nx.dev" target="_blank"><img src="https://nestjs.com/img/nx-logo.png" height="45" valign="middle" /></a></td>
<td>
<a href="https://valor-software.com/" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/valor-software.png" width="170" valign="middle" /></a></td><td>
<a href="https://amplication.com/" target="_blank"><img src="https://nestjs.com/img/amplication-logo.svg" width="190" valign="middle" /></a></td>
</tr></table>
#### Gold Sponsors
<table style="text-align:center;">
<tr>
<td><a href="https://www.redhat.com" target="_blank"><img src="https://nestjs.com/img/logos/red-hat-logo.svg" width="200" valign="middle" /></a></td>
<td><a href="https://github.com/Sanofi-IADC" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/sanofi.png" width="180" valign="middle" /></a></td>
<td><a href="https://nx.dev" target="_blank"><img src="https://nestjs.com/img/logos/nx-logo.png" height="45" valign="middle" /></a></td>
<td><a href="https://intrinsic.ventures/" target="_blank"><img src="https://nestjs.com/img/logos/intrinisic-logo.png" width="210" valign="middle" /></a></td>
<td><a href="https://jetbrains.com/" target="_blank"><img src="https://nestjs.com/img/logos/jetbrains-logo.svg" width="90" valign="middle" /></a></td>
</tr>
<tr>
<td><a href="https://snyk.co/nestjs" target="_blank"><img src="https://nestjs.com/img/logos/snyk-logo-black.png" width="185" valign="middle" /></a></td>
<td><a href="https://www.movavi.com/imovie-for-windows.html" target="_blank"><img src="https://nestjs.com/img/logos/movavi-logo.svg" width="105" valign="middle" /></a></td>
</tr>
</table>
<table style="text-align:center;"><tr><td>
<a href="https://weld.app/" target="_blank"><img src="https://nestjs.com/img/weld-logo.svg" width="140" valign="middle" /></a></td>
<td>
<a href="https://intrinsic.ventures/" target="_blank"><img src="https://nestjs.com/img/intrinisic-logo.png" width="210" valign="middle" /></a></td>
<td>
<a href="https://jetbrains.com/" target="_blank"><img src="https://nestjs.com/img/jetbrains-logo.svg" width="110" valign="middle" /></a></td><td>
<a href="https://snyk.co/nestjs" target="_blank"><img src="https://nestjs.com/img/snyk-logo-black.png" width="185" valign="middle" /></a></td><td>
<a href="https://fuseautotech.com/" target="_blank"><img src="https://nestjs.com/img/fuse-logo.svg" width="105" valign="middle" /></a></td>
<td>
<a href="https://ridicorp.com/career/" target="_blank"><img src="https://nestjs.com/img/ridi-logo.svg" width="105" valign="middle" /></a></td><td>
<a href="https://www.movavi.com/imovie-for-windows.html" target="_blank"><img src="https://nestjs.com/img/movavi-logo.svg" width="105" valign="middle" /></a></td></</tr></table>
#### Silver Sponsors
<table style="text-align:center;">
<tr>
<td><a href="https://www.mercedes-benz.com/" target="_blank"><img src="https://nestjs.com/img/logos/mercedes-logo.png" width="100" valign="middle" /></a></td>
<td><a href="https://www.dinii.jp/" target="_blank"><img src="https://nestjs.com/img/logos/dinii-logo.png" width="65" valign="middle" /></a></td>
<td><a href="https://handsontable.com/docs/react-data-grid/?utm_source=NestJS_GH&utm_medium=sponsorship&utm_campaign=library_sponsorship_2024" target="_blank"><img src="https://nestjs.com/img/logos/handsontable-dark-logo.svg#2" width="150" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.itflashcards.com/" target="_blank"><img src="https://nestjs.com/img/logos/it_flashcards-logo.png" width="170" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://arcjet.com/?ref=nestjs" target="_blank"><img src="https://nestjs.com/img/logos/arcjet-logo.svg" width="170" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://crawljobs.com" target="_blank"><img src="https://nestjs.com/img/logos/crawljobs-logo.svg" width="130" valign="middle" /></a></td>
</tr><tr>
<td align="center" valign="middle"><a href="https://pandektes.com" target="_blank"><img src="https://nestjs.com/img/logos/pandektes-logo.png" width="65" valign="middle" /></a></td>
</tr>
<table style="text-align:center;"><tr><td>
<a href="https://neoteric.eu/" target="_blank"><img src="https://nestjs.com/img/neoteric-cut.png" width="120" valign="middle" /></a> </td><td>
<a href="http://gojob.com" target="_blank"><img src="http://nestjs.com/img/gojob-logo.png" valign="middle" width="100" /></a> </td><td>
<a href="https://www.myleodsc.com/" target="_blank"><img src="https://nestjs.com/img/myleo-logo.png" width="180" valign="middle" /></td><td>
<a href="https://careers.meetdandy.com/?gh_src=063ba61e3us" target="_blank"><img src="https://nestjs.com/img/dandy-roles-logo.svg" width="150" valign="middle" /></td><td>
<a href="https://www.castlecraft.in" target="_blank"><img src="https://nestjs.com/img/castlecraft-logo.png" width="150" valign="middle" /></td>
<td><a href="https://www.tinystacks.com" target="_blank"><img src="https://nestjs.com/img/tinystacks-logo.png#1" width="140" valign="middle" /></td>
<td><a href="https://n.inc" target="_blank"><img src="https://nestjs.com/img/n-inc-logo.svg" width="120" valign="middle" /></td></tr><tr>
<td><a href="https://bilberrry.com/" target="_blank"><img src="https://nestjs.com/img/bilberrry-logo.svg" width="180" valign="middle" /></td>
<td><a href="https://ipinfo.ai/" target="_blank"><img src="https://nestjs.com/img/ipinfo-logo.png" width="130" valign="middle" /></td>
<td><a href="https://chax.at" target="_blank"><img src="https://nestjs.com/img/chaxat-logo.png" width="100" valign="middle" /></td></tr>
</table>
#### Sponsors
<table>
<tr>
<td align="center" valign="middle"><a href="https://www.swingdev.io" target="_blank"><img src="https://nestjs.com/img/logos/swingdev-logo.svg#1" width="110" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.novologic.com/" target="_blank"><img src="https://nestjs.com/img/logos/novologic.png" width="110" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://mantro.net/" target="_blank"><img src="https://nestjs.com/img/logos/mantro-logo.svg" width="95" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://triplebyte.com/" target="_blank"><img src="https://nestjs.com/img/logos/triplebyte.png" width="107" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://nearpod.com/" target="_blank"><img src="https://nestjs.com/img/logos/nearpod-logo.svg" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://genuinebee.com/" target="_blank"><img src="https://nestjs.com/img/logos/genuinebee.svg" width="97" valign="middle" /></a></td>
</tr>
<tr>
<td align="center" valign="middle"><a href="https://vpn-review.com/vpn-for-torrenting" target="_blank"><img src="https://nestjs.com/img/logos/vpn-review-logo.png" width="85" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://lambda-it.ch/" target="_blank"><img src="https://nestjs.com/img/logos/lambda-it-logo.svg" width="115" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://rocketech.it/cases/?utm_source=google&utm_medium=badge&utm_campaign=nestjs" target="_blank"><img src="https://nestjs.com/img/logos/rocketech-logo.svg" width="110" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.anonymistic.com/" target="_blank"><img src="https://nestjs.com/img/logos/anonymistic-logo.png" width="125" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.naologic.com/" target="_blank"><img src="https://nestjs.com/img/logos/naologic-logo.svg" width="125" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://triplecore.io" target="_blank"><img src="https://nestjs.com/img/logos/triplecore-logo.svg" width="50" valign="middle" /></a></td>
</tr>
<tr>
<td align="center" valign="middle"><a href="https://polygon-software.ch/" target="_blank"><img src="https://nestjs.com/img/logos/polygon-logo.svg" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://boringowl.io/" target="_blank"><img src="https://nestjs.com/img/logos/boringowl-logo.svg" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://nordbot.app/" target="_blank"><img src="https://nestjs.com/img/logos/nordbot-logo.png" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://doppio.sh/" target="_blank"><img src="https://nestjs.com/img/logos/dopiosh-logo.png" width="50" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.hingehealth.com/" target="_blank"><img src="https://nestjs.com/img/logos/hinge-health-logo.svg" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.tripoffice.com/" target="_blank"><img src="https://nestjs.com/img/logos/tripoffice-logo.png" width="140" valign="middle" /></a></td>
</tr>
<tr>
<td align="center" valign="middle"><a href="https://solcellsforetag.se/" target="_blank"><img src="https://nestjs.com/img/logos/solcellsforetag-logo.svg" width="140" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.route4me.com/" target="_blank"><img src="https://nestjs.com/img/logos/route4me-logo.svg" width="100" valign="middle" /></a></td>
</tr>
</table>
<table><tr><td align="center" valign="middle">
<a href="https://www.swingdev.io" target="_blank"><img src="https://nestjs.com/img/swingdev-logo.svg#1" width="110" valign="middle" /> </a></td><td align="center" valign="middle">
<a href="https://www.novologic.com/" target="_blank"><img src="https://nestjs.com/img/novologic.png" width="110" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://ever.co/" target="_blank"><img src="https://nestjs.com/img/ever-logo.png" width="72" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://blokt.com" target="_blank"><img src="https://nestjs.com/img/blokt-logo.png" width="120" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="http://architectnow.net/" target="_blank"><img src="https://nestjs.com/img/architectnow.png" width="125" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://quander.io/" target="_blank"><img src="https://nestjs.com/img/quander.png" width="100" valign="middle" /></a> </td></tr><tr><td align="center" valign="middle">
<a href="https://mantro.net/" target="_blank"><img src="https://nestjs.com/img/mantro-logo.svg" width="95" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://triplebyte.com/" target="_blank"><img src="https://nestjs.com/img/triplebyte.png" width="107" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://reposit.co.uk/" target="_blank"><img src="https://nestjs.com/img/reposit-logo.png" width="71" valign="middle" /></a></td><td align="center" valign="middle">
<a href="https://nearpod.com/" target="_blank"><img src="https://nestjs.com/img/nearpod-logo.svg" width="100" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://clay.global/" target="_blank"><img src="https://nestjs.com/img/clay-logo.svg" width="75" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://firesticktricks.com" target="_blank"><img src="https://nestjs.com/img/firesticktricks-logo.png" width="120" valign="middle" /></a></td></tr><tr><td align="center" valign="middle">
<a href="https://www.codeguesser.co.uk" target="_blank"><img src="https://nestjs.com/img/codeguesser-logo.svg" width="120" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://f-a.nz/" target="_blank"><img src="https://nestjs.com/img/franz.svg" width="80" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://sparkfabrik.com/" target="_blank"><img src="https://nestjs.com/img/sparkfabrik-logo.png" width="120" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://www.thebigphonestore.co.uk/" target="_blank"><img src="https://nestjs.com/img/the-big-phone-store-logo.png" width="65" valign="middle" /></a></td>
<td align="center" valign="middle">
<a href="https://genuinebee.com/" target="_blank"><img src="https://nestjs.com/img/genuinebee.svg" width="97" valign="middle" /></a> </td>
<td align="center" valign="middle"><a href="https://sanyodigital.com/" target="_blank"><img src="https://nestjs.com/img/sanyo-digital.png" width="130" valign="middle" /></a></td></tr><tr><td align="center" valign="middle"><a href="https://vpn-review.com/vpn-for-torrenting" target="_blank"><img src="https://nestjs.com/img/vpn-review-logo.png" width="85" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://lambda-it.ch/" target="_blank"><img src="https://nestjs.com/img/lambda-it-logo.svg" width="115" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://pickwriters.com/top-10-translation-services" target="_blank"><img src="https://nestjs.com/img/pickwriters-logo.png" width="40" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://thewordpoint.com/services/localization" target="_blank"><img src="https://nestjs.com/img/thewordpoint-logo.png" width="40" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://meercode.io/" target="_blank"><img src="https://nestjs.com/img/meercode-logo.png" width="60" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.najlepszeplatformyforex.pl/blog/broker-xtb/" target="_blank"><img src="https://nestjs.com/img/npf-logo.jpg" width="200" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://thestandarddaily.com/" target="_blank"><img src="https://nestjs.com/img/the-standard-daily-logo.png" width="180" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://houseofangular.io/" target="_blank"><img src="https://nestjs.com/img/house-of-angular.png" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://rocketech.it/cases/?utm_source=google&utm_medium=badge&utm_campaign=nestjs" target="_blank"><img src="https://nestjs.com/img/rocketech-logo.svg" width="110" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.bystored.com/" target="_blank"><img src="https://nestjs.com/img/stored-logo.svg" width="110" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://studyclerk.com/pay-for-research-paper" target="_blank"><img src="https://nestjs.com/img/studyclerk-logo.png" width="125" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://xyndata.com" target="_blank"><img src="https://nestjs.com/img/xyndata-logo.png" width="125" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://www.anonymistic.com/" target="_blank"><img src="https://nestjs.com/img/anonymistic-logo.png" width="125" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.naologic.com/" target="_blank"><img src="https://nestjs.com/img/naologic-logo.svg" width="125" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://messaged.com/" target="_blank"><img src="https://nestjs.com/img/messaged-logo.png" width="50" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://triplecore.io" target="_blank"><img src="https://nestjs.com/img/triplecore-logo.svg" width="50" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://klqc.de" target="_blank"><img src="https://nestjs.com/img/klcqcl-logo.png" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://thecasinowizard.com/bonuses/no-deposit-bonuses/" target="_blank"><img src="https://nestjs.com/img/casinowizard-logo.png" width="120" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://polygon-software.ch/" target="_blank"><img src="https://nestjs.com/img/polygon-logo.svg" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://themobilereality.com/" target="_blank"><img src="https://nestjs.com/img/mobile-reality-logo.png" width="45" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://boringowl.io/" target="_blank"><img src="https://nestjs.com/img/boringowl-logo.svg" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.mobilefactory.jp/" target="_blank"><img src="https://nestjs.com/img/mobilefactory-logo.png" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://nordbot.app/" target="_blank"><img src="https://nestjs.com/img/nordbot-logo.png" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://doppio.sh/" target="_blank"><img src="https://nestjs.com/img/dopiosh-logo.png" width="50" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://www.hingehealth.com/" target="_blank"><img src="https://nestjs.com/img/hinge-health-logo.svg" width="100" valign="middle" /></a></td>
</tr></table>
## Backers
@@ -137,9 +146,9 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
## Stay in touch
- Author - [Kamil Myśliwiec](https://x.com/kammysliwiec)
- Website - [https://nestjs.com](https://nestjs.com/)
- X - [@nestframework](https://x.com/nestframework)
* Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
* Website - [https://nestjs.com](https://nestjs.com/)
* Twitter - [@nestframework](https://twitter.com/nestframework)
## License

88
benchmarks/all_output.txt Normal file
View File

@@ -0,0 +1,88 @@
-----------------------
express
-----------------------
Running 10s test @ http://localhost:3000
1024 connections
┌─────────┬───────┬───────┬───────┬────────┬──────────┬──────────┬────────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼───────┼───────┼───────┼────────┼──────────┼──────────┼────────┤
│ Latency │ 55 ms │ 58 ms │ 91 ms │ 138 ms │ 61.88 ms │ 23.95 ms │ 747 ms │
└─────────┴───────┴───────┴───────┴────────┴──────────┴──────────┴────────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬──────────┬─────────┬─────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼─────────┼─────────┼─────────┼─────────┼──────────┼─────────┼─────────┤
│ Req/Sec │ 8407 │ 8407 │ 17407 │ 17743 │ 16454.41 │ 2716.94 │ 8402 │
├───────────┼─────────┼─────────┼─────────┼─────────┼──────────┼─────────┼─────────┤
│ Bytes/Sec │ 1.81 MB │ 1.81 MB │ 3.74 MB │ 3.81 MB │ 3.54 MB │ 584 kB │ 1.81 MB │
└───────────┴─────────┴─────────┴─────────┴─────────┴──────────┴─────────┴─────────┘
Req/Bytes counts sampled once per second.
165k requests in 10.17s, 35.4 MB read
-----------------------
nest (with "@nestjs/platform-express")
-----------------------
Running 10s test @ http://localhost:3000
1024 connections
┌─────────┬───────┬───────┬───────┬───────┬──────────┬──────────┬────────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼───────┼───────┼───────┼───────┼──────────┼──────────┼────────┤
│ Latency │ 61 ms │ 64 ms │ 71 ms │ 94 ms │ 65.44 ms │ 17.35 ms │ 325 ms │
└─────────┴───────┴───────┴───────┴───────┴──────────┴──────────┴────────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬────────┬─────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼────────┼─────────┤
│ Req/Sec │ 14183 │ 14183 │ 15767 │ 15991 │ 15640 │ 501.13 │ 14182 │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼────────┼─────────┤
│ Bytes/Sec │ 3.06 MB │ 3.06 MB │ 3.41 MB │ 3.45 MB │ 3.38 MB │ 108 kB │ 3.06 MB │
└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴────────┴─────────┘
Req/Bytes counts sampled once per second.
156k requests in 10.24s, 33.8 MB read
-----------------------
fastify
-----------------------
Running 10s test @ http://localhost:3000
1024 connections
┌─────────┬───────┬───────┬───────┬───────┬──────────┬──────────┬─────────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼───────┼───────┼───────┼───────┼──────────┼──────────┼─────────┤
│ Latency │ 27 ms │ 30 ms │ 39 ms │ 78 ms │ 31.62 ms │ 26.59 ms │ 1232 ms │
└─────────┴───────┴───────┴───────┴───────┴──────────┴──────────┴─────────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ Req/Sec │ 19935 │ 19935 │ 33247 │ 34111 │ 32030.4 │ 4103.84 │ 19931 │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ Bytes/Sec │ 3.03 MB │ 3.03 MB │ 5.05 MB │ 5.19 MB │ 4.87 MB │ 624 kB │ 3.03 MB │
└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘
Req/Bytes counts sampled once per second.
320k requests in 10.18s, 48.7 MB read
-----------------------
nest (with "@nestjs/platform-fastify")
-----------------------
Running 10s test @ http://localhost:3000
1024 connections
┌─────────┬───────┬───────┬───────┬───────┬──────────┬──────────┬────────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼───────┼───────┼───────┼───────┼──────────┼──────────┼────────┤
│ Latency │ 31 ms │ 33 ms │ 38 ms │ 52 ms │ 34.41 ms │ 11.73 ms │ 245 ms │
└─────────┴───────┴───────┴───────┴───────┴──────────┴──────────┴────────┘
┌───────────┬─────────┬─────────┬────────┬─────────┬─────────┬─────────┬─────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼─────────┼─────────┼────────┼─────────┼─────────┼─────────┼─────────┤
│ Req/Sec │ 24911 │ 24911 │ 30031 │ 30335 │ 29470.4 │ 1564.48 │ 24907 │
├───────────┼─────────┼─────────┼────────┼─────────┼─────────┼─────────┼─────────┤
│ Bytes/Sec │ 3.81 MB │ 3.81 MB │ 4.6 MB │ 4.64 MB │ 4.51 MB │ 239 kB │ 3.81 MB │
└───────────┴─────────┴─────────┴────────┴─────────┴─────────┴─────────┴─────────┘
Req/Bytes counts sampled once per second.
295k requests in 10.17s, 45.1 MB read

7
benchmarks/express.js Normal file
View File

@@ -0,0 +1,7 @@
'use strict';
const express = require('express');
const app = express();
app.get('/', async (req, res) => res.send('Hello world'));
app.listen(3000);

7
benchmarks/fastify.js Normal file
View File

@@ -0,0 +1,7 @@
'use strict';
const fastify = require('fastify')();
fastify.get('/', async (req, reply) => reply.send('Hello world'));
fastify.listen({
port: 3000
});

View File

@@ -0,0 +1,14 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
const core_1 = require('@nestjs/core');
const fastify_platform_1 = require('@nestjs/platform-fastify');
const app_module_1 = require('./nest/app.module');
core_1.NestFactory.create(
app_module_1.AppModule,
new fastify_platform_1.FastifyAdapter(),
{
logger: false,
bodyParser: false,
},
).then(app => app.listen(3000));
//# sourceMappingURL=main.js.map

9
benchmarks/nest.js Normal file
View File

@@ -0,0 +1,9 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
const core_1 = require('@nestjs/core');
const app_module_1 = require('./nest/app.module');
core_1.NestFactory.create(app_module_1.AppModule, {
logger: false,
bodyParser: false,
}).then(app => app.listen(3000));
//# sourceMappingURL=main.js.map

3
benchmarks/nest/app.controller.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
export declare class AppController {
root(): string;
}

View File

@@ -0,0 +1,47 @@
'use strict';
var __decorate =
(this && this.__decorate) ||
function(decorators, target, key, desc) {
var c = arguments.length,
r =
c < 3
? target
: desc === null
? (desc = Object.getOwnPropertyDescriptor(target, key))
: desc,
d;
if (typeof Reflect === 'object' && typeof Reflect.decorate === 'function')
r = Reflect.decorate(decorators, target, key, desc);
else
for (var i = decorators.length - 1; i >= 0; i--)
if ((d = decorators[i]))
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata =
(this && this.__metadata) ||
function(k, v) {
if (typeof Reflect === 'object' && typeof Reflect.metadata === 'function')
return Reflect.metadata(k, v);
};
Object.defineProperty(exports, '__esModule', { value: true });
const common_1 = require('@nestjs/common');
let AppController = class AppController {
root() {
return 'Hello world!';
}
};
__decorate(
[
common_1.Get(),
__metadata('design:type', Function),
__metadata('design:paramtypes', []),
__metadata('design:returntype', String),
],
AppController.prototype,
'root',
null,
);
AppController = __decorate([common_1.Controller()], AppController);
exports.AppController = AppController;
//# sourceMappingURL=app.controller.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"app.controller.js","sourceRoot":"","sources":["../src/app.controller.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,2CAAiD;AAGjD,IAAa,aAAa,GAA1B,MAAa,aAAa;IAExB,IAAI;QACF,OAAO,cAAc,CAAA;IACvB,CAAC;CACF,CAAA;AAHC;IADC,YAAG,EAAE;;;;yCAGL;AAJU,aAAa;IADzB,mBAAU,EAAE;GACA,aAAa,CAKzB;AALY,sCAAa"}

1
benchmarks/nest/app.module.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export declare class AppModule {}

View File

@@ -0,0 +1,20 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
const common_1 = require("@nestjs/common");
const app_controller_1 = require("./app.controller");
let AppModule = class AppModule {
};
AppModule = __decorate([
common_1.Module({
imports: [],
controllers: [app_controller_1.AppController],
})
], AppModule);
exports.AppModule = AppModule;
//# sourceMappingURL=app.module.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"app.module.js","sourceRoot":"","sources":["../src/app.module.ts"],"names":[],"mappings":";;;;;;;;AAAA,2CAAwC;AACxC,qDAAiD;AAMjD,IAAa,SAAS,GAAtB,MAAa,SAAS;CAAG,CAAA;AAAZ,SAAS;IAJrB,eAAM,CAAC;QACN,OAAO,EAAE,EAAE;QACX,WAAW,EAAE,CAAC,8BAAa,CAAC;KAC7B,CAAC;GACW,SAAS,CAAG;AAAZ,8BAAS"}

1
benchmarks/nest/main.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1 @@
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";;AAAA,uCAA2C;AAC3C,6CAAyC;AAEzC,KAAK,UAAU,SAAS;IACtB,MAAM,GAAG,GAAG,MAAM,kBAAW,CAAC,MAAM,CAAC,sBAAS,CAAC,CAAC;IAChD,MAAM,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AACzB,CAAC;AACD,SAAS,EAAE,CAAC"}

View File

@@ -1,55 +0,0 @@
// @ts-check
import eslint from '@eslint/js';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
import globals from 'globals';
import tseslint from 'typescript-eslint';
export default tseslint.config(
{
ignores: ['node_modules', '**/node_modules/**', '**/*.js', '**/*.d.ts'],
},
eslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
eslintPluginPrettierRecommended,
{
languageOptions: {
globals: {
...globals.node,
...globals.jest,
},
ecmaVersion: 5,
sourceType: 'module',
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
},
{
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-function-type': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/no-unused-expressions': 'off',
'@typescript-eslint/no-require-imports': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-non-null-asserted-optional-chain': 'warn',
'@typescript-eslint/no-misused-promises': [
'error',
{
checksVoidReturn: false,
checksConditionals: false,
},
],
'@typescript-eslint/require-await': 'off',
'@typescript-eslint/prefer-promise-reject-errors': 'off',
'@typescript-eslint/no-base-to-string': 'off',
'@typescript-eslint/unbound-method': 'off',
'@typescript-eslint/only-throw-error': 'off',
},
},
);

16
gulpfile.js Normal file
View File

@@ -0,0 +1,16 @@
'use strict';
/**
* Load the TypeScript compiler, then load the TypeScript gulpfile which simply loads all
* the tasks. The tasks are really inside tools/gulp/tasks.
*/
const path = require('path');
const projectDir = __dirname;
const tsconfigPath = path.join(projectDir, 'tools/gulp/tsconfig.json');
require('ts-node').register({
project: tsconfigPath
});
require('./tools/gulp/gulpfile');

View File

@@ -1,13 +0,0 @@
/**
* Load the TypeScript compiler, then load the TypeScript gulpfile which simply loads all
* the tasks. The tasks are really inside tools/gulp/tasks.
*/
import { register } from 'node:module';
import { pathToFileURL } from 'node:url';
register('ts-node/esm', pathToFileURL('./'), {
data: { project: './tools/gulp/tsconfig.json' },
});
await import('./tools/gulp/gulpfile.ts');

7
hooks/mocha-init-hook.ts Normal file
View File

@@ -0,0 +1,7 @@
export const mochaHooks = (): Mocha.RootHookObject => {
return {
async beforeAll(this: Mocha.Context) {
await import('reflect-metadata');
},
};
};

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common';
import { FooService } from './foo.service.js';
import { FooService } from './foo.service';
@Injectable()
export class BarService {

View File

@@ -1,11 +1,16 @@
import { Test } from '@nestjs/testing';
import { BarService } from '../src/bar.service.js';
import { FooService } from '../src/foo.service.js';
import * as chai from 'chai';
import * as chaiAsPromised from 'chai-as-promised';
import * as sinon from 'sinon';
import { BarService } from '../src/bar.service';
import { FooService } from '../src/foo.service';
chai.use(chaiAsPromised);
const { expect } = chai;
describe('Auto-Mocking Bar Deps', () => {
let service: BarService;
let fooService: FooService;
const stub = vi.fn();
const stub = sinon.stub();
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
providers: [BarService],
@@ -17,12 +22,12 @@ describe('Auto-Mocking Bar Deps', () => {
});
it('should be defined', () => {
expect(service).not.toBeUndefined();
expect(fooService).not.toBeUndefined();
expect(service).not.to.be.undefined;
expect(fooService).not.to.be.undefined;
});
it('should call bar.bar', () => {
service.bar();
expect(stub).toHaveBeenCalled();
expect(stub.called);
});
});
@@ -33,25 +38,23 @@ describe('Auto-Mocking with token in factory', () => {
})
.useMocker(token => {
if (token === FooService) {
return { foo: vi.fn() };
return { foo: sinon.stub };
}
})
.compile();
const service = moduleRef.get(BarService);
const fooServ = moduleRef.get<{ foo: ReturnType<typeof vi.fn> }>(
FooService as any,
);
const fooServ = moduleRef.get<{ foo: sinon.SinonStub }>(FooService as any);
service.bar();
expect(fooServ.foo).toHaveBeenCalled();
expect(fooServ.foo.called);
});
it('cannot mock the dependencies', async () => {
const moduleRef = Test.createTestingModule({
providers: [BarService],
}).useMocker(token => {
if (token === FooService.name + 'something that fails the token') {
return { foo: vi.fn() };
return { foo: sinon.stub };
}
}).compile;
await expect(moduleRef()).rejects.toThrow();
expect(moduleRef()).to.eventually.throw();
});
});

View File

@@ -1,18 +1,15 @@
{
"compilerOptions": {
"types": ["vitest/globals"],
"module": "commonjs",
"declaration": false,
"noImplicitAny": false,
"removeComments": true,
"noLib": false,
"esModuleInterop": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "ES2023",
"target": "ES2021",
"sourceMap": true,
"allowJs": true,
"strictNullChecks": true,
"outDir": "./dist",
"paths": {
"@nestjs/common": ["../../packages/common"],
@@ -35,8 +32,7 @@
},
"include": [
"src/**/*",
"e2e/**/*",
"test/**/*"
"e2e/**/*"
],
"exclude": [
"node_modules",

View File

@@ -1,7 +1,7 @@
import { NestExpressApplication } from '@nestjs/platform-express';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { AppModule } from '../src/app.module.js';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
describe('Express Cors', () => {
let app: NestExpressApplication;
@@ -25,7 +25,7 @@ describe('Express Cors', () => {
];
describe('Dynamic config', () => {
describe('enableCors', () => {
beforeAll(async () => {
before(async () => {
const module = await Test.createTestingModule({
imports: [AppModule],
}).compile();
@@ -66,13 +66,13 @@ describe('Express Cors', () => {
.expect('content-length', '0');
});
afterAll(async () => {
after(async () => {
await app.close();
});
});
describe('Application Options', () => {
beforeAll(async () => {
before(async () => {
const module = await Test.createTestingModule({
imports: [AppModule],
}).compile();
@@ -114,14 +114,14 @@ describe('Express Cors', () => {
.expect('content-length', '0');
});
afterAll(async () => {
after(async () => {
await app.close();
});
});
});
describe('Static config', () => {
describe('enableCors', () => {
beforeAll(async () => {
before(async () => {
const module = await Test.createTestingModule({
imports: [AppModule],
}).compile();
@@ -141,14 +141,14 @@ describe('Express Cors', () => {
.expect('access-control-expose-headers', 'foo,bar')
.expect('content-length', '0');
});
});
afterAll(async () => {
await app.close();
});
after(async () => {
await app.close();
});
describe('Application Options', () => {
beforeAll(async () => {
before(async () => {
const module = await Test.createTestingModule({
imports: [AppModule],
}).compile();
@@ -169,7 +169,7 @@ describe('Express Cors', () => {
.expect('content-length', '0');
});
afterAll(async () => {
after(async () => {
await app.close();
});
});

View File

@@ -3,8 +3,8 @@ import {
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { AppModule } from '../src/app.module.js';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
describe.skip('Fastify Cors', () => {
let app: NestFastifyApplication;
@@ -28,7 +28,7 @@ describe.skip('Fastify Cors', () => {
];
describe('Dynamic config', () => {
describe('enableCors', () => {
beforeAll(async () => {
before(async () => {
const module = await Test.createTestingModule({
imports: [AppModule],
}).compile();
@@ -38,12 +38,10 @@ describe.skip('Fastify Cors', () => {
);
let requestId = 0;
const configDelegation = {
delegator: function (req, cb) {
const config = configs[requestId];
requestId++;
cb(null, config);
},
const configDelegation = function (req, cb) {
const config = configs[requestId];
requestId++;
cb(null, config);
};
app.enableCors(configDelegation);
@@ -73,13 +71,13 @@ describe.skip('Fastify Cors', () => {
.expect('content-length', '0');
});
afterAll(async () => {
after(async () => {
await app.close();
});
});
describe('Application Options', () => {
beforeAll(async () => {
before(async () => {
const module = await Test.createTestingModule({
imports: [AppModule],
}).compile();
@@ -124,7 +122,7 @@ describe.skip('Fastify Cors', () => {
.expect('content-length', '0');
});
afterAll(async () => {
after(async () => {
await app.close();
});
});
@@ -132,7 +130,7 @@ describe.skip('Fastify Cors', () => {
describe('Static config', () => {
describe('enableCors', () => {
beforeAll(async () => {
before(async () => {
const module = await Test.createTestingModule({
imports: [AppModule],
}).compile();
@@ -154,14 +152,14 @@ describe.skip('Fastify Cors', () => {
.expect('access-control-expose-headers', 'foo,bar')
.expect('content-length', '0');
});
});
afterAll(async () => {
await app.close();
});
after(async () => {
await app.close();
});
describe('Application Options', () => {
beforeAll(async () => {
before(async () => {
const module = await Test.createTestingModule({
imports: [AppModule],
}).compile();
@@ -184,10 +182,10 @@ describe.skip('Fastify Cors', () => {
.expect('access-control-expose-headers', 'foo,bar')
.expect('content-length', '0');
});
});
afterAll(async () => {
await app.close();
});
after(async () => {
await app.close();
});
});
});

View File

@@ -1,5 +1,5 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller.js';
import { AppController } from './app.controller';
@Module({
controllers: [AppController],

View File

@@ -1,18 +1,15 @@
{
"compilerOptions": {
"types": ["vitest/globals"],
"module": "commonjs",
"declaration": false,
"noImplicitAny": false,
"removeComments": true,
"noLib": false,
"esModuleInterop": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "ES2023",
"target": "ES2021",
"sourceMap": true,
"allowJs": true,
"strictNullChecks": true,
"outDir": "./dist",
"paths": {
"@nestjs/common": ["../../packages/common"],

View File

@@ -1,62 +0,0 @@
import { DiscoveryService } from '@nestjs/core';
import { Test, TestingModule } from '@nestjs/testing';
import { AppModule } from '../src/app.module.js';
import { NonAppliedDecorator } from '../src/decorators/non-applied.decorator.js';
import { WebhooksExplorer } from '../src/webhooks.explorer.js';
describe('DiscoveryModule', () => {
let moduleRef: TestingModule;
beforeEach(async () => {
moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();
});
afterEach(async () => {
await moduleRef.close();
});
it('should discover all providers & handlers with corresponding annotations', async () => {
const webhooksExplorer = moduleRef.get(WebhooksExplorer);
expect(webhooksExplorer.getWebhooks()).toEqual([
{
handlers: [
{
event: 'start',
methodName: 'onStart',
},
],
name: 'cleanup',
},
{
handlers: [
{
event: 'start',
methodName: 'onStart',
},
],
name: 'flush',
},
]);
});
it('should return an empty array if no providers were found for a given discoverable decorator', () => {
const discoveryService = moduleRef.get(DiscoveryService);
const providers = discoveryService.getProviders({
metadataKey: NonAppliedDecorator.KEY,
});
expect(providers).toEqual([]);
});
it('should return an empty array if no controllers were found for a given discoverable decorator', () => {
const discoveryService = moduleRef.get(DiscoveryService);
const controllers = discoveryService.getControllers({
metadataKey: NonAppliedDecorator.KEY,
});
expect(controllers).toEqual([]);
});
});

View File

@@ -1,10 +0,0 @@
import { Module } from '@nestjs/common';
import { DiscoveryModule } from '@nestjs/core';
import { MyWebhookModule } from './my-webhook/my-webhook.module.js';
import { WebhooksExplorer } from './webhooks.explorer.js';
@Module({
imports: [MyWebhookModule, DiscoveryModule],
providers: [WebhooksExplorer],
})
export class AppModule {}

View File

@@ -1,9 +0,0 @@
import { DiscoveryService } from '@nestjs/core';
/**
* This decorator must not be used anywhere!
*
* This will be used to test the scenario where we are trying to retrieving
* metadata for a discoverable decorator that was not applied to any class.
*/
export const NonAppliedDecorator = DiscoveryService.createDecorator();

View File

@@ -1,6 +0,0 @@
import { DiscoveryService } from '@nestjs/core';
export const Webhook = DiscoveryService.createDecorator<{ name: string }>();
export const WebhookHandler = DiscoveryService.createDecorator<{
event: string;
}>();

View File

@@ -1,9 +0,0 @@
import { Webhook, WebhookHandler } from '../decorators/webhook.decorators.js';
@Webhook({ name: 'cleanup' })
export class CleanupWebhook {
@WebhookHandler({ event: 'start' })
onStart() {
console.log('cleanup started');
}
}

View File

@@ -1,9 +0,0 @@
import { Webhook, WebhookHandler } from '../decorators/webhook.decorators.js';
@Webhook({ name: 'flush' })
export class FlushWebhook {
@WebhookHandler({ event: 'start' })
onStart() {
console.log('flush started');
}
}

View File

@@ -1,6 +0,0 @@
import { Module } from '@nestjs/common';
import { CleanupWebhook } from './cleanup.webhook.js';
import { FlushWebhook } from './flush.webhook.js';
@Module({ providers: [CleanupWebhook, FlushWebhook] })
export class MyWebhookModule {}

View File

@@ -1,39 +0,0 @@
import { Injectable } from '@nestjs/common';
import { DiscoveryService, MetadataScanner } from '@nestjs/core';
import { Webhook, WebhookHandler } from './decorators/webhook.decorators.js';
@Injectable()
export class WebhooksExplorer {
constructor(
private readonly discoveryService: DiscoveryService,
private readonly metadataScanner: MetadataScanner,
) {}
getWebhooks() {
const webhooks = this.discoveryService.getProviders({
metadataKey: Webhook.KEY,
});
return webhooks.map(wrapper => {
const { name } = this.discoveryService.getMetadataByDecorator(
Webhook,
wrapper,
)!;
return {
name,
handlers: this.metadataScanner
.getAllMethodNames(wrapper.metatype!.prototype)
.map(methodName => {
const { event } = this.discoveryService.getMetadataByDecorator(
WebhookHandler,
wrapper,
methodName,
)!;
return {
methodName,
event,
};
}),
};
});
}
}

View File

@@ -1,43 +0,0 @@
{
"compilerOptions": {
"types": ["vitest/globals"],
"module": "commonjs",
"declaration": false,
"noImplicitAny": false,
"removeComments": true,
"noLib": false,
"esModuleInterop": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "ES2023",
"sourceMap": true,
"allowJs": true,
"strictNullChecks": true,
"outDir": "./dist",
"paths": {
"@nestjs/common": ["../../packages/common"],
"@nestjs/common/*": ["../../packages/common/*"],
"@nestjs/core": ["../../packages/core"],
"@nestjs/core/*": ["../../packages/core/*"],
"@nestjs/microservices": ["../../packages/microservices"],
"@nestjs/microservices/*": ["../../packages/microservices/*"],
"@nestjs/websockets": ["../../packages/websockets"],
"@nestjs/websockets/*": ["../../packages/websockets/*"],
"@nestjs/testing": ["../../packages/testing"],
"@nestjs/testing/*": ["../../packages/testing/*"],
"@nestjs/platform-express": ["../../packages/platform-express"],
"@nestjs/platform-express/*": ["../../packages/platform-express/*"],
"@nestjs/platform-socket.io": ["../../packages/platform-socket.io"],
"@nestjs/platform-socket.io/*": ["../../packages/platform-socket.io/*"],
"@nestjs/platform-ws": ["../../packages/platform-ws"],
"@nestjs/platform-ws/*": ["../../packages/platform-ws/*"]
}
},
"include": [
"src/**/*",
"e2e/**/*"
],
"exclude": [
"node_modules",
]
}

View File

@@ -25,9 +25,8 @@ services:
- "9001:9001"
restart: always
mysql:
image: mysql:9.6.0
image: mysql:8.0.32
environment:
MYSQL_ROOT_HOST: '%'
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: test
ports:
@@ -51,7 +50,7 @@ services:
zookeeper:
container_name: test-zookeeper
hostname: zookeeper
image: confluentinc/cp-zookeeper:7.9.5
image: confluentinc/cp-zookeeper:7.3.2
ports:
- "2181:2181"
environment:
@@ -60,7 +59,7 @@ services:
kafka:
container_name: test-kafka
hostname: kafka
image: confluentinc/cp-kafka:8.1.1
image: confluentinc/cp-kafka:7.3.2
depends_on:
- zookeeper
ports:

View File

@@ -1,7 +1,7 @@
import { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { AppModule } from '../src/app.module.js';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
describe('GraphQL - Code-first', () => {
let app: INestApplication;

View File

@@ -1,7 +1,7 @@
import { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { AppModule } from '../src/app.module.js';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
describe('GraphQL - Guards', () => {
let app: INestApplication;

View File

@@ -1,7 +1,7 @@
import { INestApplication, ValidationPipe } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { AppModule } from '../src/app.module.js';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
describe('GraphQL Pipes', () => {
let app: INestApplication;

View File

@@ -2,7 +2,7 @@ import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { join } from 'path';
import { RecipesModule } from './recipes/recipes.module.js';
import { RecipesModule } from './recipes/recipes.module';
@Module({
imports: [

View File

@@ -1,10 +1,10 @@
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module.js';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
void bootstrap();
bootstrap();

View File

@@ -1,9 +1,9 @@
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';
import { UnauthorizedFilter } from '../common/filters/unauthorized.filter.js';
import { DateScalar } from '../common/scalars/date.scalar.js';
import { RecipesResolver } from './recipes.resolver.js';
import { RecipesService } from './recipes.service.js';
import { UnauthorizedFilter } from '../common/filters/unauthorized.filter';
import { DateScalar } from '../common/scalars/date.scalar';
import { RecipesResolver } from './recipes.resolver';
import { RecipesService } from './recipes.service';
@Module({
providers: [

View File

@@ -1,12 +1,12 @@
import { NotFoundException, UseGuards, UseInterceptors } from '@nestjs/common';
import { Args, Mutation, Query, Resolver, Subscription } from '@nestjs/graphql';
import { PubSub } from 'graphql-subscriptions';
import { AuthGuard } from '../common/guards/auth.guard.js';
import { DataInterceptor } from '../common/interceptors/data.interceptor.js';
import { NewRecipeInput } from './dto/new-recipe.input.js';
import { RecipesArgs } from './dto/recipes.args.js';
import { Recipe } from './models/recipe.js';
import { RecipesService } from './recipes.service.js';
import { AuthGuard } from '../common/guards/auth.guard';
import { DataInterceptor } from '../common/interceptors/data.interceptor';
import { NewRecipeInput } from './dto/new-recipe.input';
import { RecipesArgs } from './dto/recipes.args';
import { Recipe } from './models/recipe';
import { RecipesService } from './recipes.service';
const pubSub = new PubSub();
@@ -35,7 +35,7 @@ export class RecipesResolver {
@Args('newRecipeData') newRecipeData: NewRecipeInput,
): Promise<Recipe> {
const recipe = await this.recipesService.create(newRecipeData);
void pubSub.publish('recipeAdded', { recipeAdded: recipe });
pubSub.publish('recipeAdded', { recipeAdded: recipe });
return recipe;
}
@@ -46,6 +46,6 @@ export class RecipesResolver {
@Subscription(returns => Recipe)
recipeAdded() {
return pubSub.asyncIterableIterator('recipeAdded');
return pubSub.asyncIterator('recipeAdded');
}
}

View File

@@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common';
import { NewRecipeInput } from './dto/new-recipe.input.js';
import { RecipesArgs } from './dto/recipes.args.js';
import { Recipe } from './models/recipe.js';
import { NewRecipeInput } from './dto/new-recipe.input';
import { RecipesArgs } from './dto/recipes.args';
import { Recipe } from './models/recipe';
@Injectable()
export class RecipesService {

View File

@@ -1,18 +1,15 @@
{
"compilerOptions": {
"types": ["vitest/globals"],
"module": "commonjs",
"declaration": false,
"noImplicitAny": false,
"removeComments": true,
"noLib": false,
"esModuleInterop": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "ES2023",
"target": "ES2021",
"sourceMap": true,
"allowJs": true,
"strictNullChecks": true,
"outDir": "./dist",
"paths": {
"@nestjs/common": ["../../packages/common"],

View File

@@ -1,7 +1,7 @@
import { INestApplication } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import request from 'supertest';
import { AsyncClassApplicationModule } from '../src/async-options-class.module.js';
import * as request from 'supertest';
import { AsyncClassApplicationModule } from '../src/async-options-class.module';
describe('GraphQL (async class)', () => {
let app: INestApplication;

View File

@@ -1,7 +1,7 @@
import { INestApplication } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import request from 'supertest';
import { AsyncExistingApplicationModule } from '../src/async-options-existing.module.js';
import * as request from 'supertest';
import { AsyncExistingApplicationModule } from '../src/async-options-existing.module';
describe('GraphQL (async existing)', () => {
let app: INestApplication;

View File

@@ -1,7 +1,7 @@
import { INestApplication } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import request from 'supertest';
import { AsyncApplicationModule } from '../src/async-options.module.js';
import * as request from 'supertest';
import { AsyncApplicationModule } from '../src/async-options.module';
describe('GraphQL (async configuration)', () => {
let app: INestApplication;

View File

@@ -2,10 +2,11 @@ import { ApolloDriver } from '@nestjs/apollo';
import { INestApplication } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { Test } from '@nestjs/testing';
import { expect } from 'chai';
import { join } from 'path';
import request from 'supertest';
import { CatsRequestScopedService } from '../src/cats/cats-request-scoped.service.js';
import { CatsModule } from '../src/cats/cats.module.js';
import * as request from 'supertest';
import { CatsRequestScopedService } from '../src/cats/cats-request-scoped.service';
import { CatsModule } from '../src/cats/cats.module';
describe('GraphQL request scoped', () => {
let app: INestApplication;
@@ -16,9 +17,7 @@ describe('GraphQL request scoped', () => {
CatsModule.enableRequestScope(),
GraphQLModule.forRoot({
driver: ApolloDriver,
typePaths: [
join(import.meta.dirname, '..', 'src', '**', '*.graphql'),
],
typePaths: [join(__dirname, '..', 'src', '**', '*.graphql')],
}),
],
}).compile();
@@ -43,7 +42,7 @@ describe('GraphQL request scoped', () => {
],
},
})
.end(err => {
.end((err, res) => {
if (err) return end(err);
end();
});
@@ -54,7 +53,7 @@ describe('GraphQL request scoped', () => {
});
it(`should create resolver for each incoming request`, () => {
expect(CatsRequestScopedService.COUNTER).toEqual(3);
expect(CatsRequestScopedService.COUNTER).to.be.eql(3);
});
afterEach(async () => {

View File

@@ -1,7 +1,7 @@
import { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { AppModule } from '../src/app.module.js';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
describe('GraphQL', () => {
let app: INestApplication;

View File

@@ -2,7 +2,7 @@ import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { join } from 'path';
import { CatsModule } from './cats/cats.module.js';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [
@@ -10,7 +10,7 @@ import { CatsModule } from './cats/cats.module.js';
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
includeStacktraceInErrorResponses: true,
typePaths: [join(import.meta.dirname, '**', '*.graphql')],
typePaths: [join(__dirname, '**', '*.graphql')],
}),
],
})

View File

@@ -2,12 +2,12 @@ import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { GqlOptionsFactory, GraphQLModule } from '@nestjs/graphql';
import { join } from 'path';
import { CatsModule } from './cats/cats.module.js';
import { CatsModule } from './cats/cats.module';
class ConfigService implements GqlOptionsFactory {
createGqlOptions(): ApolloDriverConfig {
return {
typePaths: [join(import.meta.dirname, '**', '*.graphql')],
typePaths: [join(__dirname, '**', '*.graphql')],
};
}
}

View File

@@ -1,9 +1,9 @@
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { CatsModule } from './cats/cats.module.js';
import { ConfigModule } from './config.module.js';
import { ConfigService } from './config.service.js';
import { CatsModule } from './cats/cats.module';
import { ConfigModule } from './config.module';
import { ConfigService } from './config.service';
@Module({
imports: [

View File

@@ -2,7 +2,7 @@ import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { join } from 'path';
import { CatsModule } from './cats/cats.module.js';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [
@@ -10,7 +10,7 @@ import { CatsModule } from './cats/cats.module.js';
GraphQLModule.forRootAsync<ApolloDriverConfig>({
driver: ApolloDriver,
useFactory: async () => ({
typePaths: [join(import.meta.dirname, '**', '*.graphql')],
typePaths: [join(__dirname, '**', '*.graphql')],
}),
}),
],

View File

@@ -1,5 +1,5 @@
import { Injectable, Scope } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface.js';
import { Cat } from './interfaces/cat.interface';
@Injectable({ scope: Scope.REQUEST })
export class CatsRequestScopedService {
@@ -20,6 +20,6 @@ export class CatsRequestScopedService {
}
findOneById(id: number): Cat {
return this.cats.find(cat => cat.id === id)!;
return this.cats.find(cat => cat.id === id);
}
}

View File

@@ -1,7 +1,7 @@
import { DynamicModule, Module, Scope } from '@nestjs/common';
import { CatsRequestScopedService } from './cats-request-scoped.service.js';
import { CatsResolvers } from './cats.resolvers.js';
import { CatsService } from './cats.service.js';
import { CatsRequestScopedService } from './cats-request-scoped.service';
import { CatsResolvers } from './cats.resolvers';
import { CatsService } from './cats.service';
@Module({
providers: [CatsService, CatsResolvers],

View File

@@ -1,9 +1,9 @@
import { ParseIntPipe, UseGuards } from '@nestjs/common';
import { Args, Mutation, Query, Resolver, Subscription } from '@nestjs/graphql';
import { PubSub } from 'graphql-subscriptions';
import { CatsGuard } from './cats.guard.js';
import { CatsService } from './cats.service.js';
import { Cat } from './interfaces/cat.interface.js';
import { CatsGuard } from './cats.guard';
import { CatsService } from './cats.service';
import { Cat } from './interfaces/cat.interface';
const pubSub = new PubSub();
@@ -27,13 +27,13 @@ export class CatsResolvers {
@Mutation('createCat')
async create(@Args() args: Cat): Promise<Cat> {
const createdCat = this.catsService.create(args);
void pubSub.publish('catCreated', { catCreated: createdCat });
const createdCat = await this.catsService.create(args);
pubSub.publish('catCreated', { catCreated: createdCat });
return createdCat;
}
@Subscription('catCreated')
catCreated() {
return pubSub.asyncIterableIterator('catCreated');
return pubSub.asyncIterator('catCreated');
}
}

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface.js';
import { Cat } from './interfaces/cat.interface';
@Injectable()
export class CatsService {
@@ -20,6 +20,6 @@ export class CatsService {
}
findOneById(id: number): Cat {
return this.cats.find(cat => cat.id === id)!;
return this.cats.find(cat => cat.id === id);
}
}

View File

@@ -1,5 +1,5 @@
import { Module } from '@nestjs/common';
import { ConfigService } from './config.service.js';
import { ConfigService } from './config.service';
@Module({
providers: [ConfigService],

View File

@@ -6,7 +6,7 @@ import { join } from 'path';
export class ConfigService implements GqlOptionsFactory {
createGqlOptions(): GqlModuleOptions {
return {
typePaths: [join(import.meta.dirname, '**', '*.graphql')],
typePaths: [join(__dirname, '**', '*.graphql')],
};
}
}

View File

@@ -1,8 +1,8 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module.js';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
void bootstrap();
bootstrap();

View File

@@ -1,18 +1,15 @@
{
"compilerOptions": {
"types": ["vitest/globals"],
"module": "commonjs",
"declaration": false,
"noImplicitAny": false,
"removeComments": true,
"noLib": false,
"esModuleInterop": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "ES2023",
"target": "ES2021",
"sourceMap": true,
"allowJs": true,
"strictNullChecks": true,
"outDir": "./dist",
"paths": {
"@nestjs/common": ["../../packages/common"],

View File

@@ -4,9 +4,10 @@ import {
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { Test } from '@nestjs/testing';
import { expect } from 'chai';
import { RawServerDefault } from 'fastify';
import request from 'supertest';
import { ErrorsController } from '../src/errors/errors.controller.js';
import * as request from 'supertest';
import { ErrorsController } from '../src/errors/errors.controller';
describe('Error messages', () => {
let server: RawServerDefault;
@@ -81,12 +82,14 @@ describe('Error messages', () => {
url: '/sync',
})
.then(({ payload, statusCode }) => {
expect(statusCode).toBe(HttpStatus.BAD_REQUEST);
expect(JSON.parse(payload)).toEqual({
statusCode: 400,
error: 'Bad Request',
message: 'Integration test',
});
expect(statusCode).to.equal(HttpStatus.BAD_REQUEST);
expect(payload).to.equal(
JSON.stringify({
statusCode: 400,
error: 'Bad Request',
message: 'Integration test',
}),
);
});
});
@@ -94,15 +97,17 @@ describe('Error messages', () => {
return app
.inject({
method: 'GET',
url: '/async',
url: '/sync',
})
.then(({ payload, statusCode }) => {
expect(statusCode).toBe(HttpStatus.BAD_REQUEST);
expect(JSON.parse(payload)).toEqual({
statusCode: 400,
error: 'Bad Request',
message: 'Integration test',
});
expect(statusCode).to.equal(HttpStatus.BAD_REQUEST);
expect(payload).to.equal(
JSON.stringify({
statusCode: 400,
error: 'Bad Request',
message: 'Integration test',
}),
);
});
});
@@ -113,11 +118,13 @@ describe('Error messages', () => {
url: '/unexpected-error',
})
.then(({ payload, statusCode }) => {
expect(statusCode).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
expect(JSON.parse(payload)).toEqual({
statusCode: 500,
message: 'Internal server error',
});
expect(statusCode).to.equal(HttpStatus.INTERNAL_SERVER_ERROR);
expect(payload).to.equal(
JSON.stringify({
statusCode: 500,
message: 'Internal server error',
}),
);
});
});

View File

@@ -12,8 +12,8 @@ import {
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { AppModule } from '../src/app.module.js';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
const RETURN_VALUE = 'test';
const MIDDLEWARE_VALUE = 'middleware';
@@ -50,16 +50,6 @@ class TestController {
return RETURN_VALUE;
}
@Get('legacy-wildcard/overview')
testLegacyWildcard() {
return RETURN_VALUE;
}
@Get('splat-wildcard/overview')
testSplatWildcard() {
return RETURN_VALUE;
}
@Get('overview/:id')
overviewById() {
return RETURN_VALUE;
@@ -74,17 +64,10 @@ class TestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply((req, res, next) => res.end(MIDDLEWARE_VALUE))
.exclude(
'test',
'overview/:id',
'wildcard/*',
'legacy-wildcard/(.*)',
'splat-wildcard/*splat',
{
path: 'middleware',
method: RequestMethod.POST,
},
)
.exclude('test', 'overview/:id', 'wildcard/(.*)', {
path: 'middleware',
method: RequestMethod.POST,
})
.forRoutes('*');
}
}
@@ -143,18 +126,6 @@ describe('Exclude middleware (fastify)', () => {
.expect(200, RETURN_VALUE);
});
it(`should exclude "/legacy-wildcard/overview" endpoint (by wildcard, legacy syntax)`, () => {
return request(app.getHttpServer())
.get('/legacy-wildcard/overview')
.expect(200, RETURN_VALUE);
});
it(`should exclude "/splat-wildcard/overview" endpoint (by wildcard, new syntax)`, () => {
return request(app.getHttpServer())
.get('/splat-wildcard/overview')
.expect(200, RETURN_VALUE);
});
afterEach(async () => {
await app.close();
});

View File

@@ -8,8 +8,8 @@ import {
RequestMethod,
} from '@nestjs/common';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { AppModule } from '../src/app.module.js';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
const RETURN_VALUE = 'test';
const MIDDLEWARE_VALUE = 'middleware';
@@ -41,25 +41,10 @@ class TestController {
return RETURN_VALUE;
}
@Get('legacy-wildcard/overview')
testLegacyWildcard() {
return RETURN_VALUE;
}
@Get('splat-wildcard/overview')
testSplatWildcard() {
return RETURN_VALUE;
}
@Get('overview/:id')
overviewById() {
return RETURN_VALUE;
}
@Get('multiple/exclude')
multipleExclude() {
return RETURN_VALUE;
}
}
@Module({
@@ -70,19 +55,11 @@ class TestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply((req, res, next) => res.send(MIDDLEWARE_VALUE))
.exclude(
'test',
'overview/:id',
'wildcard/*',
'legacy-wildcard/(.*)',
'splat-wildcard/*splat',
{
path: 'middleware',
method: RequestMethod.POST,
},
)
.exclude('multiple/exclude')
.forRoutes('*path');
.exclude('test', 'overview/:id', 'wildcard/(.*)', {
path: 'middleware',
method: RequestMethod.POST,
})
.forRoutes('*');
}
}
@@ -133,24 +110,6 @@ describe('Exclude middleware', () => {
.expect(200, RETURN_VALUE);
});
it(`should exclude "/legacy-wildcard/overview" endpoint (by wildcard, legacy syntax)`, () => {
return request(app.getHttpServer())
.get('/legacy-wildcard/overview')
.expect(200, RETURN_VALUE);
});
it(`should exclude "/splat-wildcard/overview" endpoint (by wildcard, new syntax)`, () => {
return request(app.getHttpServer())
.get('/splat-wildcard/overview')
.expect(200, RETURN_VALUE);
});
it(`should exclude "/multiple/exclude" endpoint`, () => {
return request(app.getHttpServer())
.get('/multiple/exclude')
.expect(200, RETURN_VALUE);
});
afterEach(async () => {
await app.close();
});

View File

@@ -1,13 +1,12 @@
import { INestApplication } from '@nestjs/common';
import { ExpressAdapter } from '@nestjs/platform-express';
import { Test } from '@nestjs/testing';
import express from 'express';
import request from 'supertest';
import { App } from 'supertest/types';
import { AppModule } from '../src/app.module.js';
import * as express from 'express';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
describe('Hello world (express instance)', () => {
let server: App;
let server;
let app: INestApplication;
beforeEach(async () => {
@@ -46,10 +45,6 @@ describe('Hello world (express instance)', () => {
});
});
it('/HEAD should respond to with a 200', () => {
return request(server).head('/hello').expect(200);
});
afterEach(async () => {
await app.close();
});

View File

@@ -1,9 +1,9 @@
import { INestApplication } from '@nestjs/common';
import { ExpressAdapter } from '@nestjs/platform-express';
import { Test } from '@nestjs/testing';
import express from 'express';
import request from 'supertest';
import { AppModule } from '../src/app.module.js';
import * as express from 'express';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
describe('Hello world (express instance with multiple applications)', () => {
let server;

View File

@@ -3,7 +3,8 @@ import {
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { Test } from '@nestjs/testing';
import { AppModule } from '../src/app.module.js';
import { expect } from 'chai';
import { AppModule } from '../src/app.module';
describe('Hello world (fastify adapter)', () => {
let app: NestFastifyApplication;
@@ -25,10 +26,7 @@ describe('Hello world (fastify adapter)', () => {
method: 'GET',
url: '/hello',
})
.then(({ payload, statusCode }) => {
expect(statusCode).toBe(200);
expect(payload).toEqual('Hello world!');
});
.then(({ payload }) => expect(payload).to.be.eql('Hello world!'));
});
it(`/GET (Promise/async)`, () => {
@@ -37,10 +35,7 @@ describe('Hello world (fastify adapter)', () => {
method: 'GET',
url: '/hello/async',
})
.then(({ payload, statusCode }) => {
expect(statusCode).toBe(200);
expect(payload).toEqual('Hello world!');
});
.then(({ payload }) => expect(payload).to.be.eql('Hello world!'));
});
it(`/GET (Observable stream)`, () => {
@@ -49,10 +44,7 @@ describe('Hello world (fastify adapter)', () => {
method: 'GET',
url: '/hello/stream',
})
.then(({ payload, statusCode }) => {
expect(statusCode).toBe(200);
expect(payload).toEqual('Hello world!');
});
.then(({ payload }) => expect(payload).to.be.eql('Hello world!'));
});
it(`/GET { host: ":tenant.example.com" } not matched`, () => {
@@ -62,7 +54,7 @@ describe('Hello world (fastify adapter)', () => {
url: '/host',
})
.then(({ payload }) => {
expect(JSON.parse(payload)).toEqual({
expect(JSON.parse(payload)).to.be.eql({
error: 'Internal Server Error',
message:
'HTTP adapter does not support filtering on host: ":tenant.example.com"',
@@ -78,7 +70,7 @@ describe('Hello world (fastify adapter)', () => {
url: '/host-array',
})
.then(({ payload }) => {
expect(JSON.parse(payload)).toEqual({
expect(JSON.parse(payload)).to.be.eql({
error: 'Internal Server Error',
message:
'HTTP adapter does not support filtering on hosts: [":tenant.example1.com", ":tenant.example2.com"]',
@@ -92,19 +84,7 @@ describe('Hello world (fastify adapter)', () => {
.inject()
.get('/hello')
.end()
.then(({ payload, statusCode }) => {
expect(statusCode).toBe(200);
expect(payload).toEqual('Hello world!');
});
});
it('/HEAD should respond to with a 200', () => {
return app
.inject({
method: 'HEAD',
url: '/hello',
})
.then(({ statusCode }) => expect(statusCode).toBe(200));
.then(({ payload }) => expect(payload).to.be.eql('Hello world!'));
});
afterEach(async () => {

View File

@@ -1,134 +0,0 @@
import {
Controller,
Get,
Injectable,
MiddlewareConsumer,
Module,
NestModule,
} from '@nestjs/common';
import {
FastifyAdapter,
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { Test } from '@nestjs/testing';
describe('Middleware before init (FastifyAdapter)', () => {
let app: NestFastifyApplication;
@Injectable()
class TestService {
getData(): string {
return 'test_data';
}
}
@Controller()
class TestController {
constructor(private readonly testService: TestService) {}
@Get('test')
test() {
return { data: this.testService.getData() };
}
@Get('health')
health() {
return { status: 'ok' };
}
}
@Module({
controllers: [TestController],
providers: [TestService],
})
class TestModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply((req, res, next) => {
res.setHeader('x-middleware', 'applied');
next();
})
.forRoutes('*');
}
}
describe('should queue middleware when registered before init', () => {
beforeEach(async () => {
const module = await Test.createTestingModule({
imports: [TestModule],
}).compile();
app = module.createNestApplication<NestFastifyApplication>(
new FastifyAdapter(),
);
// Register middleware before init - should be queued
app.use((req, res, next) => {
res.setHeader('x-global-middleware', 'applied');
next();
});
// Now init the app - queued middleware should be registered
await app.init();
await app.getHttpAdapter().getInstance().ready();
});
it('should apply queued middleware after init', () => {
return app
.inject({
method: 'GET',
url: '/test',
})
.then(({ statusCode, payload, headers }) => {
expect(statusCode).toBe(200);
expect(JSON.parse(payload)).toEqual({ data: 'test_data' });
// Verify both module-level and global middleware were applied
expect(headers['x-middleware']).toBe('applied');
expect(headers['x-global-middleware']).toBe('applied');
});
});
afterEach(async () => {
await app.close();
});
});
describe('should work when app is initialized before middleware registration', () => {
beforeEach(async () => {
const module = await Test.createTestingModule({
imports: [TestModule],
}).compile();
app = module.createNestApplication<NestFastifyApplication>(
new FastifyAdapter(),
);
// Initialize app first
await app.init();
// Now middleware registration should work
app.use((req, res, next) => {
res.setHeader('x-global-middleware', 'applied');
next();
});
await app.getHttpAdapter().getInstance().ready();
});
it('should register middleware successfully after init', () => {
return app
.inject({
method: 'GET',
url: '/test',
})
.then(({ statusCode, payload }) => {
expect(statusCode).toBe(200);
expect(JSON.parse(payload)).toEqual({ data: 'test_data' });
});
});
afterEach(async () => {
await app.close();
});
});
});

View File

@@ -1,4 +1,91 @@
// Temporarily disabled due to various regressions
describe.skip('Hello world (fastify adapter with multiple applications)', () => {
it('placeholder', () => {});
});
/* Temporarily disabled due to various regressions
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
import { Test } from '@nestjs/testing';
import { expect } from 'chai';
import { ApplicationModule } from '../src/app.module';
describe('Hello world (fastify adapter with multiple applications)', () => {
let adapter: FastifyAdapter;
let apps: NestFastifyApplication[];
beforeEach(async () => {
const module1 = await Test.createTestingModule({
imports: [ApplicationModule],
}).compile();
const module2 = await Test.createTestingModule({
imports: [ApplicationModule],
}).compile();
adapter = new FastifyAdapter();
apps = [
module1.createNestApplication<NestFastifyApplication>(adapter),
module2
.createNestApplication<NestFastifyApplication>(adapter, {
bodyParser: false,
})
.setGlobalPrefix('/app2'),
];
await Promise.all(apps.map(app => app.init()));
});
it(`/GET`, () => {
return adapter
.inject({
method: 'GET',
url: '/hello',
})
.then(({ payload }) => expect(payload).to.be.eql('Hello world!'));
});
it(`/GET (app2)`, () => {
return adapter
.inject({
method: 'GET',
url: '/app2/hello',
})
.then(({ payload }) => expect(payload).to.be.eql('Hello world!'));
});
it(`/GET (Promise/async)`, () => {
return adapter
.inject({
method: 'GET',
url: '/hello/async',
})
.then(({ payload }) => expect(payload).to.be.eql('Hello world!'));
});
it(`/GET (app2 Promise/async)`, () => {
return adapter
.inject({
method: 'GET',
url: '/app2/hello/async',
})
.then(({ payload }) => expect(payload).to.be.eql('Hello world!'));
});
it(`/GET (Observable stream)`, () => {
return adapter
.inject({
method: 'GET',
url: '/hello/stream',
})
.then(({ payload }) => expect(payload).to.be.eql('Hello world!'));
});
it(`/GET (app2 Observable stream)`, () => {
return adapter
.inject({
method: 'GET',
url: '/app2/hello/stream',
})
.then(({ payload }) => expect(payload).to.be.eql('Hello world!'));
});
afterEach(async () => {
await Promise.all(apps.map(app => app.close()));
await adapter.close();
});
});*/

View File

@@ -1,157 +0,0 @@
import { ConsoleLogger, INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { AppModule } from '../src/app.module.js';
describe('ForceConsole Option', () => {
let app: INestApplication;
describe('When forceConsole is true', () => {
let consoleLogSpy: ReturnType<typeof vi.fn>;
let consoleErrorSpy: ReturnType<typeof vi.fn>;
let processStdoutSpy: ReturnType<typeof vi.fn>;
let processStderrSpy: ReturnType<typeof vi.fn>;
beforeEach(async () => {
// Spy on console and process methods
consoleLogSpy = vi.spyOn(console, 'log');
consoleErrorSpy = vi.spyOn(console, 'error');
processStdoutSpy = vi.spyOn(process.stdout, 'write');
processStderrSpy = vi.spyOn(process.stderr, 'write');
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleRef.createNestApplication({
forceConsole: true,
logger: ['log', 'error'],
});
await app.init();
});
afterEach(async () => {
consoleLogSpy.mockRestore();
consoleErrorSpy.mockRestore();
processStdoutSpy.mockRestore();
processStderrSpy.mockRestore();
await app.close();
});
it('should use console.log instead of process.stdout.write', async () => {
const logger = new ConsoleLogger('TestContext', { forceConsole: true });
logger.log('Test log message');
// Should use console.log when forceConsole is true
expect(consoleLogSpy).toHaveBeenCalled();
// Verify console.log was called with the message
const consoleLogCalls = consoleLogSpy.mock.calls.filter(args =>
args.some(arg => String(arg).includes('Test log message')),
);
expect(consoleLogCalls.length).toBeGreaterThan(0);
});
it('should use console.error instead of process.stderr.write', async () => {
const logger = new ConsoleLogger('TestContext', { forceConsole: true });
logger.error('Test error message');
// Should use console.error when forceConsole is true
expect(consoleErrorSpy).toHaveBeenCalled();
// Verify console.error was called with the message
const consoleErrorCalls = consoleErrorSpy.mock.calls.filter(args =>
args.some(arg => String(arg).includes('Test error message')),
);
expect(consoleErrorCalls.length).toBeGreaterThan(0);
});
it('should handle GET request with forceConsole option enabled', () => {
return request(app.getHttpServer()).get('/hello').expect(200);
});
});
describe('When forceConsole is false (default)', () => {
let consoleLogSpy: ReturnType<typeof vi.fn>;
let consoleErrorSpy: ReturnType<typeof vi.fn>;
let processStdoutSpy: ReturnType<typeof vi.fn>;
let processStderrSpy: ReturnType<typeof vi.fn>;
beforeEach(async () => {
// Spy on console and process methods
consoleLogSpy = vi.spyOn(console, 'log');
consoleErrorSpy = vi.spyOn(console, 'error');
processStdoutSpy = vi.spyOn(process.stdout, 'write');
processStderrSpy = vi.spyOn(process.stderr, 'write');
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleRef.createNestApplication({
logger: ['log', 'error'],
// forceConsole is not set, defaults to false
});
await app.init();
});
afterEach(async () => {
consoleLogSpy.mockRestore();
consoleErrorSpy.mockRestore();
processStdoutSpy.mockRestore();
processStderrSpy.mockRestore();
await app.close();
});
it('should not directly call console.log when forceConsole is false', async () => {
const logger = new ConsoleLogger('TestContext');
// Reset spy to ensure clean state
consoleLogSpy.mockClear();
logger.log('Test log message');
// When forceConsole is false, should not call console.log
expect(consoleLogSpy).not.toHaveBeenCalled();
});
it('should not directly call console.error when forceConsole is false', async () => {
const logger = new ConsoleLogger('TestContext');
// Reset spy to ensure clean state
consoleErrorSpy.mockClear();
logger.error('Test error message');
// When forceConsole is false, should not call console.error
expect(consoleErrorSpy).not.toHaveBeenCalled();
});
});
describe('When forceConsole is set via NestFactory.create', () => {
it('should apply forceConsole to the default logger', async () => {
const consoleLogSpy = vi.spyOn(console, 'log');
const processStdoutSpy = vi.spyOn(process.stdout, 'write');
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();
const testApp = moduleRef.createNestApplication({
forceConsole: true,
});
await testApp.init();
// The logger created by NestFactory should respect forceConsole option
const logger = new ConsoleLogger('AppContext', { forceConsole: true });
logger.log('Application started');
expect(consoleLogSpy).toHaveBeenCalled();
consoleLogSpy.mockRestore();
processStdoutSpy.mockRestore();
await testApp.close();
});
});
});

View File

@@ -5,8 +5,8 @@ import {
} from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { AppModule } from '../src/app.module.js';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
@Injectable()
export class AuthGuard {
@@ -33,31 +33,10 @@ function createTestModule(guard) {
describe('Guards', () => {
let app: INestApplication;
afterEach(async () => {
await app.close();
});
it(`should prevent access (unauthorized)`, async () => {
app = (await createTestModule(new AuthGuard())).createNestApplication();
await app.init();
return request(app.getHttpServer())
.get('/hello')
.expect(401)
.expect(({ body }) => {
expect(body.message).toBe('Unauthorized');
expect(body.statusCode).toBe(401);
});
});
it(`should allow access when guard returns true`, async () => {
const allowGuard = { canActivate: () => true };
app = (await createTestModule(allowGuard)).createNestApplication();
await app.init();
return request(app.getHttpServer())
.get('/hello')
.expect(200)
.expect('Hello world!');
return request(app.getHttpServer()).get('/hello').expect(401);
});
});

View File

@@ -1,7 +1,7 @@
import request from 'supertest';
import * as request from 'supertest';
import { Test } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import { AppModule } from '../src/app.module.js';
import { AppModule } from '../src/app.module';
describe('Hello world (default adapter)', () => {
let server;

View File

@@ -9,8 +9,8 @@ import { APP_INTERCEPTOR } from '@nestjs/core';
import { Test } from '@nestjs/testing';
import { of } from 'rxjs';
import { map } from 'rxjs/operators';
import request from 'supertest';
import { AppModule } from '../src/app.module.js';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
const RETURN_VALUE = 'test';
@@ -48,7 +48,7 @@ export class HeaderInterceptor {
const ctx = context.switchToHttp();
const res = ctx.getResponse();
for (const key in this.headers) {
if (Object.prototype.hasOwnProperty.call(this.headers, key)) {
if (this.headers.hasOwnProperty(key)) {
res.header(key, this.headers[key]);
}
}

View File

@@ -1,7 +1,7 @@
import { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { AppModule } from '../src/app.module.js';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
describe('Hello world (default adapter)', () => {
let server;

View File

@@ -8,9 +8,9 @@ import {
RequestMethod,
} from '@nestjs/common';
import { Test } from '@nestjs/testing';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
import { Response } from 'express';
import request from 'supertest';
import { AppModule } from '../src/app.module.js';
const INCLUDED_VALUE = 'test_included';
const RETURN_VALUE = 'test';

View File

@@ -1,56 +1,20 @@
import {
Global,
INestApplication,
MiddlewareConsumer,
Module,
} from '@nestjs/common';
import { INestApplication, MiddlewareConsumer, Module } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import * as request from 'supertest';
const RETURN_VALUE_A = 'test_A';
const RETURN_VALUE_B = 'test_B';
const RETURN_VALUE_X = 'test_X';
const RETURN_VALUE_GLOBAL = 'test_GLOBAL';
@Global()
@Module({})
class GlobalModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply((req, res, next) => res.send(RETURN_VALUE_GLOBAL))
.forRoutes('ping');
}
}
@Global()
@Module({})
class GlobalModule2 {
configure(consumer: MiddlewareConsumer) {
consumer
.apply((req, res, next) => res.send(RETURN_VALUE_GLOBAL + '2'))
.forRoutes('ping');
}
}
@Module({ imports: [GlobalModule, GlobalModule2] })
class ModuleX {
configure(consumer: MiddlewareConsumer) {
consumer
.apply((req, res, next) => res.send(RETURN_VALUE_X))
.forRoutes('hello')
.apply((req, res, next) => res.send(RETURN_VALUE_X))
.forRoutes('ping');
}
}
@Module({ imports: [ModuleX] })
@Module({
imports: [],
})
class ModuleA {
configure(consumer: MiddlewareConsumer) {
consumer
.apply((req, res, next) => res.send(RETURN_VALUE_A))
.forRoutes('hello')
.apply((req, res, next) => res.send(RETURN_VALUE_A))
.forRoutes('ping');
.apply((req, res, next) => {
res.send(RETURN_VALUE_A);
})
.forRoutes('hello');
}
}
@@ -60,10 +24,10 @@ class ModuleA {
class ModuleB {
configure(consumer: MiddlewareConsumer) {
consumer
.apply((req, res, next) => res.send(RETURN_VALUE_B))
.forRoutes('hello')
.apply((req, res, next) => res.send(RETURN_VALUE_B))
.forRoutes('ping');
.apply((req, res, next) => {
res.send(RETURN_VALUE_B);
})
.forRoutes('hello');
}
}
@@ -91,12 +55,6 @@ describe('Middleware (execution order)', () => {
.expect(200, RETURN_VALUE_B);
});
it('should execute global middleware first', () => {
return request(app.getHttpServer())
.get('/ping')
.expect(200, RETURN_VALUE_GLOBAL);
});
afterEach(async () => {
await app.close();
});

View File

@@ -5,7 +5,6 @@ import {
Module,
NestMiddleware,
NestModule,
Param,
Query,
Req,
RequestMethod,
@@ -15,9 +14,8 @@ import {
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { Test } from '@nestjs/testing';
import { FastifyRequest } from 'fastify';
import request from 'supertest';
import { AppModule } from '../src/app.module.js';
import { expect } from 'chai';
import { AppModule } from '../src/app.module';
describe('Middleware (FastifyAdapter)', () => {
let app: NestFastifyApplication;
@@ -37,11 +35,6 @@ describe('Middleware (FastifyAdapter)', () => {
return RETURN_VALUE;
}
@Get('legacy_style_wildcard/wildcard_nested')
legacy_style_wildcard() {
return RETURN_VALUE;
}
@Get('test')
test() {
return RETURN_VALUE;
@@ -81,13 +74,9 @@ describe('Middleware (FastifyAdapter)', () => {
.apply((req, res, next) => res.end(INCLUDED_VALUE))
.forRoutes({ path: 'tests/included', method: RequestMethod.POST })
.apply((req, res, next) => res.end(REQ_URL_VALUE))
.forRoutes('req/url/*')
.forRoutes('req/url/(.*)')
.apply((req, res, next) => res.end(WILDCARD_VALUE))
.forRoutes(
'express_style_wildcard/*',
'tests/*path',
'legacy_style_wildcard/(.*)',
)
.forRoutes('express_style_wildcard/*', 'tests/(.*)')
.apply((req, res, next) => res.end(QUERY_VALUE))
.forRoutes('query')
.apply((req, res, next) => next())
@@ -96,7 +85,7 @@ describe('Middleware (FastifyAdapter)', () => {
.forRoutes(TestController)
.apply((req, res, next) => res.end(RETURN_VALUE))
.exclude({ path: QUERY_VALUE, method: -1 as any })
.forRoutes('*');
.forRoutes('(.*)');
}
}
@@ -110,13 +99,13 @@ describe('Middleware (FastifyAdapter)', () => {
await app.init();
});
it(`forRoutes(*)`, () => {
it(`forRoutes((.*))`, () => {
return app
.inject({
method: 'GET',
url: '/hello',
})
.then(({ payload }) => expect(payload).toEqual(RETURN_VALUE));
.then(({ payload }) => expect(payload).to.be.eql(RETURN_VALUE));
});
it(`forRoutes(TestController)`, () => {
@@ -125,7 +114,7 @@ describe('Middleware (FastifyAdapter)', () => {
method: 'GET',
url: '/test',
})
.then(({ payload }) => expect(payload).toEqual(SCOPED_VALUE));
.then(({ payload }) => expect(payload).to.be.eql(SCOPED_VALUE));
});
it(`query?test=${QUERY_VALUE} forRoutes(query)`, () => {
@@ -137,7 +126,7 @@ describe('Middleware (FastifyAdapter)', () => {
test: QUERY_VALUE,
},
})
.then(({ payload }) => expect(payload).toEqual(QUERY_VALUE));
.then(({ payload }) => expect(payload).to.be.eql(QUERY_VALUE));
});
it(`${QUERY_VALUE}?test=${QUERY_VALUE} forRoutes(${QUERY_VALUE})`, () => {
@@ -149,16 +138,16 @@ describe('Middleware (FastifyAdapter)', () => {
test: QUERY_VALUE,
},
})
.then(({ payload }) => expect(payload).toEqual(QUERY_VALUE));
.then(({ payload }) => expect(payload).to.be.eql(QUERY_VALUE));
});
it(`forRoutes(tests/*path)`, () => {
it(`forRoutes(tests/(.*))`, () => {
return app
.inject({
method: 'GET',
url: '/tests/wildcard_nested',
})
.then(({ payload }) => expect(payload).toEqual(WILDCARD_VALUE));
.then(({ payload }) => expect(payload).to.be.eql(WILDCARD_VALUE));
});
it(`forRoutes(express_style_wildcard/*)`, () => {
@@ -167,16 +156,7 @@ describe('Middleware (FastifyAdapter)', () => {
method: 'GET',
url: '/express_style_wildcard/wildcard_nested',
})
.then(({ payload }) => expect(payload).toEqual(WILDCARD_VALUE));
});
it(`forRoutes(legacy_style_wildcard/*)`, () => {
return app
.inject({
method: 'GET',
url: '/legacy_style_wildcard/wildcard_nested',
})
.then(({ payload }) => expect(payload).toEqual(WILDCARD_VALUE));
.then(({ payload }) => expect(payload).to.be.eql(WILDCARD_VALUE));
});
it(`forRoutes(req/url/)`, () => {
@@ -186,7 +166,7 @@ describe('Middleware (FastifyAdapter)', () => {
method: 'GET',
url: `/req/url${reqUrl}`,
})
.then(({ payload }) => expect(payload).toEqual(REQ_URL_VALUE));
.then(({ payload }) => expect(payload).to.be.eql(REQ_URL_VALUE));
});
it(`GET forRoutes(POST tests/included)`, () => {
@@ -195,7 +175,7 @@ describe('Middleware (FastifyAdapter)', () => {
method: 'GET',
url: '/tests/included',
})
.then(({ payload }) => expect(payload).toEqual(WILDCARD_VALUE));
.then(({ payload }) => expect(payload).to.be.eql(WILDCARD_VALUE));
});
it(`POST forRoutes(POST tests/included)`, () => {
@@ -204,16 +184,7 @@ describe('Middleware (FastifyAdapter)', () => {
method: 'POST',
url: '/tests/included',
})
.then(({ payload }) => expect(payload).toEqual(INCLUDED_VALUE));
});
it(`GET forRoutes(POST /tests/%69ncluded) - ensure middleware is executed correctly with encoded characters`, () => {
return app
.inject({
method: 'POST',
url: '/tests/%69ncluded', // 'i' character is encoded
})
.then(({ payload }) => expect(payload).toEqual(INCLUDED_VALUE));
.then(({ payload }) => expect(payload).to.be.eql(INCLUDED_VALUE));
});
afterEach(async () => {
@@ -221,7 +192,7 @@ describe('Middleware (FastifyAdapter)', () => {
});
});
describe('should execute middleware only once for given routes', () => {
describe.only('should execute middleware only once for given routes', () => {
class Middleware implements NestMiddleware {
use(request: any, reply: any, next: () => void) {
if (request.middlewareExecutionCount === undefined) {
@@ -328,7 +299,7 @@ describe('Middleware (FastifyAdapter)', () => {
url: '/a/b/c',
})
.then(({ payload }) => {
expect(payload).toEqual(
expect(payload).to.be.eql(
JSON.stringify({
success: true,
actual: 1,
@@ -345,7 +316,7 @@ describe('Middleware (FastifyAdapter)', () => {
url: '/a/b',
})
.then(({ payload }) =>
expect(payload).toEqual(
expect(payload).to.be.eql(
JSON.stringify({
success: true,
actual: 1,
@@ -362,7 +333,7 @@ describe('Middleware (FastifyAdapter)', () => {
url: '/a',
})
.then(({ payload }) =>
expect(payload).toEqual(
expect(payload).to.be.eql(
JSON.stringify({
success: true,
actual: 1,
@@ -379,7 +350,7 @@ describe('Middleware (FastifyAdapter)', () => {
url: '/similar',
})
.then(({ payload }) =>
expect(payload).toEqual(
expect(payload).to.be.eql(
JSON.stringify({
success: true,
actual: 1,
@@ -396,7 +367,7 @@ describe('Middleware (FastifyAdapter)', () => {
url: '/similar/test',
})
.then(({ payload }) =>
expect(payload).toEqual(
expect(payload).to.be.eql(
JSON.stringify({
success: true,
actual: 1,
@@ -413,7 +384,7 @@ describe('Middleware (FastifyAdapter)', () => {
url: '/similar/arbitrary',
})
.then(({ payload }) =>
expect(payload).toEqual(
expect(payload).to.be.eql(
JSON.stringify({
success: true,
actual: 1,
@@ -427,188 +398,4 @@ describe('Middleware (FastifyAdapter)', () => {
await app.close();
});
});
describe('should have data attached in middleware', () => {
@Controller()
class DataController {
@Get('data')
async data(@Req() req: FastifyRequest['raw']) {
return {
success: true,
extras: req?.['raw']?.extras,
pong: req?.['raw']?.headers?.ping,
};
}
@Get('pong')
async pong(@Req() req: FastifyRequest['raw']) {
return { success: true, pong: req?.['raw']?.headers?.ping };
}
@Get('')
async rootPath(@Req() req: FastifyRequest['raw']) {
return { success: true, root: true };
}
@Get('record/:id')
async record(@Req() req: FastifyRequest['raw'], @Param('id') id: string) {
return { success: true, record: id };
}
}
@Module({
controllers: [DataController],
})
class DataModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply((req, res, next) => {
req.extras = { data: 'Data attached in middleware' };
req.headers['ping'] = 'pong';
// When global prefix is set and the route is the root path
if (req.originalUrl === '/api') {
return res.end(JSON.stringify({ success: true, pong: 'pong' }));
}
next();
})
.forRoutes('{*path}');
}
}
beforeEach(async () => {
app = (
await Test.createTestingModule({
imports: [DataModule],
}).compile()
).createNestApplication<NestFastifyApplication>(new FastifyAdapter());
});
it(`GET forRoutes('{*path}') with global prefix (route: /api/pong)`, async () => {
app.setGlobalPrefix('/api');
await app.init();
await app.getHttpAdapter().getInstance().ready();
return app
.inject({
method: 'GET',
url: '/api/pong',
})
.then(({ payload }) =>
expect(payload).toEqual(
JSON.stringify({
success: true,
pong: 'pong',
}),
),
);
});
it(`GET forRoutes('{*path}') with global prefix (route: /api)`, async () => {
app.setGlobalPrefix('/api');
await app.init();
await app.getHttpAdapter().getInstance().ready();
return app
.inject({
method: 'GET',
url: '/api',
})
.then(({ payload }) =>
expect(payload).toEqual(
JSON.stringify({
success: true,
pong: 'pong',
}),
),
);
});
it(`GET forRoutes('{*path}') without prefix config`, async () => {
await app.init();
await app.getHttpAdapter().getInstance().ready();
return app
.inject({
method: 'GET',
url: '/pong',
})
.then(({ payload }) =>
expect(payload).toEqual(
JSON.stringify({
success: true,
pong: 'pong',
}),
),
);
});
it(`GET forRoutes('{*path}') with global prefix and exclude patterns`, async () => {
app.setGlobalPrefix('/api', { exclude: ['/'] });
await app.init();
await app.getHttpAdapter().getInstance().ready();
await request(app.getHttpServer())
.get('/')
.expect(200, { success: true, root: true });
});
it(`GET forRoutes('{*path}') with global prefix and exclude pattern with wildcard`, async () => {
app.setGlobalPrefix('/api', { exclude: ['/record/{*path}'] });
await app.init();
await app.getHttpAdapter().getInstance().ready();
await request(app.getHttpServer())
.get('/api/pong')
.expect(200, { success: true, pong: 'pong' });
await request(app.getHttpServer())
.get('/record/abc123')
.expect(200, { success: true, record: 'abc123' });
});
it(`GET forRoutes('{*path}') with global prefix and exclude pattern with parameter`, async () => {
app.setGlobalPrefix('/api', { exclude: ['/record/:id'] });
await app.init();
await app.getHttpAdapter().getInstance().ready();
await request(app.getHttpServer())
.get('/record/abc123')
.expect(200, { success: true, record: 'abc123' });
await request(app.getHttpServer())
.get('/api/pong')
.expect(200, { success: true, pong: 'pong' });
});
it(`GET forRoutes('{*path}') with global prefix and global prefix options`, async () => {
app.setGlobalPrefix('/api', { exclude: ['/'] });
await app.init();
await app.getHttpAdapter().getInstance().ready();
await request(app.getHttpServer())
.get('/api/data')
.expect(200, {
success: true,
extras: { data: 'Data attached in middleware' },
pong: 'pong',
});
await request(app.getHttpServer())
.get('/')
.expect(200, { success: true, root: true });
});
it(`GET forRoutes('{*path}') with global prefix that not starts with /`, async () => {
app.setGlobalPrefix('api');
await app.init();
await app.getHttpAdapter().getInstance().ready();
await request(app.getHttpServer())
.get('/api/data')
.expect(200, {
success: true,
extras: { data: 'Data attached in middleware' },
pong: 'pong',
});
await request(app.getHttpServer()).get('/').expect(404);
});
afterEach(async () => {
await app.close();
});
});
});

View File

@@ -7,8 +7,10 @@ import {
NestMiddleware,
Module,
} from '@nestjs/common';
import { Test } from '../../../packages/testing.js';
import request from 'supertest';
import { Test } from '../../../packages/testing';
import * as request from 'supertest';
import { expect } from 'chai';
/**
* Number of times that the middleware was executed.
*/

View File

@@ -6,14 +6,14 @@ import {
Module,
RequestMethod,
Version,
VERSION_NEUTRAL,
VersioningOptions,
VersioningType,
VERSION_NEUTRAL,
} from '@nestjs/common';
import { CustomVersioningOptions } from '@nestjs/common/interfaces/index.js';
import { CustomVersioningOptions } from '@nestjs/common/interfaces';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { AppModule } from '../src/app.module.js';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
const RETURN_VALUE = 'test';
const VERSIONED_VALUE = 'test_versioned';

View File

@@ -6,8 +6,8 @@ import {
Module,
} from '@nestjs/common';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { AppModule } from '../src/app.module.js';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
const RETURN_VALUE = 'test';
const SCOPED_VALUE = 'test_scoped';
@@ -26,11 +26,6 @@ class TestController {
return RETURN_VALUE;
}
@Get('legacy-wildcard/overview')
legacyWildcard() {
return RETURN_VALUE;
}
@Get('exclude')
exclude() {
return EXCLUDE_VALUE;
@@ -45,7 +40,7 @@ class TestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply((req, res, next) => res.send(WILDCARD_VALUE))
.forRoutes('tests/*path', 'legacy-wildcard/*')
.forRoutes('tests/*')
.apply((req, res, next) => res.send(SCOPED_VALUE))
.exclude('exclude')
.forRoutes(TestController)
@@ -91,13 +86,6 @@ describe('Middleware', () => {
.expect(200, WILDCARD_VALUE);
});
it(`forRoutes(legacy-wildcard/*)`, async () => {
app = await createApp();
return request(app.getHttpServer())
.get('/legacy-wildcard/overview')
.expect(200, WILDCARD_VALUE);
});
afterEach(async () => {
await app.close();
});

View File

@@ -7,8 +7,8 @@ import {
} from '@nestjs/common';
import { RouterModule } from '@nestjs/core';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { AppModule } from '../src/app.module.js';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
const RETURN_VALUE = 'test';
const SCOPED_VALUE = 'test_scoped';

View File

@@ -1,7 +1,7 @@
import { Controller, Get, INestApplication, Module } from '@nestjs/common';
import { RouterModule, Routes } from '@nestjs/core';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import * as request from 'supertest';
describe('RouterModule', () => {
let app: INestApplication;
@@ -66,7 +66,7 @@ describe('RouterModule', () => {
})
class AppModule {}
beforeAll(async () => {
before(async () => {
const moduleRef = await Test.createTestingModule({
imports: [MainModule, AppModule],
}).compile();

View File

@@ -1,166 +0,0 @@
import {
ArgumentMetadata,
Body,
Controller,
createParamDecorator,
ExecutionContext,
Get,
INestApplication,
Injectable,
Module,
Param,
PipeTransform,
Post,
Query,
} from '@nestjs/common';
import { Test } from '@nestjs/testing';
import type { StandardSchemaV1 } from '@standard-schema/spec';
import request from 'supertest';
const testSchema: StandardSchemaV1 = {
'~standard': {
version: 1,
vendor: 'test',
validate: (value: unknown) => ({ value }),
},
};
/**
* A pipe that captures the ArgumentMetadata it receives,
* so the test can verify that `schema` is propagated.
*/
@Injectable()
class SchemaCaptorPipe implements PipeTransform {
static lastMetadata: ArgumentMetadata | undefined;
transform(value: any, metadata: ArgumentMetadata) {
SchemaCaptorPipe.lastMetadata = metadata;
return value;
}
}
const CustomParam = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
return ctx.switchToHttp().getRequest().query;
},
);
@Controller('schema-test')
class SchemaTestController {
@Post('body')
bodyWithSchema(
@Body({ schema: testSchema, pipes: [SchemaCaptorPipe] }) body: any,
) {
return { received: body };
}
@Get('query')
queryWithSchema(
@Query({ schema: testSchema, pipes: [SchemaCaptorPipe] }) query: any,
) {
return { received: query };
}
@Get('param/:id')
paramWithSchema(
@Param('id', { schema: testSchema, pipes: [SchemaCaptorPipe] }) id: string,
) {
return { received: id };
}
@Get('custom')
customWithSchema(
@CustomParam({ schema: testSchema, pipes: [SchemaCaptorPipe] }) value: any,
) {
return { received: value };
}
@Post('body-property')
bodyPropertyWithSchema(
@Body('name', { schema: testSchema, pipes: [SchemaCaptorPipe] })
name: string,
) {
return { received: name };
}
}
@Module({
controllers: [SchemaTestController],
providers: [SchemaCaptorPipe],
})
class SchemaTestModule {}
describe('Schema propagation to pipes', () => {
let app: INestApplication;
let server: any;
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [SchemaTestModule],
}).compile();
app = module.createNestApplication();
server = app.getHttpServer();
await app.init();
});
beforeEach(() => {
SchemaCaptorPipe.lastMetadata = undefined;
});
afterAll(async () => {
await app.close();
});
it('should pass schema to pipe via @Body(options)', async () => {
await request(server)
.post('/schema-test/body')
.send({ name: 'test' })
.expect(201);
expect(SchemaCaptorPipe.lastMetadata).toBeDefined();
expect(SchemaCaptorPipe.lastMetadata!.schema).toBe(testSchema);
expect(SchemaCaptorPipe.lastMetadata!.type).toBe('body');
});
it('should pass schema to pipe via @Query(options)', async () => {
await request(server).get('/schema-test/query?user=john').expect(200);
expect(SchemaCaptorPipe.lastMetadata).toBeDefined();
expect(SchemaCaptorPipe.lastMetadata!.schema).toBe(testSchema);
expect(SchemaCaptorPipe.lastMetadata!.type).toBe('query');
});
it('should pass schema to pipe via @Param(property, options)', async () => {
await request(server)
.get('/schema-test/param/42')
.expect(200)
.expect({ received: '42' });
expect(SchemaCaptorPipe.lastMetadata).toBeDefined();
expect(SchemaCaptorPipe.lastMetadata!.schema).toBe(testSchema);
expect(SchemaCaptorPipe.lastMetadata!.type).toBe('param');
expect(SchemaCaptorPipe.lastMetadata!.data).toBe('id');
});
it('should pass schema to pipe via createParamDecorator(options)', async () => {
await request(server).get('/schema-test/custom?key=val').expect(200);
expect(SchemaCaptorPipe.lastMetadata).toBeDefined();
expect(SchemaCaptorPipe.lastMetadata!.schema).toBe(testSchema);
expect(SchemaCaptorPipe.lastMetadata!.type).toBe('custom');
});
it('should pass schema to pipe via @Body(property, options)', async () => {
await request(server)
.post('/schema-test/body-property')
.send({ name: 'Alice' })
.expect(201)
.expect({ received: 'Alice' });
expect(SchemaCaptorPipe.lastMetadata).toBeDefined();
expect(SchemaCaptorPipe.lastMetadata!.schema).toBe(testSchema);
expect(SchemaCaptorPipe.lastMetadata!.type).toBe('body');
expect(SchemaCaptorPipe.lastMetadata!.data).toBe('name');
});
});

View File

@@ -1,316 +0,0 @@
import {
Controller,
Get,
INestApplication,
Module,
SerializeOptions,
StandardSchemaSerializerInterceptor,
UseInterceptors,
} from '@nestjs/common';
import { APP_INTERCEPTOR, Reflector } from '@nestjs/core';
import { Test } from '@nestjs/testing';
import type { StandardSchemaV1 } from '@standard-schema/spec';
import request from 'supertest';
// ─── Test schemas ──────────────────────────────────────────────
/**
* Schema that strips out the `password` field (simulating a "safe user" DTO).
*/
const safeUserSchema: StandardSchemaV1 = {
'~standard': {
version: 1,
vendor: 'test',
validate: (value: unknown) => {
const { password, ...safe } = value as Record<string, unknown>;
return { value: safe };
},
},
};
/**
* Schema that adds a `serialized: true` flag (useful for asserting the schema ran).
*/
const flagSchema: StandardSchemaV1 = {
'~standard': {
version: 1,
vendor: 'test',
validate: (value: unknown) => ({
value: { ...(value as any), serialized: true },
}),
},
};
/**
* Schema that always fails — used for the error case.
*/
const failingSchema: StandardSchemaV1 = {
'~standard': {
version: 1,
vendor: 'test',
validate: () => ({
issues: [{ message: 'not allowed' }],
}),
},
};
/**
* Async schema — validates that the interceptor awaits promises.
*/
const asyncSchema: StandardSchemaV1 = {
'~standard': {
version: 1,
vendor: 'test',
validate: async (value: unknown) => ({
value: { ...(value as any), async: true },
}),
},
};
// ─── Controllers ───────────────────────────────────────────────
@Controller('serializer')
@UseInterceptors(StandardSchemaSerializerInterceptor)
class SerializerTestController {
@Get('user')
@SerializeOptions({ schema: safeUserSchema })
getUser() {
return { id: 1, name: 'Alice', password: 'secret123' };
}
@Get('users')
@SerializeOptions({ schema: safeUserSchema })
getUsers() {
return [
{ id: 1, name: 'Alice', password: 'pw1' },
{ id: 2, name: 'Bob', password: 'pw2' },
];
}
@Get('flagged')
@SerializeOptions({ schema: flagSchema })
getFlagged() {
return { id: 1 };
}
@Get('no-schema')
getNoSchema() {
return { id: 1, secret: 'visible' };
}
@Get('failing')
@SerializeOptions({ schema: failingSchema })
getFailing() {
return { id: 1 };
}
@Get('async')
@SerializeOptions({ schema: asyncSchema })
getAsync() {
return { id: 1 };
}
@Get('primitive')
@SerializeOptions({ schema: failingSchema })
getPrimitive() {
return 'plain string';
}
}
/**
* Controller-level schema applied via class decorator — all routes inherit it.
*/
@Controller('class-level')
@UseInterceptors(StandardSchemaSerializerInterceptor)
@SerializeOptions({ schema: safeUserSchema })
class ClassLevelSerializerController {
@Get('user')
getUser() {
return { id: 1, name: 'Carol', password: 'secret' };
}
@Get('override')
@SerializeOptions({ schema: flagSchema })
getOverride() {
return { id: 1, name: 'Carol', password: 'secret' };
}
}
/**
* Controller demonstrating global interceptor registration with a default schema.
*/
@Controller('global')
class GlobalSerializerController {
@Get('default')
getDefault() {
return { id: 1, name: 'Dave', password: 'global-secret' };
}
@Get('override')
@SerializeOptions({ schema: flagSchema })
getOverride() {
return { id: 1 };
}
}
@Module({
controllers: [SerializerTestController, ClassLevelSerializerController],
})
class SerializerTestModule {}
@Module({
controllers: [GlobalSerializerController],
providers: [
{
provide: APP_INTERCEPTOR,
useFactory: (reflector: Reflector) =>
new StandardSchemaSerializerInterceptor(reflector, {
schema: safeUserSchema,
}),
inject: [Reflector],
},
],
})
class GlobalSerializerTestModule {}
// ─── Tests ─────────────────────────────────────────────────────
describe('StandardSchemaSerializerInterceptor (integration)', () => {
let app: INestApplication;
afterEach(async () => {
await app.close();
});
describe('handler-level @SerializeOptions', () => {
beforeEach(async () => {
const module = await Test.createTestingModule({
imports: [SerializerTestModule],
}).compile();
app = module.createNestApplication();
await app.init();
});
it('should strip fields via schema on a single object', () => {
return request(app.getHttpServer())
.get('/serializer/user')
.expect(200)
.expect(({ body }) => {
expect(body).toEqual({ id: 1, name: 'Alice' });
expect(body).not.toHaveProperty('password');
});
});
it('should apply schema to each item in an array response', () => {
return request(app.getHttpServer())
.get('/serializer/users')
.expect(200)
.expect(({ body }) => {
expect(body).toEqual([
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
]);
body.forEach((item: any) =>
expect(item).not.toHaveProperty('password'),
);
});
});
it('should augment response through the schema', () => {
return request(app.getHttpServer())
.get('/serializer/flagged')
.expect(200)
.expect({ id: 1, serialized: true });
});
it('should return response unchanged when no schema is set', () => {
return request(app.getHttpServer())
.get('/serializer/no-schema')
.expect(200)
.expect({ id: 1, secret: 'visible' });
});
it('should return 500 when schema validation fails', () => {
return request(app.getHttpServer())
.get('/serializer/failing')
.expect(500);
});
it('should handle async schemas', () => {
return request(app.getHttpServer())
.get('/serializer/async')
.expect(200)
.expect({ id: 1, async: true });
});
it('should pass primitive values through even when a schema is set', () => {
return request(app.getHttpServer())
.get('/serializer/primitive')
.expect(200)
.expect(({ text }) => {
expect(text).toBe('plain string');
});
});
});
describe('class-level @SerializeOptions', () => {
beforeEach(async () => {
const module = await Test.createTestingModule({
imports: [SerializerTestModule],
}).compile();
app = module.createNestApplication();
await app.init();
});
it('should apply class-level schema to all routes', () => {
return request(app.getHttpServer())
.get('/class-level/user')
.expect(200)
.expect(({ body }) => {
expect(body).toEqual({ id: 1, name: 'Carol' });
expect(body).not.toHaveProperty('password');
});
});
it('should allow handler-level schema to override class-level', () => {
return request(app.getHttpServer())
.get('/class-level/override')
.expect(200)
.expect(({ body }) => {
// flagSchema adds `serialized: true` but does NOT strip password
expect(body).toHaveProperty('serialized', true);
expect(body).toHaveProperty('password', 'secret');
});
});
});
describe('global interceptor with default schema', () => {
beforeEach(async () => {
const module = await Test.createTestingModule({
imports: [GlobalSerializerTestModule],
}).compile();
app = module.createNestApplication();
await app.init();
});
it('should apply the default schema globally', () => {
return request(app.getHttpServer())
.get('/global/default')
.expect(200)
.expect(({ body }) => {
expect(body).toEqual({ id: 1, name: 'Dave' });
expect(body).not.toHaveProperty('password');
});
});
it('should let @SerializeOptions override the global default', () => {
return request(app.getHttpServer())
.get('/global/override')
.expect(200)
.expect({ id: 1, serialized: true });
});
});
});

Some files were not shown because too many files have changed in this diff Show More