diff --git a/.github/workflows/lighthouse.yml b/.github/workflows/lighthouse.yml index fbd55114d0..eb57f6c89d 100644 --- a/.github/workflows/lighthouse.yml +++ b/.github/workflows/lighthouse.yml @@ -10,11 +10,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Use Node.js 20.x - uses: actions/setup-node@v4 + - uses: actions/checkout@v5 + - name: Use Node.js 22.x + uses: actions/setup-node@v5 with: - node-version: 20.x + node-version: 22.x # Build needs around 400 seconds on Netlify. # We can not rely on "Waiting for 200", since the user # might push another commit to an existing PR, meaning @@ -28,7 +28,7 @@ jobs: site_name: 'docs-nestjs' max_timeout: 600 - name: Run Lighthouse on urls and validate with lighthouserc - uses: treosh/lighthouse-ci-action@v8 + uses: treosh/lighthouse-ci-action@v12 with: urls: | ${{ steps.wait-for-netflify-preview.outputs.url }} diff --git a/LICENSE b/LICENSE index bdf11c44a1..ad0b9f46a4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ (The MIT License) -Copyright (c) 2017-2023 Kamil Myśliwiec +Copyright (c) 2017-2025 Kamil Myśliwiec Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/content/cli/workspaces.md b/content/cli/workspaces.md index 06ad6e9242..930dacba97 100644 --- a/content/cli/workspaces.md +++ b/content/cli/workspaces.md @@ -3,7 +3,7 @@ Nest has two modes for organizing code: - **standard mode**: useful for building individual project-focused applications that have their own dependencies and settings, and don't need to optimize for sharing modules, or optimizing complex builds. This is the default mode. -- **monorepo mode**: this mode treats code artifacts as part of a lightweight **monorepo**, and may be more appropriate for teams of developers and/or multi-project environments. It automates parts of the build process to make it easy to create and compose modular components, promotes code re-use, makes integration testing easier, makes it easy to share project-wide artifacts like `eslint` rules and other configuration policies, and is easier to use than alternatives like github submodules. Monorepo mode employs the concept of a **workspace**, represented in the `nest-cli.json` file, to coordinate the relationship between the components of the monorepo. +- **monorepo mode**: this mode treats code artifacts as part of a lightweight **monorepo**, and may be more appropriate for teams of developers and/or multi-project environments. It automates parts of the build process to make it easy to create and compose modular components, promotes code re-use, makes integration testing easier, makes it easy to share project-wide artifacts like `eslint` rules and other configuration policies, and is easier to use than alternatives like Git submodules. Monorepo mode employs the concept of a **workspace**, represented in the `nest-cli.json` file, to coordinate the relationship between the components of the monorepo. It's important to note that virtually all of Nest's features are independent of your code organization mode. The **only** effect of this choice is how your projects are composed and how build artifacts are generated. All other functionality, from the CLI to core modules to add-on modules work the same in either mode. diff --git a/content/controllers.md b/content/controllers.md index c7a0583e29..0bc1cec08a 100644 --- a/content/controllers.md +++ b/content/controllers.md @@ -78,7 +78,7 @@ Handlers often need access to the client’s **request** details. Nest provides ```typescript @@filename(cats.controller) import { Controller, Get, Req } from '@nestjs/common'; -import { Request } from 'express'; +import type { Request } from 'express'; @Controller('cats') export class CatsController { @@ -319,7 +319,7 @@ export class AdminController { > warning **Warning** Since **Fastify** does not support nested routers, if you are using sub-domain routing, it is recommended to use the default Express adapter instead. -Similar to a route `path`, the `hosts` option can use tokens to capture the dynamic value at that position in the host name. The host parameter token in the `@Controller()` decorator example below demonstrates this usage. Host parameters declared in this way can be accessed using the `@HostParam()` decorator, which should be added to the method signature. +Similar to a route `path`, the `host` option can use tokens to capture the dynamic value at that position in the host name. The host parameter token in the `@Controller()` decorator example below demonstrates this usage. Host parameters declared in this way can be accessed using the `@HostParam()` decorator, which should be added to the method signature. ```typescript @Controller({ host: ':account.example.com' }) diff --git a/content/discover/who-uses.json b/content/discover/who-uses.json index 4ae3490a58..092ddcada7 100644 --- a/content/discover/who-uses.json +++ b/content/discover/who-uses.json @@ -180,6 +180,8 @@ "https://devitjobs.com", "https://formation.tech", "https://gofirmex.com", - "https://vbdhub.org" + "https://vbdhub.org", + "https://www.arven-tech.com", + "https://runsystem.net" ] } diff --git a/content/fundamentals/circular-dependency.md b/content/fundamentals/circular-dependency.md index 3c0da68418..c0c133b8ab 100644 --- a/content/fundamentals/circular-dependency.md +++ b/content/fundamentals/circular-dependency.md @@ -6,7 +6,7 @@ While circular dependencies should be avoided where possible, you can't always d We also describe resolving circular dependencies between modules. -> warning **Warning** A circular dependency might also be caused when using "barrel files"/index.ts files to group imports. Barrel files should be omitted when it comes to module/provider classes. For example, barrel files should not be used when importing files within the same directory as the barrel file, i.e. `cats/cats.controller` should not import `cats` to import the `cats/cats.service` file. For more details please also see [this github issue](https://github.com/nestjs/nest/issues/1181#issuecomment-430197191). +> warning **Warning** A circular dependency might also be caused when using "barrel files"/index.ts files to group imports. Barrel files should be omitted when it comes to module/provider classes. For example, barrel files should not be used when importing files within the same directory as the barrel file, i.e. `cats/cats.controller` should not import `cats` to import the `cats/cats.service` file. For more details please also see [this GitHub issue](https://github.com/nestjs/nest/issues/1181#issuecomment-430197191). #### Forward reference diff --git a/content/fundamentals/dynamic-modules.md b/content/fundamentals/dynamic-modules.md index f1a3a8731e..0fcbeec0ab 100644 --- a/content/fundamentals/dynamic-modules.md +++ b/content/fundamentals/dynamic-modules.md @@ -181,9 +181,9 @@ That nicely handles passing an `options` object to our dynamic module. How do we ```typescript import { Injectable } from '@nestjs/common'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; import * as dotenv from 'dotenv'; -import * as fs from 'fs'; -import * as path from 'path'; import { EnvConfig } from './interfaces'; @Injectable() @@ -235,9 +235,9 @@ export class ConfigModule { Now we can complete the process by injecting the `'CONFIG_OPTIONS'` provider into the `ConfigService`. Recall that when we define a provider using a non-class token we need to use the `@Inject()` decorator [as described here](https://docs.nestjs.com/fundamentals/custom-providers#non-class-based-provider-tokens). ```typescript +import * as fs from 'node:fs'; +import * as path from 'node:path'; import * as dotenv from 'dotenv'; -import * as fs from 'fs'; -import * as path from 'path'; import { Injectable, Inject } from '@nestjs/common'; import { EnvConfig } from './interfaces'; @@ -343,6 +343,45 @@ Extending the `ConfigurableModuleClass` means that `ConfigModule` provides now n export class AppModule {} ``` +The `registerAsync` method takes the following object as an argument: + +```typescript +{ + /** + * Injection token resolving to a class that will be instantiated as a provider. + * The class must implement the corresponding interface. + */ + useClass?: Type< + ConfigurableModuleOptionsFactory +>; + /** + * Function returning options (or a Promise resolving to options) to configure the + * module. + */ + useFactory?: (...args: any[]) => Promise | ModuleOptions; + /** + * Dependencies that a Factory may inject. + */ + inject?: FactoryProvider['inject']; + /** + * Injection token resolving to an existing provider. The provider must implement + * the corresponding interface. + */ + useExisting?: Type< + ConfigurableModuleOptionsFactory +>; +} +``` + +Let's go through the above properties one by one: + +- `useFactory` - a function that returns the configuration object. It can be either synchronous or asynchronous. To inject dependencies into the factory function, use the `inject` property. We used this variant in the example above. +- `inject` - an array of dependencies that will be injected into the factory function. The order of the dependencies must match the order of the parameters in the factory function. +- `useClass` - a class that will be instantiated as a provider. The class must implement the corresponding interface. Typically, this is a class that provides a `create()` method that returns the configuration object. Read more about this in the [Custom method key](/fundamentals/dynamic-modules#custom-method-key) section below. +- `useExisting` - a variant of `useClass` that allows you to use an existing provider instead of instructing Nest to create a new instance of the class. This is useful when you want to use a provider that is already registered in the module. Keep in mind that the class must implement the same interface as the one used in `useClass` (and so it must provide the `create()` method, unless you override the default method name, see [Custom method key](/fundamentals/dynamic-modules#custom-method-key) section below). + +Always choose one of the above options (`useFactory`, `useClass`, or `useExisting`), as they are mutually exclusive. + Lastly, let's update the `ConfigService` class to inject the generated module options' provider instead of the `'CONFIG_OPTIONS'` that we used so far. ```typescript @@ -431,17 +470,18 @@ There are edge-cases when your module may need to take extra options that determ In such cases, the `ConfigurableModuleBuilder#setExtras` method can be used. See the following example: ```typescript -export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModuleBuilder() - .setExtras( - { - isGlobal: true, - }, - (definition, extras) => ({ - ...definition, - global: extras.isGlobal, - }), - ) - .build(); +export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = + new ConfigurableModuleBuilder() + .setExtras( + { + isGlobal: true, + }, + (definition, extras) => ({ + ...definition, + global: extras.isGlobal, + }), + ) + .build(); ``` In the example above, the first argument passed into the `setExtras` method is an object containing default values for the "extra" properties. The second argument is a function that takes an auto-generated module definitions (with `provider`, `exports`, etc.) and `extras` object which represents extra properties (either specified by the consumer or defaults). The returned value of this function is a modified module definition. In this specific example, we're taking the `extras.isGlobal` property and assigning it to the `global` property of the module definition (which in turn determines whether a module is global or not, read more [here](/modules#dynamic-modules)). @@ -465,7 +505,9 @@ However, since `isGlobal` is declared as an "extra" property, it won't be availa ```typescript @Injectable() export class ConfigService { - constructor(@Inject(MODULE_OPTIONS_TOKEN) private options: ConfigModuleOptions) { + constructor( + @Inject(MODULE_OPTIONS_TOKEN) private options: ConfigModuleOptions, + ) { // "options" object will not have the "isGlobal" property // ... } @@ -479,7 +521,11 @@ The auto-generated static methods (`register`, `registerAsync`, etc.) can be ext ```typescript import { Module } from '@nestjs/common'; import { ConfigService } from './config.service'; -import { ConfigurableModuleClass, ASYNC_OPTIONS_TYPE, OPTIONS_TYPE } from './config.module-definition'; +import { + ConfigurableModuleClass, + ASYNC_OPTIONS_TYPE, + OPTIONS_TYPE, +} from './config.module-definition'; @Module({ providers: [ConfigService], @@ -505,5 +551,10 @@ export class ConfigModule extends ConfigurableModuleClass { Note the use of `OPTIONS_TYPE` and `ASYNC_OPTIONS_TYPE` types that must be exported from the module definition file: ```typescript -export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE, ASYNC_OPTIONS_TYPE } = new ConfigurableModuleBuilder().build(); +export const { + ConfigurableModuleClass, + MODULE_OPTIONS_TOKEN, + OPTIONS_TYPE, + ASYNC_OPTIONS_TYPE, +} = new ConfigurableModuleBuilder().build(); ``` diff --git a/content/fundamentals/unit-testing.md b/content/fundamentals/unit-testing.md index f6096c6aab..1721ee3cd6 100644 --- a/content/fundamentals/unit-testing.md +++ b/content/fundamentals/unit-testing.md @@ -166,7 +166,7 @@ Nest also allows you to define a mock factory to apply to all of your missing de ```typescript // ... -import { ModuleMocker, MockFunctionMetadata } from 'jest-mock'; +import { ModuleMocker, MockMetadata } from 'jest-mock'; const moduleMocker = new ModuleMocker(global); @@ -185,8 +185,10 @@ describe('CatsController', () => { if (typeof token === 'function') { const mockMetadata = moduleMocker.getMetadata( token, - ) as MockFunctionMetadata; - const Mock = moduleMocker.generateFromMetadata(mockMetadata); + ) as MockMetadata; + const Mock = moduleMocker.generateFromMetadata( + mockMetadata, + ) as ObjectConstructor; return new Mock(); } }) diff --git a/content/graphql/quick-start.md b/content/graphql/quick-start.md index f68f65c997..d25f67e793 100644 --- a/content/graphql/quick-start.md +++ b/content/graphql/quick-start.md @@ -12,7 +12,7 @@ Start by installing the required packages: ```bash # For Express and Apollo (default) -$ npm i @nestjs/graphql @nestjs/apollo @apollo/server graphql +$ npm i @nestjs/graphql @nestjs/apollo @apollo/server @as-integrations/express5 graphql # For Fastify and Apollo # npm i @nestjs/graphql @nestjs/apollo @apollo/server @as-integrations/fastify graphql @@ -88,19 +88,18 @@ With that in place, and with your application running in the background, you can -> warning **Note** `@nestjs/mercurius` integration does not ship with the built-in GraphQL Playground integration. Instead, you can use [GraphiQL](https://github.com/graphql/graphiql) (set `graphiql: true`). +> info **Note** `@nestjs/mercurius` integration does not ship with the built-in GraphQL Playground integration. Instead, you can use [GraphiQL](https://github.com/graphql/graphiql) (set `graphiql: true`). -#### Multiple endpoints - -Another useful feature of the `@nestjs/graphql` module is the ability to serve multiple endpoints at once. This lets you decide which modules should be included in which endpoint. By default, `GraphQL` searches for resolvers throughout the whole app. To limit this scan to only a subset of modules, use the `include` property. - -```typescript -GraphQLModule.forRoot({ - include: [CatsModule], -}), -``` - -> warning **Warning** If you use the `@apollo/server` with `@as-integrations/fastify` package with multiple GraphQL endpoints in a single application, make sure to enable the `disableHealthCheck` setting in the `GraphQLModule` configuration. +> warning **Warning** Update (04/14/2025): The default Apollo playground has been deprecated and will be removed in the next major release. Instead, you can use [GraphiQL](https://github.com/graphql/graphiql), just set `graphiql: true` in the `GraphQLModule` configuration, as shown below: +> +> ```typescript +> GraphQLModule.forRoot({ +> driver: ApolloDriver, +> graphiql: true, +> }), +> ``` +> +> If your application uses [subscriptions](/graphql/subscriptions), be sure to use `graphql-ws`, as `subscriptions-transport-ws` isn't supported by GraphiQL. #### Code first @@ -178,7 +177,7 @@ The above approach dynamically generates TypeScript definitions each time the ap ```typescript import { GraphQLDefinitionsFactory } from '@nestjs/graphql'; -import { join } from 'path'; +import { join } from 'node:path'; const definitionsFactory = new GraphQLDefinitionsFactory(); definitionsFactory.generate({ @@ -356,6 +355,18 @@ export class AppModule {} The `forRoot()` method takes an options object as an argument. These options are passed through to the underlying driver instance. Read more about available settings [here](https://github.com/mercurius-js/mercurius/blob/master/docs/api/options.md#plugin-options). +#### Multiple endpoints + +Another useful feature of the `@nestjs/graphql` module is the ability to serve multiple endpoints at once. This lets you decide which modules should be included in which endpoint. By default, `GraphQL` searches for resolvers throughout the whole app. To limit this scan to only a subset of modules, use the `include` property. + +```typescript +GraphQLModule.forRoot({ + include: [CatsModule], +}), +``` + +> warning **Warning** If you use the `@apollo/server` with `@as-integrations/fastify` package with multiple GraphQL endpoints in a single application, make sure to enable the `disableHealthCheck` setting in the `GraphQLModule` configuration. + #### Third-party integrations - [GraphQL Yoga](https://github.com/dotansimha/graphql-yoga) diff --git a/content/graphql/resolvers-map.md b/content/graphql/resolvers-map.md index 03e69222d0..8cf6884ecb 100644 --- a/content/graphql/resolvers-map.md +++ b/content/graphql/resolvers-map.md @@ -583,12 +583,12 @@ Assuming that we use the schema first approach and have enabled the typings gene ```typescript @@filename(graphql) -export (class Author { +export class Author { id: number; firstName?: string; lastName?: string; posts?: Post[]; -}) +} export class Post { id: number; title: string; diff --git a/content/microservices/basics.md b/content/microservices/basics.md index 7d54fb4eb1..0de4f056df 100644 --- a/content/microservices/basics.md +++ b/content/microservices/basics.md @@ -87,7 +87,7 @@ The second argument of the `createMicroservice()` method is an `options` object. serializer - Custom serializer for outcoming messages + Custom serializer for outgoing messages deserializer diff --git a/content/microservices/custom-transport.md b/content/microservices/custom-transport.md index 4ec054b30a..8ca6c847da 100644 --- a/content/microservices/custom-transport.md +++ b/content/microservices/custom-transport.md @@ -192,7 +192,13 @@ class GoogleCloudPubSubClient extends ClientProxy { // In a real-world application, the "callback" function should be executed // with payload sent back from the responder. Here, we'll simply simulate (5 seconds delay) // that response came through by passing the same "data" as we've originally passed in. - setTimeout(() => callback({ response: packet.data }), 5000); + // + // The "isDisposed" bool on the WritePacket tells the response that no further data is + // expected. If not sent or is false, this will simply emit data to the Observable. + setTimeout(() => callback({ + response: packet.data, + isDisposed: true, + }), 5000); return () => console.log('teardown'); } diff --git a/content/microservices/grpc.md b/content/microservices/grpc.md index 4b267f17f7..f116ac3cc2 100644 --- a/content/microservices/grpc.md +++ b/content/microservices/grpc.md @@ -507,6 +507,42 @@ lotsOfGreetings(requestStream: any, callback: (err: unknown, value: HelloRespons Here we used the `callback` function to send the response once processing of the `requestStream` has been completed. +#### Health checks + +When running a gRPC application in an orchestrator such a Kubernetes, you may need to know if it is running and in a healthy state. The [gRPC Health Check specification](https://grpc.io/docs/guides/health-checking/) is a standard that allow gRPC clients to expose their health status to allow the orchestrator to act accordingly. + +To add gRPC health check support, first install the [grpc-node](https://github.com/grpc/grpc-node/tree/master/packages/grpc-health-check) package: + +```bash +$ npm i --save grpc-health-check +``` + +Then it can be hooked into the gRPC service using the `onLoadPackageDefinition` hook in your gRPC server options, as follows. Note that the `protoPath` needs to have both the health check and the hero package. + +```typescript +@@filename(main) +import { HealthImplementation, protoPath as healthCheckProtoPath } from 'grpc-health-check'; + +const app = await NestFactory.createMicroservice(AppModule, { + options: { + protoPath: [ + healthCheckProtoPath, + protoPath: join(__dirname, 'hero/hero.proto'), + ], + onLoadPackageDefinition: (pkg, server) => { + const healthImpl = new HealthImplementation({ + '': 'UNKNOWN', + }); + + healthImpl.addToServer(server); + healthImpl.setStatus('', 'SERVING'); + }, + }, +}); +``` + +> info **Hint** The [gRPC health probe](https://github.com/grpc-ecosystem/grpc-health-probe) is a useful CLI to test gRPC health checks in a containerized environment. + #### gRPC Metadata Metadata is information about a particular RPC call in the form of a list of key-value pairs, where the keys are strings and the values are typically strings but can be binary data. Metadata is opaque to gRPC itself - it lets the client provide information associated with the call to the server and vice versa. Metadata may include authentication tokens, request identifiers and tags for monitoring purposes, and data information such as the number of records in a data set. diff --git a/content/microservices/kafka.md b/content/microservices/kafka.md index f6ab7a9dce..2660093a67 100644 --- a/content/microservices/kafka.md +++ b/content/microservices/kafka.md @@ -171,7 +171,7 @@ client: ClientKafkaProxy; #### Message pattern -The Kafka microservice message pattern utilizes two topics for the request and reply channels. The `ClientKafkaProxy#send()` method sends messages with a [return address](https://www.enterpriseintegrationpatterns.com/patterns/messaging/ReturnAddress.html) by associating a [correlation id](https://www.enterpriseintegrationpatterns.com/patterns/messaging/CorrelationIdentifier.html), reply topic, and reply partition with the request message. This requires the `ClientKafkaProxy` instance to be subscribed to the reply topic and assigned to at least one partition before sending a message. +The Kafka microservice message pattern utilizes two topics for the request and reply channels. The `ClientKafkaProxy.send()` method sends messages with a [return address](https://www.enterpriseintegrationpatterns.com/patterns/messaging/ReturnAddress.html) by associating a [correlation id](https://www.enterpriseintegrationpatterns.com/patterns/messaging/CorrelationIdentifier.html), reply topic, and reply partition with the request message. This requires the `ClientKafkaProxy` instance to be subscribed to the reply topic and assigned to at least one partition before sending a message. Subsequently, you need to have at least one reply topic partition for every Nest application running. For example, if you are running 4 Nest applications but the reply topic only has 3 partitions, then 1 of the Nest applications will error out when trying to send a message. @@ -183,7 +183,7 @@ To prevent the `ClientKafkaProxy` consumers from losing response messages, a Nes #### Message response subscription -> warning **Note** This section is only relevant if you use [request-response](/microservices/basics#request-response) message style (with the `@MessagePattern` decorator and the `ClientKafkaProxy#send` method). Subscribing to the response topic is not necessary for the [event-based](/microservices/basics#event-based) communication (`@EventPattern` decorator and `ClientKafkaProxy#emit` method). +> warning **Note** This section is only relevant if you use [request-response](/microservices/basics#request-response) message style (with the `@MessagePattern` decorator and the `ClientKafkaProxy.send` method). Subscribing to the response topic is not necessary for the [event-based](/microservices/basics#event-based) communication (`@EventPattern` decorator and `ClientKafkaProxy.emit` method). The `ClientKafkaProxy` class provides the `subscribeToResponseOf()` method. The `subscribeToResponseOf()` method takes a request's topic name as an argument and adds the derived reply topic name to a collection of reply topics. This method is required when implementing the message pattern. @@ -617,7 +617,7 @@ server.status.subscribe((status: KafkaStatus) => { #### Underlying producer and consumer -For more advanced use cases, you may need to access the underlying prodocuer and consumer instances. This can be useful for scenarios like manually closing the connection or using driver-specific methods. However, keep in mind that for most cases, you **shouldn't need** to access the driver directly. +For more advanced use cases, you may need to access the underlying producer and consumer instances. This can be useful for scenarios like manually closing the connection or using driver-specific methods. However, keep in mind that for most cases, you **shouldn't need** to access the driver directly. To do so, you can use `producer` and `consumer` getters exposed by the `ClientKafkaProxy` instance. diff --git a/content/microservices/rabbitmq.md b/content/microservices/rabbitmq.md index e88b722c36..a95e15a9ac 100644 --- a/content/microservices/rabbitmq.md +++ b/content/microservices/rabbitmq.md @@ -48,7 +48,7 @@ The `options` property is specific to the chosen transporter. The Rabbit
urls - Connection urls + An array of connection URLs to try in order
queue @@ -68,7 +68,7 @@ The `options` property is specific to the chosen transporter. The Rabbit
consumerTag - Consumer Tag Identifier (read more here) + A name which the server will use to distinguish message deliveries for the consumer; mustn’t be already in use on the channel. It’s usually easier to omit this, in which case the server will create a random name and supply it in the reply. Consumer Tag Identifier (read more here)
queueOptions @@ -82,6 +82,38 @@ The `options` property is specific to the chosen transporter. The Rabbit headers Headers to be sent along with every message
+
+ replyQueue + Reply queue for the producer. Default is amq.rabbitmq.reply-to +
+
+ persistent + If truthy, the message will survive broker restarts provided it’s in a queue that also survives restarts +
+
+ noAssert + When false, a queue will not be asserted before consuming +
+
+ wildcards + Set to true only if you want to use Topic Exchange for routing messages to queues. Enabling this will allow you to use wildcards (*, #) as message and event patterns +
+
+ exchange + Name for the exchange. Defaults to the queue name when "wildcards" is set to true +
+
+ exchangeType + Type of the exchange. Default is topic. Valid values are direct, fanout, topic, and headers +
+
+ routingKey + Additional routing key for the topic exchange +
+
+ maxConnectionAttempts + Maximum number of connection attempts. Applies only to the consumer configuration. -1 === infinite +
#### Client @@ -300,3 +332,46 @@ Similarly, you can access the server's underlying driver instance: const managerRef = server.unwrap(); ``` + +#### Wildcards + +RabbitMQ supports the use of wildcards in routing keys to allow for flexible message routing. The `#` wildcard matches zero or more words, while the `*` wildcard matches exactly one word. + +For example, the routing key `cats.#` matches `cats`, `cats.meow`, and `cats.meow.purr`. The routing key `cats.*` matches `cats.meow` but not `cats.meow.purr`. + +To enable wildcard support in your RabbitMQ microservice, set the `wildcards` configuration option to `true` in the options object: + +```typescript +const app = await NestFactory.createMicroservice( + AppModule, + { + transport: Transport.RMQ, + options: { + urls: ['amqp://localhost:5672'], + queue: 'cats_queue', + wildcards: true, + }, + }, +); +``` + +With this configuration, you can use wildcards in your routing keys when subscribing to events/messages. For example, to listen for messages with the routing key `cats.#`, you can use the following code: + +```typescript +@MessagePattern('cats.#') +getCats(@Payload() data: { message: string }, @Ctx() context: RmqContext) { + console.log(`Received message with routing key: ${context.getPattern()}`); + + return { + message: 'Hello from the cats service!', + } +} +``` + +To send a message with a specific routing key, you can use the `send()` method of the `ClientProxy` instance: + +```typescript +this.client.send('cats.meow', { message: 'Meow!' }).subscribe((response) => { + console.log(response); +}); +``` diff --git a/content/migration.md b/content/migration.md index 35a0a05c77..fac6422dbb 100644 --- a/content/migration.md +++ b/content/migration.md @@ -90,6 +90,27 @@ bootstrap(); > info **Hint** There have been no changes to path matching in Fastify v5 (except for middleware, see the section below), so you can continue using the wildcard syntax as you did before. The behavior remains the same, and routes defined with wildcards (like `*`) will still work as expected. +#### Fastify CORS + +By default, only [CORS-safelisted methods](https://fetch.spec.whatwg.org/#methods) are allowed. If you need to enable additional methods (such as `PUT`, `PATCH`, or `DELETE`), you must explicitly define them in the `methods` option. + +```typescript +const methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']; // OR comma-delimited string 'GET,POST,PUT,PATH,DELETE' + +const app = await NestFactory.create( + AppModule, + new FastifyAdapter(), + { cors: { methods } }, +); + +// OR alternatively, you can use the `enableCors` method +const app = await NestFactory.create( + AppModule, + new FastifyAdapter(), +); +app.enableCors({ methods }); +``` + #### Fastify middleware registration NestJS 11 now uses the latest version of the [path-to-regexp](https://www.npmjs.com/package/path-to-regexp) package to match **middleware paths** in `@nestjs/platform-fastify`. As a result, the `(.*)` syntax for matching all paths is no longer supported. Instead, you should use named wildcards. @@ -157,7 +178,7 @@ While the `OnModuleDestroy` hooks are executed in the reverse order: A -> B -> C ``` -> info **Hint** Global modules are treated as if they depend on all other modules. This means that global modules are initialized first and destroyed last. +> info **Hint** Global modules are treated as a dependency of all other modules. This means that global modules are initialized first and destroyed last. #### Middleware registration order diff --git a/content/openapi/cli-plugin.md b/content/openapi/cli-plugin.md index 4af8e59adf..0d90669832 100644 --- a/content/openapi/cli-plugin.md +++ b/content/openapi/cli-plugin.md @@ -173,6 +173,7 @@ export interface PluginOptions { controllerKeyOfComment?: string; introspectComments?: boolean; skipAutoHttpCode?: boolean; + esmCompatible?: boolean; } ``` @@ -217,6 +218,11 @@ export interface PluginOptions { false Disables the automatic addition of @HttpCode() in controllers + + esmCompatible + false + If set to true, resolves syntax errors encountered when using ESM ({ "type": "module" }). + Make sure to delete the `/dist` folder and rebuild your application whenever plugin options are updated. diff --git a/content/openapi/introduction.md b/content/openapi/introduction.md index 9f901779fe..ac6d1ce992 100644 --- a/content/openapi/introduction.md +++ b/content/openapi/introduction.md @@ -37,7 +37,7 @@ async function bootstrap() { bootstrap(); ``` -> info **Hint** The factory method `SwaggerModule#createDocument()` is used specifically to generate the Swagger document when you request it. This approach helps save some initialization time, and the resulting document is a serializable object that conforms to the [OpenAPI Document](https://swagger.io/specification/#openapi-document) specification. Instead of serving the document over HTTP, you can also save it as a JSON or YAML file and use it in various ways. +> info **Hint** The factory method `SwaggerModule.createDocument()` is used specifically to generate the Swagger document when you request it. This approach helps save some initialization time, and the resulting document is a serializable object that conforms to the [OpenAPI Document](https://swagger.io/specification/#openapi-document) specification. Instead of serving the document over HTTP, you can also save it as a JSON or YAML file and use it in various ways. The `DocumentBuilder` helps to structure a base document that conforms to the OpenAPI Specification. It provides several methods that allow setting such properties as title, description, version, etc. In order to create a full document (with all HTTP routes defined) we use the `createDocument()` method of the `SwaggerModule` class. This method takes two arguments, an application instance and a Swagger options object. Alternatively, we can provide a third argument, which should be of type `SwaggerDocumentOptions`. More on this in the [Document options section](/openapi/introduction#document-options). @@ -286,20 +286,21 @@ export interface SwaggerCustomOptions { urls?: Record<'url' | 'name', string>[]; } ``` -> info **Hint** `ui` and `raw` are independent options. Disabling Swagger UI (`ui: false`) does not disable API definitions (JSON/YAML). Conversely, disabling API definitions (`raw: []`) does not disable the Swagger UI. + +> info **Hint** `ui` and `raw` are independent options. Disabling Swagger UI (`ui: false`) does not disable API definitions (JSON/YAML). Conversely, disabling API definitions (`raw: []`) does not disable the Swagger UI. > > For example, the following configuration will disable the Swagger UI but still allow access to API definitions: +> > ```typescript ->const options: SwaggerCustomOptions = { -> ui: false, // Swagger UI is disabled -> raw: ['json'], // JSON API definition is still accessible (YAML is disabled) ->}; ->SwaggerModule.setup('api', app, options); +> const options: SwaggerCustomOptions = { +> ui: false, // Swagger UI is disabled +> raw: ['json'], // JSON API definition is still accessible (YAML is disabled) +> }; +> SwaggerModule.setup('api', app, options); > ``` > > In this case, http://localhost:3000/api-json will still be accessible, but http://localhost:3000/api (Swagger UI) will not. - #### Example A working example is available [here](https://github.com/nestjs/nest/tree/master/sample/11-swagger). diff --git a/content/openapi/operations.md b/content/openapi/operations.md index c4edcbf301..0b2a147c1e 100644 --- a/content/openapi/operations.md +++ b/content/openapi/operations.md @@ -115,6 +115,18 @@ Let's open the browser and verify the generated `Cat` model: +Instead of defining responses for each endpoint or controller individually, you can define a global response for all endpoints using the `DocumentBuilder` class. This approach is useful when you want to define a global response for all endpoints in your application (e.g., for errors like `401 Unauthorized` or `500 Internal Server Error`). + +```typescript +const config = new DocumentBuilder() + .addGlobalResponse({ + status: 500, + description: 'Internal server error', + }) + // other configurations + .build(); +``` + #### File upload You can enable file upload for a specific method with the `@ApiBody` decorator together with `@ApiConsumes()`. Here's a full example using the [File Upload](/techniques/file-upload) technique: @@ -126,7 +138,7 @@ You can enable file upload for a specific method with the `@ApiBody` decorator t description: 'List of cats', type: FileUploadDto, }) -uploadFile(@UploadedFile() file) {} +uploadFile(@UploadedFile() file: Express.Multer.File) {} ``` Where `FileUploadDto` is defined as follows: @@ -299,7 +311,9 @@ findAll(): Observable<{ total: number, limit: number, offset: number, results: C As you can see, the **Return Type** here is ambiguous. To workaround this issue, you can add a `title` property to the `schema` for `ApiPaginatedResponse`: ```typescript -export const ApiPaginatedResponse = >(model: TModel) => { +export const ApiPaginatedResponse = >( + model: TModel, +) => { return applyDecorators( ApiOkResponse({ schema: { diff --git a/content/openapi/other-features.md b/content/openapi/other-features.md index 6d40dcd49b..c0b4856e37 100644 --- a/content/openapi/other-features.md +++ b/content/openapi/other-features.md @@ -14,13 +14,30 @@ const document = SwaggerModule.createDocument(app, options, { #### Global parameters -You can add parameter definitions to all routes using `DocumentBuilder`: +You can define parameters for all routes using `DocumentBuilder`, as shown below: ```typescript -const options = new DocumentBuilder().addGlobalParameters({ - name: 'tenantId', - in: 'header', -}); +const config = new DocumentBuilder() + .addGlobalParameters({ + name: 'tenantId', + in: 'header', + }) + // other configurations + .build(); +``` + +#### Global responses + +You can define global responses for all routes using `DocumentBuilder`. This is useful for setting up consistent responses across all endpoints in your application, such as error codes like `401 Unauthorized` or `500 Internal Server Error`. + +```typescript +const config = new DocumentBuilder() + .addGlobalResponse({ + status: 500, + description: 'Internal server error', + }) + // other configurations + .build(); ``` #### Multiple specifications diff --git a/content/openapi/types-and-parameters.md b/content/openapi/types-and-parameters.md index fdb7e1a70d..5c881e003b 100644 --- a/content/openapi/types-and-parameters.md +++ b/content/openapi/types-and-parameters.md @@ -293,7 +293,7 @@ export class CreateCatDto {} > info **Hint** You only need to use `@ApiExtraModels()` once for a specific model class. -Alternatively, you can pass an options object with the `extraModels` property specified to the `SwaggerModule#createDocument()` method, as follows: +Alternatively, you can pass an options object with the `extraModels` property specified to the `SwaggerModule.createDocument()` method, as follows: ```typescript const documentFactory = () => diff --git a/content/recipes/cqrs.md b/content/recipes/cqrs.md index 0b3df8079a..35460161fc 100644 --- a/content/recipes/cqrs.md +++ b/content/recipes/cqrs.md @@ -89,7 +89,9 @@ export class KillDragonCommand extends Command<{ constructor( public readonly heroId: string, public readonly dragonId: string, - ) {} + ) { + super(); + } } @@switch export class KillDragonCommand extends Command { @@ -183,7 +185,7 @@ export class GetHeroHandler implements IQueryHandler { constructor(private repository: HeroesRepository) {} async execute(query: GetHeroQuery) { - return this.repository.findOneById(query.hero); + return this.repository.findOneById(query.heroId); } } @@switch @@ -494,7 +496,7 @@ Using request-scoped providers alongside CQRS can be complex because the `Comman To make a handler request-scoped, you can either: 1. Depend on a request-scoped provider. -2. Explicitly set its scope to `REQUEST` using the `@CommandHandler`, `@QueryHandler`, or `@EventHandler` decorator, as shown: +2. Explicitly set its scope to `REQUEST` using the `@CommandHandler`, `@QueryHandler`, or `@EventsHandler` decorator, as shown: ```typescript @CommandHandler(KillDragonCommand, { diff --git a/content/recipes/mikroorm.md b/content/recipes/mikroorm.md index d6a384d325..a422b2951c 100644 --- a/content/recipes/mikroorm.md +++ b/content/recipes/mikroorm.md @@ -32,8 +32,7 @@ import { SqliteDriver } from '@mikro-orm/sqlite'; controllers: [AppController], providers: [AppService], }) -export class AppModule { -} +export class AppModule {} ``` The `forRoot()` method accepts the same configuration object as `init()` from the MikroORM package. Check [this page](https://mikro-orm.io/docs/configuration) for the complete configuration documentation. @@ -67,7 +66,7 @@ export class AppModule {} Afterward, the `EntityManager` will be available to inject across the entire project (without importing any module elsewhere). ```ts -// Import everytyhing from your driver package or `@mikro-orm/knex` +// Import everything from your driver package or `@mikro-orm/knex` import { EntityManager, MikroORM } from '@mikro-orm/sqlite'; @Injectable() @@ -246,7 +245,7 @@ The `@mikro-orm/nestjs` package exposes `getRepositoryToken()` function that ret PhotoService, { // or when you have a custom repository: `provide: PhotoRepository` - provide: getRepositoryToken(Photo), + provide: getRepositoryToken(Photo), useValue: mockedRepository, }, ], diff --git a/content/recipes/passport.md b/content/recipes/passport.md index 59f69eff4a..c9547b1fa6 100644 --- a/content/recipes/passport.md +++ b/content/recipes/passport.md @@ -372,7 +372,7 @@ async login(@Request() req) { #### Logout route -To log out, we can create an additional route that invokes `res.logout()` to clear the user's session. This is a typical approach used in session-based authentication, but it does not apply to JWTs. +To log out, we can create an additional route that invokes `req.logout()` to clear the user's session. This is a typical approach used in session-based authentication, but it does not apply to JWTs. ```typescript @UseGuards(LocalAuthGuard) diff --git a/content/recipes/prisma.md b/content/recipes/prisma.md index 427f03b092..c794f81f9e 100644 --- a/content/recipes/prisma.md +++ b/content/recipes/prisma.md @@ -67,6 +67,29 @@ This command creates a new `prisma` directory with the following contents: - `schema.prisma`: Specifies your database connection and contains the database schema - `.env`: A [dotenv](https://github.com/motdotla/dotenv) file, typically used to store your database credentials in a group of environment variables +#### Set the generator output path + +> warning **Warning** In Prisma ORM 7, Prisma Client will no longer be generated in `node_modules` by default and will require an output path to be defined. [Learn more below on how to define an output path](https://www.prisma.io/docs/orm/prisma-client/setup-and-configuration/generating-prisma-client#using-a-custom-output-path). + +Specify your output `path` for the generated Prisma client either by passing `--output ../generated/prisma` during prisma init, or directly in your Prisma schema: + +```groovy +generator client { + provider = "prisma-client-js" + output = "../generated/prisma" +} +``` + +By default, Nest does not include the generated Prisma client in the build. To fix this, the path should be explicitly defined in `tsconfig.build.json`: + +```json +{ + "extends": "./tsconfig.json", + "include": ["src", "generated"], + "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] +} +``` + #### Set the database connection Your database connection is configured in the `datasource` block in your `schema.prisma` file. By default it's set to `postgresql`, but since you're using a SQLite database in this guide you need to adjust the `provider` field of the `datasource` block to `sqlite`: @@ -79,6 +102,7 @@ datasource db { generator client { provider = "prisma-client-js" + output = "../generated/prisma" } ``` @@ -110,6 +134,7 @@ datasource db { generator client { provider = "prisma-client-js" + output = "../generated/prisma" } ``` @@ -141,6 +166,7 @@ datasource db { generator client { provider = "prisma-client-js" + output = "../generated/prisma" } ``` @@ -166,6 +192,7 @@ datasource db { generator client { provider = "prisma-client-js" + output = "../generated/prisma" } ``` @@ -274,7 +301,7 @@ Inside the `src` directory, create a new file called `prisma.service.ts` and add ```typescript import { Injectable, OnModuleInit } from '@nestjs/common'; -import { PrismaClient } from '@prisma/client'; +import { PrismaClient } from 'generated/prisma'; @Injectable() export class PrismaService extends PrismaClient implements OnModuleInit { @@ -293,7 +320,7 @@ Still inside the `src` directory, create a new file called `user.service.ts` and ```typescript import { Injectable } from '@nestjs/common'; import { PrismaService } from './prisma.service'; -import { User, Prisma } from '@prisma/client'; +import { User, Prisma } from 'generated/prisma'; @Injectable() export class UsersService { @@ -358,7 +385,7 @@ Still inside the `src` directory, create a new file called `post.service.ts` and ```typescript import { Injectable } from '@nestjs/common'; import { PrismaService } from './prisma.service'; -import { Post, Prisma } from '@prisma/client'; +import { Post, Prisma } from 'generated/prisma'; @Injectable() export class PostsService { @@ -434,9 +461,9 @@ import { Put, Delete, } from '@nestjs/common'; -import { UsersService } from './users.service'; -import { PostsService } from './posts.service'; -import { User as UserModel, Post as PostModel } from '@prisma/client'; +import { UsersService } from './user.service'; +import { PostsService } from './post.service'; +import { User as UserModel, Post as PostModel } from 'generated/prisma'; @Controller() export class AppController { diff --git a/content/recipes/sentry.md b/content/recipes/sentry.md index 21df728eb9..9a7954a6ce 100644 --- a/content/recipes/sentry.md +++ b/content/recipes/sentry.md @@ -14,7 +14,7 @@ $ npm install --save @sentry/nestjs @sentry/profiling-node #### Basic setup -To get started with Sentry, you'll need to create a file named `instrument.js` that should be imported before any other modules in your application: +To get started with Sentry, you'll need to create a file named `instrument.ts` that should be imported before any other modules in your application: ```typescript @@filename(instrument) @@ -39,7 +39,7 @@ Sentry.init({ }); ``` -Update your `main.ts` file to import `instrument.js` before other imports: +Update your `main.ts` file to import `instrument.ts` before other imports: ```typescript @@filename(main) diff --git a/content/recipes/terminus.md b/content/recipes/terminus.md index 1d8c49da9a..af18586ea2 100644 --- a/content/recipes/terminus.md +++ b/content/recipes/terminus.md @@ -2,7 +2,7 @@ Terminus integration provides you with **readiness/liveness** health checks. Healthchecks are crucial when it comes to complex backend setups. In a nutshell, a health check in the realm of web development usually consists of a special address, for example, `https://my-website.com/health/readiness`. -A service or a component of your infrastructure (e.g., [Kubernetes](https://kubernetes.io/) checks this address continuously. Depending on the HTTP status code returned from a `GET` request to this address the service will take action when it receives an "unhealthy" response. +A service or a component of your infrastructure (e.g., [Kubernetes](https://kubernetes.io/) checks this address continuously). Depending on the HTTP status code returned from a `GET` request to this address the service will take action when it receives an "unhealthy" response. Since the definition of "healthy" or "unhealthy" varies with the type of service you provide, the **Terminus** integration supports you with a set of **health indicators**. diff --git a/content/security/authorization.md b/content/security/authorization.md index a5f7f8c7d6..4f32b373ad 100644 --- a/content/security/authorization.md +++ b/content/security/authorization.md @@ -220,7 +220,7 @@ export enum Action { } ``` -> warning **Notice** `manage` is a special keyword in CASL which represents "any" action. +> warning **Notice** `manage` is a special keyword in CASL which represents "any action". To encapsulate CASL library, let's generate the `CaslModule` and `CaslAbilityFactory` now. @@ -234,14 +234,12 @@ With this in place, we can define the `createForUser()` method on the `CaslAbili ```typescript type Subjects = InferSubjects | 'all'; -export type AppAbility = Ability<[action, Subjects]>; +export type AppAbility = MongoAbility<[action, Subjects]>; @Injectable() export class CaslAbilityFactory { createForUser(user: User) { - const { can, cannot, build } = new AbilityBuilder< - Ability<[action, Subjects]> ->(Ability as AbilityClass); + const { can, cannot, build } = new AbilityBuilder(createMongoAbility); if (user.isAdmin) { can(Action.Manage, 'all'); // read-write access to everything @@ -263,11 +261,13 @@ export class CaslAbilityFactory { > warning **Notice** `all` is a special keyword in CASL that represents "any subject". -> info **Hint** `Ability`, `AbilityBuilder`, `AbilityClass`, and `ExtractSubjectType` classes are exported from the `@casl/ability` package. +> info **Hint** Since CASL v6, `MongoAbility` serves as the default ability class, replacing the legacy `Ability` to better support condition-based permissions using MongoDB-like syntax. Despite the name, it is not tied to MongoDB — it works with any kind of data by simply comparing objects against conditions written in Mongo-like syntax. + +> info **Hint** `MongoAbility`, `AbilityBuilder`, `AbilityClass`, and `ExtractSubjectType` classes are exported from the `@casl/ability` package. > info **Hint** `detectSubjectType` option let CASL understand how to get subject type out of an object. For more information read [CASL documentation](https://casl.js.org/v6/en/guide/subject-type-detection#use-classes-as-subject-types) for details. -In the example above, we created the `Ability` instance using the `AbilityBuilder` class. As you probably guessed, `can` and `cannot` accept the same arguments but have different meanings, `can` allows to do an action on the specified subject and `cannot` forbids. Both may accept up to 4 arguments. To learn more about these functions, visit the official [CASL documentation](https://casl.js.org/v6/en/guide/intro). +In the example above, we created the `MongoAbility` instance using the `AbilityBuilder` class. As you probably guessed, `can` and `cannot` accept the same arguments but have different meanings, `can` allows to do an action on the specified subject and `cannot` forbids. Both may accept up to 4 arguments. To learn more about these functions, visit the official [CASL documentation](https://casl.js.org/v6/en/guide/intro). Lastly, make sure to add the `CaslAbilityFactory` to the `providers` and `exports` arrays in the `CaslModule` module definition: @@ -297,7 +297,7 @@ if (ability.can(Action.Read, 'all')) { } ``` -> info **Hint** Learn more about the `Ability` class in the official [CASL documentation](https://casl.js.org/v6/en/guide/intro). +> info **Hint** Learn more about the `MongoAbility` class in the official [CASL documentation](https://casl.js.org/v6/en/guide/intro). For example, let's say we have a user who is not an admin. In this case, the user should be able to read articles, but creating new ones or removing the existing articles should be prohibited. @@ -311,7 +311,7 @@ ability.can(Action.Delete, Article); // false ability.can(Action.Create, Article); // false ``` -> info **Hint** Although both `Ability` and `AbilityBuilder` classes provide `can` and `cannot` methods, they have different purposes and accept slightly different arguments. +> info **Hint** Although both `MongoAbility` and `AbilityBuilder` classes provide `can` and `cannot` methods, they have different purposes and accept slightly different arguments. Also, as we have specified in our requirements, the user should be able to update its articles: @@ -329,7 +329,7 @@ article.authorId = 2; ability.can(Action.Update, article); // false ``` -As you can see, `Ability` instance allows us to check permissions in pretty readable way. Likewise, `AbilityBuilder` allows us to define permissions (and specify various conditions) in a similar fashion. To find more examples, visit the official documentation. +As you can see, `MongoAbility` instance allows us to check permissions in pretty readable way. Likewise, `AbilityBuilder` allows us to define permissions (and specify various conditions) in a similar fashion. To find more examples, visit the official documentation. #### Advanced: Implementing a `PoliciesGuard` diff --git a/content/security/encryption-hashing.md b/content/security/encryption-hashing.md index 71691d4927..518ee52203 100644 --- a/content/security/encryption-hashing.md +++ b/content/security/encryption-hashing.md @@ -11,8 +11,8 @@ Node.js provides a built-in [crypto module](https://nodejs.org/api/crypto.html) As an example, let's use AES (Advanced Encryption System) `'aes-256-ctr'` algorithm CTR encryption mode. ```typescript -import { createCipheriv, randomBytes, scrypt } from 'crypto'; -import { promisify } from 'util'; +import { createCipheriv, randomBytes, scrypt } from 'node:crypto'; +import { promisify } from 'node:util'; const iv = randomBytes(16); const password = 'Password used to generate key'; @@ -32,7 +32,7 @@ const encryptedText = Buffer.concat([ Now to decrypt `encryptedText` value: ```typescript -import { createDecipheriv } from 'crypto'; +import { createDecipheriv } from 'node:crypto'; const decipher = createDecipheriv('aes-256-ctr', key, iv); const decryptedText = Buffer.concat([ diff --git a/content/security/rate-limiting.md b/content/security/rate-limiting.md index 47ce1998bd..43cefcbb34 100644 --- a/content/security/rate-limiting.md +++ b/content/security/rate-limiting.md @@ -94,7 +94,7 @@ export class UsersController { } ``` -There is also the `@Throttle()` decorator which can be used to override the `limit` and `ttl` set in the global module, to give tighter or looser security options. This decorator can be used on a class or a function as well. With version 5 and onwards, the decorator takes in an object with the string relating to the name of the throttler set, and an object with the limit and ttl keys and integer values, similar to the options passed to the root module. If you do not have a name set in your original options, use the string `default` You have to configure it like this: +There is also the `@Throttle()` decorator which can be used to override the `limit` and `ttl` set in the global module, to give tighter or looser security options. This decorator can be used on a class or a function as well. With version 5 and onwards, the decorator takes in an object with the string relating to the name of the throttler set, and an object with the limit and ttl keys and integer values, similar to the options passed to the root module. If you do not have a name set in your original options, use the string `default`. You have to configure it like this: ```typescript // Override default configuration for Rate limiting and duration. @@ -112,7 +112,7 @@ If your application is running behind a proxy server, it’s essential to config Here's an example that demonstrates how to enable `trust proxy` for the Express adapter: ```typescript -@@filename(main.ts) +@@filename(main) import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { NestExpressApplication } from '@nestjs/platform-express'; @@ -238,7 +238,7 @@ The following options are valid for the object passed to the array of the `Throt - + @@ -354,9 +354,7 @@ For most people, wrapping your options in an array will be enough. If you are using a custom storage, you should wrap your `ttl` and `limit` in an array and assign it to the `throttlers` property of the options object. -Any `@ThrottleSkip()` should now take in an object with `string: boolean` props. -The strings are the names of the throttlers. If you do not have a name, pass the -string `'default'`, as this is what will be used under the hood otherwise. +Any `@SkipThrottle()` decorator can be used to bypass throttling for specific routes or methods. It accepts an optional boolean parameter, which defaults to `true`. This is useful when you want to skip rate limiting on particular endpoints. Any `@Throttle()` decorators should also now take in an object with string keys, relating to the names of the throttler contexts (again, `'default'` if no name) diff --git a/content/techniques/caching.md b/content/techniques/caching.md index 11ffd0d54c..e4d12c97cb 100644 --- a/content/techniques/caching.md +++ b/content/techniques/caching.md @@ -38,7 +38,7 @@ To interact with the cache manager instance, inject it to your class using the ` constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {} ``` -> info **Hint** The `Cache` class is imported from the `cache-manager`, while `CACHE_MANAGER` token from the `@nestjs/cache-manager` package. +> info **Hint** The `Cache` class and the `CACHE_MANAGER` token are both imported from the `@nestjs/cache-manager` package. The `get` method on the `Cache` instance (from the `cache-manager` package) is used to retrieve items from the cache. If the item does not exist in the cache, `null` will be returned. @@ -54,7 +54,7 @@ await this.cacheManager.set('key', 'value'); > warning **Note** The in-memory cache storage can only store values of types that are supported by [the structured clone algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#javascript_types). -You can manually specify a TTL (expiration time in miliseconds) for this specific key, as follows: +You can manually specify a TTL (expiration time in milliseconds) for this specific key, as follows: ```typescript await this.cacheManager.set('key', 'value', 1000); @@ -234,9 +234,9 @@ With this in place, you can register the `CacheModule` with multiple stores as s ```typescript import { Module } from '@nestjs/common'; -import { CacheModule, CacheStore } from '@nestjs/cache-manager'; +import { CacheModule } from '@nestjs/cache-manager'; import { AppController } from './app.controller'; -import { createKeyv } from '@keyv/redis'; +import KeyvRedis from '@keyv/redis'; import { Keyv } from 'keyv'; import { CacheableMemory } from 'cacheable'; @@ -249,7 +249,7 @@ import { CacheableMemory } from 'cacheable'; new Keyv({ store: new CacheableMemory({ ttl: 60000, lruSize: 5000 }), }), - createKeyv('redis://localhost:6379'), + new KeyvRedis('redis://localhost:6379'), ], }; }, @@ -322,7 +322,7 @@ CacheModule.registerAsync({ This works the same as `useClass` with one critical difference - `CacheModule` will lookup imported modules to reuse any already-created `ConfigService`, instead of instantiating its own. -> info **Hint** `CacheModule#register` and `CacheModule#registerAsync` and `CacheOptionsFactory` has an optional generic (type argument) to narrow down store-specific configuration options, making it type safe. +> info **Hint** `CacheModule#register`, `CacheModule#registerAsync` and `CacheOptionsFactory` have an optional generic (type argument) to narrow down store-specific configuration options, making it type safe. You can also pass so-called `extraProviders` to the `registerAsync()` method. These providers will be merged with the module providers. diff --git a/content/techniques/compression.md b/content/techniques/compression.md index 46244a4769..164efec3a8 100644 --- a/content/techniques/compression.md +++ b/content/techniques/compression.md @@ -33,16 +33,22 @@ $ npm i --save @fastify/compress Once the installation is complete, apply the `@fastify/compress` middleware as global middleware. +> warning **Warning** Please ensure, that you use the type `NestFastifyApplication` when creating the application. Otherwise, you cannot use `register` to apply the compression-middleware. + ```typescript +import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify'; + import compression from '@fastify/compress'; -// somewhere in your initialization file + +// inside bootstrap() +const app = await NestFactory.create(AppModule, new FastifyAdapter()); await app.register(compression); ``` By default, `@fastify/compress` will use Brotli compression (on Node>= 11.7.0) when browsers indicate support for the encoding. While Brotli can be quite efficient in terms of compression ratio, it can also be quite slow. By default, Brotli sets a maximum compression quality of 11, although it can be adjusted to reduce compression time in lieu of compression quality by adjusting the `BROTLI_PARAM_QUALITY` between 0 min and 11 max. This will require fine tuning to optimize space/time performance. An example with quality 4: ```typescript -import { constants } from 'zlib'; +import { constants } from 'node:zlib'; // somewhere in your initialization file await app.register(compression, { brotliOptions: { params: { [constants.BROTLI_PARAM_QUALITY]: 4 } } }); ``` diff --git a/content/techniques/configuration.md b/content/techniques/configuration.md index c0574bd8e1..159f98d1d0 100644 --- a/content/techniques/configuration.md +++ b/content/techniques/configuration.md @@ -148,13 +148,13 @@ $ npm i js-yaml $ npm i -D @types/js-yaml ``` -Once the package is installed, we use `yaml#load` function to load YAML file we just created above. +Once the package is installed, we use the `yaml#load` function to load the YAML file we just created above. ```typescript @@filename(config/configuration) -import { readFileSync } from 'fs'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; import * as yaml from 'js-yaml'; -import { join } from 'path'; const YAML_CONFIG_FILENAME = 'config.yaml'; diff --git a/content/techniques/events.md b/content/techniques/events.md index e903aefcac..fef6e4d6a4 100644 --- a/content/techniques/events.md +++ b/content/techniques/events.md @@ -149,7 +149,7 @@ To avoid this issue, you can use the `waitUntilReady` method of the `EventEmitte ```typescript await this.eventEmitterReadinessWatcher.waitUntilReady(); -await this.eventEmitter.emit( +this.eventEmitter.emit( 'order.created', new OrderCreatedEvent({ orderId: 1, payload: {} }), ); diff --git a/content/techniques/file-upload.md b/content/techniques/file-upload.md index c36af51b3d..02663af9f7 100644 --- a/content/techniques/file-upload.md +++ b/content/techniques/file-upload.md @@ -131,9 +131,7 @@ export abstract class FileValidator> { `FileValidator` is a regular class that has access to the file object and validates it according to the options provided by the client. Nest has two built-in `FileValidator` implementations you can use in your project: - `MaxFileSizeValidator` - Checks if a given file's size is less than the provided value (measured in `bytes`) -- `FileTypeValidator` - Checks if a given file's mime-type matches the given value. - -> warning **Warning** To verify file type, [FileTypeValidator](https://github.com/nestjs/nest/blob/master/packages/common/pipes/file/file-type.validator.ts) class uses the type as detected by multer. By default, multer derives file type from file extension on user's device. However, it does not check actual file contents. As files can be renamed to arbitrary extensions, consider using a custom implementation (like checking the file's [magic number](https://www.ibm.com/support/pages/what-magic-number)) if your app requires a safer solution. +- `FileTypeValidator` - Checks if a given file's mime-type matches a given string or RegExp. By default, validates the mime-type using file content [magic number](https://www.ibm.com/support/pages/what-magic-number) To understand how these can be used in conjunction with the aforementioned `FileParsePipe`, we'll use an altered snippet of the last presented example: diff --git a/content/techniques/logger.md b/content/techniques/logger.md index 5e1752ecf1..ee5ff296c3 100644 --- a/content/techniques/logger.md +++ b/content/techniques/logger.md @@ -60,7 +60,7 @@ Here are all the available options listed in the table below: | Option | Description | Default | | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | -| `logLevels` | Enabled log levels. | `['log', 'error', 'warn', 'debug', 'verbose']` | +| `logLevels` | Enabled log levels. | `['log', 'fatal', 'error', 'warn', 'debug', 'verbose']` | | `timestamp` | If enabled, will print timestamp (time difference) between current and previous log message. Note: This option is not used when `json` is enabled. | `false` | | `prefix` | A prefix to be used for each log message. Note: This option is not used when `json` is enabled. | `Nest` | | `json` | If enabled, will print the log message in JSON format. | `false` | diff --git a/content/techniques/mvc.md b/content/techniques/mvc.md index e2d88e717d..f0c06f4301 100644 --- a/content/techniques/mvc.md +++ b/content/techniques/mvc.md @@ -21,7 +21,7 @@ We've used the `hbs` ([Handlebars](https://github.com/pillarjs/hbs#readme)) engi @@filename(main) import { NestFactory } from '@nestjs/core'; import { NestExpressApplication } from '@nestjs/platform-express'; -import { join } from 'path'; +import { join } from 'node:path'; import { AppModule } from './app.module'; async function bootstrap() { @@ -38,7 +38,7 @@ async function bootstrap() { bootstrap(); @@switch import { NestFactory } from '@nestjs/core'; -import { join } from 'path'; +import { join } from 'node:path'; import { AppModule } from './app.module'; async function bootstrap() { @@ -139,7 +139,7 @@ The next steps cover almost the same process used with Express, with minor diffe import { NestFactory } from '@nestjs/core'; import { NestFastifyApplication, FastifyAdapter } from '@nestjs/platform-fastify'; import { AppModule } from './app.module'; -import { join } from 'path'; +import { join } from 'node:path'; async function bootstrap() { const app = await NestFactory.create( @@ -163,7 +163,7 @@ bootstrap(); import { NestFactory } from '@nestjs/core'; import { FastifyAdapter } from '@nestjs/platform-fastify'; import { AppModule } from './app.module'; -import { join } from 'path'; +import { join } from 'node:path'; async function bootstrap() { const app = await NestFactory.create(AppModule, new FastifyAdapter()); diff --git a/content/techniques/queues.md b/content/techniques/queues.md index 6e0354923e..155a5d3604 100644 --- a/content/techniques/queues.md +++ b/content/techniques/queues.md @@ -230,7 +230,7 @@ export class AudioConsumer extends WorkerHost { The process method is called whenever the worker is idle and there are jobs to process in the queue. This handler method receives the `job` object as its only argument. The value returned by the handler method is stored in the job object and can be accessed later on, for example in a listener for the completed event. -`Job` objects have multiple methods that allow you to interact with their state. For example, the above code uses the `progress()` method to update the job's progress. See [here](https://api.docs.bullmq.io/classes/v4.Job.html) for the complete `Job` object API reference. +`Job` objects have multiple methods that allow you to interact with their state. For example, the above code uses the `updateProgress()` method to update the job's progress. See [here](https://api.docs.bullmq.io/classes/v4.Job.html) for the complete `Job` object API reference. In the older version, Bull, you could designate that a job handler method will handle **only** jobs of a certain type (jobs with a specific `name`) by passing that `name` to the `@Process()` decorator as shown below. @@ -370,7 +370,7 @@ Job handlers can also be run in a separate (forked) process ([source](https://do @@filename(app.module) import { Module } from '@nestjs/common'; import { BullModule } from '@nestjs/bullmq'; -import { join } from 'path'; +import { join } from 'node:path'; @Module({ imports: [ diff --git a/content/techniques/sql.md b/content/techniques/sql.md index 747d0c60c0..93a3756355 100644 --- a/content/techniques/sql.md +++ b/content/techniques/sql.md @@ -113,7 +113,7 @@ export class User { } ``` -> info **Hint** Learn more about entities in the [TypeORM documentation](https://typeorm.io/#/entities). +> info **Hint** Learn more about entities in the [TypeORM documentation](https://typeorm.io/docs/entity/entities/). The `User` entity file sits in the `users` directory. This directory contains all files related to the `UsersModule`. You can decide where to keep your model files, however, we recommend creating them near their **domain**, in the corresponding module directory. @@ -296,7 +296,7 @@ export class User { } ``` -> info **Hint** To learn more about relations in TypeORM, visit the [TypeORM documentation](https://typeorm.io/#/relations). +> info **Hint** To learn more about relations in TypeORM, visit the [TypeORM documentation](https://typeorm.io/docs/relations/relations). #### Auto-load entities @@ -324,7 +324,7 @@ With that option specified, every entity registered through the `forFeature()` m #### Separating entity definition -You can define an entity and its columns right in the model, using decorators. But some people prefer to define entities and their columns inside separate files using the ["entity schemas"](https://typeorm.io/#/separating-entity-definition). +You can define an entity and its columns right in the model, using decorators. But some people prefer to define entities and their columns inside separate files using the ["entity schemas"](https://typeorm.io/docs/entity/separating-entity-definition). ```typescript import { EntitySchema } from 'typeorm'; @@ -383,7 +383,7 @@ export class UsersModule {} A database transaction symbolizes a unit of work performed within a database management system against a database, and treated in a coherent and reliable way independent of other transactions. A transaction generally represents any change in a database ([learn more](https://en.wikipedia.org/wiki/Database_transaction)). -There are many different strategies to handle [TypeORM transactions](https://typeorm.io/#/transactions). We recommend using the `QueryRunner` class because it gives full control over the transaction. +There are many different strategies to handle [TypeORM transactions](https://typeorm.io/docs/advanced-topics/transactions/). We recommend using the `QueryRunner` class because it gives full control over the transaction. First, we need to inject the `DataSource` object into a class in the normal way: @@ -423,7 +423,7 @@ async createMany(users: User[]) { -Alternatively, you can use the callback-style approach with the `transaction` method of the `DataSource` object ([read more](https://typeorm.io/#/transactions/creating-and-using-transactions)). +Alternatively, you can use the callback-style approach with the `transaction` method of the `DataSource` object ([read more](https://typeorm.io/docs/advanced-topics/transactions/#creating-and-using-transactions)). ```typescript async createMany(users: User[]) { @@ -436,7 +436,7 @@ async createMany(users: User[]) { #### Subscribers -With TypeORM [subscribers](https://typeorm.io/#/listeners-and-subscribers/what-is-a-subscriber), you can listen to specific entity events. +With TypeORM [subscribers](https://typeorm.io/docs/advanced-topics/listeners-and-subscribers#what-is-a-subscriber), you can listen to specific entity events. ```typescript import { @@ -483,13 +483,11 @@ import { UserSubscriber } from './user.subscriber'; export class UsersModule {} ``` -> info **Hint** Learn more about entity subscribers [here](https://typeorm.io/#/listeners-and-subscribers/what-is-a-subscriber). - #### Migrations -[Migrations](https://typeorm.io/#/migrations) provide a way to incrementally update the database schema to keep it in sync with the application's data model while preserving existing data in the database. To generate, run, and revert migrations, TypeORM provides a dedicated [CLI](https://typeorm.io/#/migrations/creating-a-new-migration). +[Migrations](https://typeorm.io/docs/advanced-topics/migrations/) provide a way to incrementally update the database schema to keep it in sync with the application's data model while preserving existing data in the database. To generate, run, and revert migrations, TypeORM provides a dedicated [CLI](https://typeorm.io/docs/advanced-topics/migrations/#creating-a-new-migration). -Migration classes are separate from the Nest application source code. Their lifecycle is maintained by the TypeORM CLI. Therefore, you are not able to leverage dependency injection and other Nest specific features with migrations. To learn more about migrations, follow the guide in the [TypeORM documentation](https://typeorm.io/#/migrations/creating-a-new-migration). +Migration classes are separate from the Nest application source code. Their lifecycle is maintained by the TypeORM CLI. Therefore, you are not able to leverage dependency injection and other Nest specific features with migrations. To learn more about migrations, follow the guide in the [TypeORM documentation](https://typeorm.io/docs/advanced-topics/migrations/). #### Multiple databases @@ -938,7 +936,7 @@ export class UsersService { > warning **Notice** Don't forget to import the `UsersModule` into the root `AppModule`. -If you want to use the repository outside of the module which imports `SequelizeModule.forFeature`, you'll need to re-export the providers generated by it. +If you want to use the model outside of the module which imports `SequelizeModule.forFeature`, you'll need to re-export the providers generated by it. You can do this by exporting the whole module, like this: ```typescript diff --git a/content/techniques/streaming-files.md b/content/techniques/streaming-files.md index 13bad1cadc..cd57c5e11a 100644 --- a/content/techniques/streaming-files.md +++ b/content/techniques/streaming-files.md @@ -33,8 +33,8 @@ You can find a simple example of returning the `package.json` as a file instead ```ts import { Controller, Get, StreamableFile } from '@nestjs/common'; -import { createReadStream } from 'fs'; -import { join } from 'path'; +import { createReadStream } from 'node:fs'; +import { join } from 'node:path'; @Controller('file') export class FileController { @@ -46,12 +46,12 @@ export class FileController { } ``` -The default content type (the value for `Content-Type` HTTP response header) is `application/octet-stream`. If you need to customize this value you can use the `type` option from `StreamableFile`, or use the `res.set` method or the [`@Header()`](/controllers#headers) decorator, like this: +The default content type (the value for `Content-Type` HTTP response header) is `application/octet-stream`. If you need to customize this value you can use the `type` option from `StreamableFile`, or use the `res.set` method or the [`@Header()`](/controllers#response-headers) decorator, like this: ```ts import { Controller, Get, StreamableFile, Res } from '@nestjs/common'; -import { createReadStream } from 'fs'; -import { join } from 'path'; +import { createReadStream } from 'node:fs'; +import { join } from 'node:path'; import type { Response } from 'express'; // Assuming that we are using the ExpressJS HTTP Adapter @Controller('file') diff --git a/content/techniques/task-scheduling.md b/content/techniques/task-scheduling.md index bf962d0757..6f9cf51224 100644 --- a/content/techniques/task-scheduling.md +++ b/content/techniques/task-scheduling.md @@ -1,4 +1,4 @@ -### Task Scheduling +### Task scheduling Task scheduling allows you to schedule arbitrary code (methods/functions) to execute at a fixed date/time, at recurring intervals, or once after a specified interval. In the Linux world, this is often handled by packages like [cron](https://en.wikipedia.org/wiki/Cron) at the OS level. For Node.js apps, there are several packages that emulate cron-like functionality. Nest provides the `@nestjs/schedule` package, which integrates with the popular Node.js [cron](https://github.com/kelektiv/node-cron) package. We'll cover this package in the current chapter. diff --git a/content/techniques/validation.md b/content/techniques/validation.md index 8b699d492a..a7f59fdc55 100644 --- a/content/techniques/validation.md +++ b/content/techniques/validation.md @@ -100,7 +100,7 @@ In addition to these, all `class-validator` options (inherited from the `Validat - + diff --git a/content/websockets/gateways.md b/content/websockets/gateways.md index dd055477ac..04e8816809 100644 --- a/content/websockets/gateways.md +++ b/content/websockets/gateways.md @@ -136,6 +136,28 @@ The `handleEvent()` method will be executed. In order to listen for messages emi socket.emit('events', { name: 'Nest' }, (data) => console.log(data)); ``` +While returning a value from a message handler implicitly sends an acknowledgement, advanced scenarios often require direct control over the acknowledgement callback. + +The `@Ack()` parameter decorator allows injecting the `ack` callback function directly into a message handler. +Without using the decorator, this callback is passed as the third argument of the method. + +```typescript +@@filename(events.gateway) +@SubscribeMessage('events') +handleEvent( + @MessageBody() data: string, + @Ack() ack: (response: { status: string; data: string }) => void, +) { + ack({ status: 'received', data }); +} +@@switch +@Bind(MessageBody(), Ack()) +@SubscribeMessage('events') +handleEvent(data, ack) { + ack({ status: 'received', data }); +} +``` + #### Multiple responses The acknowledgment is dispatched only once. Furthermore, it is not supported by native WebSockets implementation. To solve this limitation, you may return an object which consists of two properties. The `event` which is a name of the emitted event and the `data` that has to be forwarded to the client. @@ -244,10 +266,15 @@ server: Server; Also, you can retrieve the corresponding namespace using the `namespace` attribute, as follows: ```typescript -@WebSocketServer({ namespace: 'my-namespace' }) -namespace: Namespace; +@WebSocketGateway({ namespace: 'my-namespace' }) +export class EventsGateway { + @WebSocketServer() + namespace: Namespace; +} ``` +`@WebSocketServer()` decorator injects a server instance by referencing the metadata stored by the `@WebSocketGateway()` decorator. If you provide the namespace option to the `@WebSocketGateway()` decorator, `@WebSocketServer()` decorator returns a `Namespace` instance instead of a `Server` instance. + > warning **Notice** The `@WebSocketServer()` decorator is imported from the `@nestjs/websockets` package. Nest will automatically assign the server instance to this property once it is ready to use. diff --git a/src/app/homepage/homepage.component.html b/src/app/homepage/homepage.component.html index a0b4335161..473382ffec 100644 --- a/src/app/homepage/homepage.component.html +++ b/src/app/homepage/homepage.component.html @@ -20,7 +20,7 @@

Support us

Nest is an MIT-licensed open source project. It can grow thanks to the - support by these awesome people. If you'd like to join them, please + support of these awesome people. If you'd like to join them, please read more here.

@@ -50,13 +50,6 @@

Principal Sponsors

class="logo-sponsor logo-sponsor--slim" /> --> - - Marblism Logo - Principal Sponsors class="logo-sponsor logo-sponsor--slim" /> - - Amplication Logo -

Sponsors / Partners

diff --git a/src/app/homepage/menu/menu.component.ts b/src/app/homepage/menu/menu.component.ts index 44cee88ab2..cbffca3aab 100644 --- a/src/app/homepage/menu/menu.component.ts +++ b/src/app/homepage/menu/menu.component.ts @@ -291,6 +291,10 @@ export class MenuComponent implements OnInit { isOpened: false, path: '/migration-guide', }, + { + title: 'API Reference', + externalUrl: 'https://api-references-nestjs.netlify.app/', + }, { title: 'Official courses', externalUrl: 'https://courses.nestjs.com/', diff --git a/src/styles.scss b/src/styles.scss index 36d18c4e07..5e565ab709 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -52,7 +52,7 @@ html[mode='dark'] { &::-webkit-scrollbar-track { background: var(--menu-background); } - + &::-webkit-scrollbar-corner { background: var(--menu-background); } @@ -127,7 +127,7 @@ blockquote { strong { color: variables.$red-color; @include utils.text-gradient(); - + &:first-of-type { display: block; text-transform: uppercase; @@ -163,7 +163,7 @@ blockquote { code[class*=language-], pre[class*=language-] { margin: 20px 0; } - + p:last-of-type { margin-bottom: 0; } @@ -399,8 +399,8 @@ tr td span.table-code-asterisk { border-radius: 2px; padding: 2px; background: var(--primary-gradient); - -webkit-mask: - linear-gradient(#fff 0 0) content-box, + -webkit-mask: + linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); -webkit-mask-composite: xor; mask-composite: exclude; @@ -420,7 +420,7 @@ tr td span.table-code-asterisk { background: rgba(0, 0, 0, 0.6); } -// Scollbar sidebar +// Scrollbar sidebar app-menu::-webkit-scrollbar { width: 6px; @@ -433,4 +433,4 @@ app-menu:hover::-webkit-scrollbar-thumb { app-menu::-webkit-scrollbar-thumb:hover { -webkit-transition: #dedede 1s linear; -} \ No newline at end of file +}

AltStyle によって変換されたページ (->オリジナル) /

namethe name for internal tracking of which throttler set is being used. Defaults to `default` if not passedthe name for internal tracking of which throttler set is being used. Defaults to default if not passed
ttl
always booleanSet default for always option of decorators. Default can be overridden in decorator optionsSet default for always option of decorators. Default can be overridden in decorator options.