Skip to content

Commit 3e8701c

Browse files
authored
feat(nest): add global configuration through nest module (#519)
1 parent ab4dd27 commit 3e8701c

16 files changed

Lines changed: 654 additions & 350 deletions

.changeset/unlucky-pets-attack.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@ts-rest/nest': minor
3+
---
4+
5+
Add functionality to be able to set configuration options globally using `TsRestModule`
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Configuration
2+
3+
There are a number of configuration options that you can use to customize the behavior of `ts-rest` in your Nest application.
4+
5+
## Configuration Options
6+
7+
### jsonQuery
8+
9+
#### Default value: `false`
10+
11+
By default, all query parameters are encoded as strings, however, you can use the `jsonQuery` option to encode query parameters as typed JSON values.
12+
13+
### validateResponses
14+
15+
#### Default value: `false`
16+
17+
You can enable response parsing and validation for defined response status codes, if there are corresponding response Zod schema defined in the contract.
18+
This is useful for ensuring absolute safety that your controller is returning the correct response types as well as stripping any extra properties.
19+
20+
If validation fails a `ResponseValidationError` will be thrown causing a 500 response to be returned.
21+
You can catch this error and handle it as you wish by using a [NestJS exception filter](https://docs.nestjs.com/exception-filters).
22+
23+
### Request Validation
24+
25+
By default, `ts-rest` validates all request components - body, headers and query parameters.
26+
In case of validation errors, the server responds with a `ZodError` object in the response body and a status code of 400.
27+
You can disable the validation of these components if you wish to perform the validation manually or handle the error differently.
28+
29+
#### Options
30+
31+
- `validateRequestBody`
32+
- `validateRequestQuery`
33+
- `validateRequestHeaders`
34+
35+
#### Default values: `true`
36+
37+
```typescript
38+
import { TsRestHandler, tsRestHandler } from '@ts-rest/nest';
39+
40+
@Controller()
41+
export class MyController {
42+
constructor() {}
43+
44+
@TsRestHandler(c.getPost, {
45+
validateRequestBody: false,
46+
validateRequestQuery: false,
47+
validateRequestHeaders: false
48+
})
49+
async getPost() {
50+
return tsRestHandler(c.getPost, async ({ query, body }) => {
51+
const isQueryValid = querySchema.safeParse(query);
52+
console.log(isQueryValid) // => { success: false; error: ZodError }
53+
54+
const isBodyValid = bodySchema.safeParse(body);
55+
console.log(isBodyValid) // => { success: true; data: {...} }
56+
});
57+
}
58+
}
59+
```
60+
61+
## Global Configuration
62+
63+
Configuring the different configuration options for each controller might get a bit cumbersome.
64+
To make it easier, you can set global options that will be used by all controllers in your Nest module, or globally across your application if you specify `isGlobal`.
65+
66+
```typescript
67+
import { Module } from '@nestjs/common';
68+
import { TsRestModule } from '@ts-rest/nest';
69+
70+
@Module({
71+
imports: [
72+
TsRestModule.register({
73+
isGlobal: true,
74+
jsonQuery: true,
75+
validateResponses: true,
76+
}),
77+
],
78+
})
79+
export class AppModule {}
80+
```

apps/docs/docs/nest/legacy.mdx

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,17 @@ import { InstallTabs } from '@site/src/components/InstallTabs';
1414
}}
1515
/>
1616

17+
:::info
18+
1719
As part of the [v3.26.0](https://github.com/ts-rest/ts-rest) we released the new `SingleHandler` and `MultiHandler` approaches to implementing your API contracts.
1820

1921
We don't have any immediate plans to deprecate the old approach, but we do recommend using the new approach as it is more flexible and allows for more control over your API implementation.
2022

2123
If we do make a v4 release, we will likely deprecate the old approach.
2224

23-
::: ## Installation
25+
:::
26+
27+
## Installation
2428

2529
<InstallTabs packageName="@ts-rest/nest" />
2630

@@ -71,19 +75,10 @@ As Typescript cannot infer class method parameter types from an implemented inte
7175

7276
## Configuration
7377

74-
To configure certain ts-rest options you can use the `@TsRest` decorator on either your Controller or use the existing `@Api` decorator.
78+
To configure `ts-rest` options you can use the `@TsRest` decorator on either your controller or use the existing `@TsRest` decorator on your method.
79+
Controller options will be applied to all routes in the controller and will override any global options, and method options will override the controller options for that specific route.
7580

76-
### JSON Query Parameters
77-
78-
To handle JSON query parameters, pass the `jsonQuery` option to the `@TsRest` decorator on your entire controller.
79-
80-
```typescript
81-
@Controller()
82-
@TsRest({ jsonQuery: true })
83-
export class PostController implements NestControllerInterface<typeof c> {}
84-
```
85-
86-
You can also use the method decorator to override or configure options for a specific route.
81+
Check out the [configuration](/docs/nest/configuration/) section for more details on the different configuration options, or on how to configure options globally.
8782

8883
```typescript
8984
@Controller()
@@ -98,29 +93,13 @@ export class PostController implements NestControllerInterface<typeof c> {
9893
}
9994
```
10095

101-
### Response Validation
102-
103-
You can enable response validation by passing the `validateResponses` option to the `@TsRest()` decorator.
104-
This will enable response parsing and validation for a returned status code, if there is a corresponding response Zod schema defined in the contract.
105-
This is useful for ensuring absolute safety that your controller is returning the correct response types as well as stripping any extra properties.
106-
107-
```typescript
108-
@Controller()
109-
@TsRest({ validateResponses: true })
110-
export class PostController implements NestControllerInterface<typeof c> {}
111-
```
112-
113-
If validation fails a `ResponseValidationError` will be thrown causing a 500 response to be returned.
114-
You can catch this error and handle it as you wish by using a [NestJS exception filter](https://docs.nestjs.com/exception-filters).
115-
11696
## Explicit Response Type Safety
11797

11898
In cases where you do not want to implement `NestControllerInterface`.
11999
Say, if you were to implement a contract's non-nested routes across multiple controllers, or use different class method names than the ones defined in your contract, you can still ensure type safety of the responses by using the `NestResponseShapes` type.
120100

121101
```typescript
122102
import {
123-
Api,
124103
nestControllerContract,
125104
NestRequestShapes,
126105
NestResponseShapes,

apps/docs/docs/nest/nest.mdx

Lines changed: 5 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -167,125 +167,18 @@ This can be somewhat mitigated if you share the error code schema between multip
167167

168168
## Configuration
169169

170-
To configure certain ts-rest options you can use the `@TsRest` decorator on either your Controller or use the existing `@Api` decorator.
170+
To configure `ts-rest` options you can use the `@TsRest` decorator on either your controller or use the existing `@TsRestHandler` decorator on your method.
171+
Controller options will be applied to all routes in the controller and will override any global options, and method options will override the controller options for that specific route.
171172

172-
### JSON Query Parameters
173-
174-
To handle JSON query parameters, pass the `jsonQuery` option to the `@TsRest` decorator on your entire controller.
175-
176-
```typescript
177-
import { TsRest } from '@ts-rest/nest';
178-
179-
@Controller()
180-
@TsRest({ jsonQuery: true })
181-
export class MyController {}
182-
```
183-
184-
You can also use the method decorator to override or configure options for a specific route.
173+
Check out the [configuration](/docs/nest/configuration/) section for more details on the different configuration options, or on how to configure options globally.
185174

186175
```typescript
187176
import { TsRestHandler, tsRestHandler } from '@ts-rest/nest';
188177

189178
@Controller()
179+
@TsRest({ jsonQuery: true })
190180
export class MyController {
191-
constructor(private readonly service: Service) {}
192-
193-
@TsRestHandler(c.getPost, { jsonQuery: true })
194-
async getPost() {
195-
return tsRestHandler(c.getPost, async () => {
196-
// ...
197-
});
198-
}
199-
}
200-
```
201-
202-
### Request Validation
203-
By default, `ts-rest` validates all request components - body, headers and query parameters.
204-
In case of validation errors, the server responds with a `ZodError` object in the response body and a status code of 400.
205-
You can disable the validation of these components if you wish to perform the validation manually or handle the error differently.
206-
207-
Using the `@TsRest()` decorator, you can configure request validation parameters for the entire controller:
208-
```typescript
209-
import { TsRest } from '@ts-rest/nest';
210-
211-
@Controller()
212-
@TsRest({
213-
validateRequestBody: false,
214-
validateRequestQuery: false,
215-
validateRequestHeaders: false
216-
})
217-
class MyController {}
218-
```
219-
220-
You can also override controller decorator values for a specific route:
221-
222-
```typescript
223-
import { TsRest } from '@ts-rest/nest';
224-
225-
@Controller()
226-
@TsRest({
227-
validateRequestBody: false,
228-
})
229-
class MyController {
230-
@TsRest(contract.create, {
231-
validateRequestBody: true
232-
})
233-
async create(@TsRestRequest() { body }) {
234-
// body validated
235-
}
236-
}
237-
```
238-
239-
Or finally, using `@TsRestHandler()` for specific route:
240-
241-
```typescript
242-
import { TsRestHandler, tsRestHandler } from '@ts-rest/nest';
243-
244-
@Controller()
245-
export class MyController {
246-
constructor() {}
247-
248-
@TsRestHandler(c.getPost, {
249-
validateRequestBody: false,
250-
validateRequestQuery: false,
251-
validateRequestHeaders: false
252-
})
253-
async getPost() {
254-
return tsRestHandler(c.getPost, async ({ query, body }) => {
255-
const isQueryValid = querySchema.safeParse(query);
256-
console.log(isQueryValid) // => { success: false; error: ZodError }
257-
258-
const isBodyValid = bodySchema.safeParse(body);
259-
console.log(isBodyValid) // => { success: true; data: {...} }
260-
});
261-
}
262-
}
263-
```
264-
265-
### Response Validation
266-
267-
You can enable response validation by passing the `validateResponses` option to the `@TsRest()` decorator.
268-
This will enable response parsing and validation for a returned status code, if there is a corresponding response Zod schema defined in the contract.
269-
This is useful for ensuring absolute safety that your controller is returning the correct response types as well as stripping any extra properties.
270-
271-
```typescript
272-
import { TsRest } from '@ts-rest/nest';
273-
274-
@Controller()
275-
@TsRest({ validateResponses: true })
276-
export class MyController {}
277-
```
278-
279-
You can also use the method decorator to override or configure options for a specific route.
280-
281-
```typescript
282-
import { TsRestHandler, tsRestHandler } from '@ts-rest/nest';
283-
284-
@Controller()
285-
export class MyController {
286-
constructor(private readonly service: Service) {}
287-
288-
@TsRestHandler(c.getPost, { validateResponses: true })
181+
@TsRestHandler(c.getPost, { jsonQuery: false })
289182
async getPost() {
290183
return tsRestHandler(c.getPost, async () => {
291184
// ...
@@ -294,9 +187,6 @@ export class MyController {
294187
}
295188
```
296189

297-
If validation fails a `ResponseValidationError` will be thrown causing a 500 response to be returned.
298-
You can catch this error and handle it as you wish by using a [NestJS exception filter](https://docs.nestjs.com/exception-filters).
299-
300190
## Gotchas
301191

302192
:::caution

apps/docs/sidebars.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ const sidebars = {
9393
items: [
9494
{ type: 'doc', id: 'nest/nest' },
9595
{ type: 'doc', id: 'nest/legacy' },
96+
{ type: 'doc', id: 'nest/configuration' },
9697
],
9798
},
9899
{

libs/ts-rest/nest/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
export * from './lib/constants';
2-
export * from './lib/json-query.decorator';
32
export * from './lib/ts-rest-nest';
43
export * from './lib/ts-rest.decorator';
54
export * from './lib/ts-rest.interceptor';
65
export * from './lib/ts-rest-request.decorator';
76
export * from './lib/ts-rest-nest-handler';
7+
export { TsRestModule } from './lib/ts-rest.module';
8+
export { TsRestOptions } from './lib/ts-rest-options';
Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,2 @@
11
export const TsRestAppRouteMetadataKey = Symbol('ts-rest-app-route');
2-
export const JsonQuerySymbol = Symbol('ts-rest-json-query');
3-
export const ValidateResponsesSymbol = Symbol('ts-rest-validate-responses');
4-
export const ValidateRequestHeadersSymbol = Symbol(
5-
'ts-rest-validate-request-headers',
6-
);
7-
export const ValidateRequestQuerySymbol = Symbol(
8-
'ts-rest-validate-request-query',
9-
);
10-
export const ValidateRequestBodySymbol = Symbol(
11-
'ts-rest-validate-request-body',
12-
);
2+
export const TsRestOptionsMetadataKey = Symbol('ts-rest-options');

libs/ts-rest/nest/src/lib/json-query.decorator.ts

Lines changed: 0 additions & 9 deletions
This file was deleted.

0 commit comments

Comments
 (0)