Skip to content

Commit dc50de6

Browse files
author
Aleksi Pekkala
committed
Add a useResponseClassTransformer global option
1 parent 0ee97fd commit dc50de6

File tree

5 files changed

+78
-28
lines changed

5 files changed

+78
-28
lines changed

README.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,10 @@ If you specify a class type to parameter that is decorated with parameter decora
509509
routing-controllers will use [class-transformer][4] to create instance of that class type.
510510
More info about this feature is available [here](#creating-instances-of-classes-from-action-params).
511511

512+
#### Disable response transformation
513+
514+
By default response values are coerced to plain objects with [class-transformer](https://github.com/pleerock/class-transformer). When returning large objects or values with complex serialization logic (e.g. Mongoose documents) you might opt for the default `toJSON` handler instead. To disable response transformation simply pass `useResponseClassTransformer: false` to createExpressServer method.
515+
512516
#### Set custom ContentType
513517

514518
You can specify a custom ContentType header:
@@ -694,7 +698,7 @@ There are set of prepared errors you can use:
694698
* UnauthorizedError
695699

696700

697-
You can also create and use your own errors by extending `HttpError` class.
701+
You can also create and use your own errors by extending `HttpError` class.
698702
To define the data returned to the client, you could define a toJSON method in your error.
699703

700704
```typescript
@@ -716,7 +720,7 @@ class DbError extends HttpError {
716720
}
717721
}
718722
}
719-
```
723+
```
720724

721725
#### Enable CORS
722726

@@ -757,7 +761,7 @@ app.listen(3000);
757761

758762
#### Default settings
759763

760-
You can override default status code in routing-controllers options.
764+
You can override default status code in routing-controllers options.
761765

762766
```typescript
763767
import "reflect-metadata";
@@ -770,9 +774,9 @@ const app = createExpressServer({
770774
//with this option, null will return 404 by default
771775
nullResultCode: 404,
772776

773-
//with this option, void or Promise<void> will return 204 by default
777+
//with this option, void or Promise<void> will return 204 by default
774778
undefinedResultCode: 204,
775-
779+
776780
paramOptions: {
777781
//with this option, argument will be required by default
778782
required: true

src/RoutingControllersOptions.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ export interface RoutingControllersOptions {
4040
*/
4141
classTransformer?: boolean;
4242

43+
/**
44+
* Toggles class-transformer serialization for response values.
45+
* Overwritten by a negative classTransformer value.
46+
*/
47+
useResponseClassTransformer?: boolean;
48+
4349
/**
4450
* Global class transformer options passed to class-transformer during classToPlain operation.
4551
* This operation is being executed when server returns response to user.
@@ -85,7 +91,7 @@ export interface RoutingControllersOptions {
8591
* Special function used to get currently authorized user.
8692
*/
8793
currentUserChecker?: CurrentUserChecker;
88-
94+
8995
/**
9096
* Default settings
9197
*/
@@ -110,4 +116,4 @@ export interface RoutingControllersOptions {
110116
required?: boolean;
111117
};
112118
};
113-
}
119+
}

src/driver/BaseDriver.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ export abstract class BaseDriver {
2929
*/
3030
useClassTransformer: boolean;
3131

32+
/**
33+
* Toggles class-transformer serialization for response values.
34+
* Overwritten by a negative classTransformer value.
35+
*/
36+
useResponseClassTransformer: boolean;
37+
3238
/**
3339
* Indicates if class-validator should be used or not.
3440
*/
@@ -44,7 +50,7 @@ export abstract class BaseDriver {
4450
* Global class-validator options passed during validate operation.
4551
*/
4652
validationOptions: ValidatorOptions;
47-
53+
4854
/**
4955
* Global class transformer options passed to class-transformer during plainToClass operation.
5056
* This operation is being executed when parsing user parameters.
@@ -92,7 +98,7 @@ export abstract class BaseDriver {
9298
* Initializes the things driver needs before routes and middleware registration.
9399
*/
94100
abstract initialize(): void;
95-
101+
96102
/**
97103
* Registers given middleware.
98104
*/
@@ -129,14 +135,14 @@ export abstract class BaseDriver {
129135

130136
protected transformResult(result: any, action: ActionMetadata, options: Action): any {
131137
// check if we need to transform result
132-
const shouldTransform = (this.useClassTransformer && result != null) // transform only if enabled and value exist
138+
const shouldTransform = (this.useClassTransformer && this.useResponseClassTransformer && result != null) // transform only if enabled and value exist
133139
&& result instanceof Object // don't transform primitive types (string/number/boolean)
134140
&& !(
135141
result instanceof Uint8Array // don't transform binary data
136142
||
137143
result.pipe instanceof Function // don't transform streams
138144
);
139-
145+
140146
// transform result if needed
141147
if (shouldTransform) {
142148
const options = action.responseClassTransformOptions || this.classToPlainTransformOptions;
@@ -152,7 +158,7 @@ export abstract class BaseDriver {
152158

153159
if (typeof error.toJSON === "function")
154160
return error.toJSON();
155-
161+
156162
let processedError: any = {};
157163
if (error instanceof Error) {
158164
const name = error.name && error.name !== "Error" ? error.name : error.constructor.name;

src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,12 @@ export function createExecutor<T extends BaseDriver>(driver: T, options: Routing
190190
driver.useClassTransformer = true;
191191
}
192192

193+
if (options.useResponseClassTransformer !== undefined) {
194+
driver.useResponseClassTransformer = options.useResponseClassTransformer;
195+
} else {
196+
driver.useResponseClassTransformer = true;
197+
}
198+
193199
if (options.validation !== undefined) {
194200
driver.enableValidation = !!options.validation;
195201
if (options.validation instanceof Object)

test/functional/global-options.spec.ts

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,14 @@ const expect = chakram.expect;
1010
export class User {
1111
firstName: string;
1212
lastName: string;
13-
getName(): string {
14-
return this.firstName + " " + this.lastName;
13+
password: string;
14+
15+
/**
16+
* This method only gets called when class-transformer serialization for
17+
* response values is disabled.
18+
*/
19+
toJSON() {
20+
return {firstName: this.firstName, lastName: this.lastName};
1521
}
1622
}
1723

@@ -34,43 +40,49 @@ describe("routing-controllers global options", () => {
3440
@Post("/users")
3541
postUsers(@Body() user: User) {
3642
initializedUser = user;
37-
return "";
43+
const ret = new User();
44+
ret.firstName = user.firstName;
45+
ret.lastName = user.lastName;
46+
ret.password = "1234";
47+
return ret;
3848
}
39-
49+
4050
@Post(new RegExp("/(prefix|regex)/users"))
4151
postUsersWithRegex(@Body() user: User) {
4252
initializedUser = user;
4353
return "";
4454
}
45-
55+
4656
}
4757
});
4858

49-
describe("useClassTransformer by default must be set to true", () => {
59+
describe("useClassTransformer and useResponseClassTransformer by default must be set to true", () => {
5060

5161
let expressApp: any, koaApp: any;
5262
before(done => expressApp = createExpressServer().listen(3001, done));
5363
after(done => expressApp.close(done));
5464
before(done => koaApp = createKoaServer().listen(3002, done));
5565
after(done => koaApp.close(done));
5666

57-
assertRequest([3001, 3002], "post", "users", { firstName: "Umed", lastName: "Khudoiberdiev" }, response => {
67+
assertRequest([3001, 3002], "post", "users", { firstName: "Umed", lastName: "Khudoiberdiev", password: "1234" }, response => {
5868
expect(initializedUser).to.be.instanceOf(User);
5969
expect(response).to.have.status(200);
70+
expect(response.body.password).to.be.defined;
6071
});
6172
});
6273

63-
describe("when useClassTransformer is set to true", () => {
74+
describe("when useClassTransformer and useResponseClassTransformer are set to true", () => {
6475

6576
let expressApp: any, koaApp: any;
66-
before(done => expressApp = createExpressServer({ classTransformer: true }).listen(3001, done));
77+
before(done => expressApp = createExpressServer({ classTransformer: true, useResponseClassTransformer: true }).listen(3001, done));
6778
after(done => expressApp.close(done));
68-
before(done => koaApp = createKoaServer({ classTransformer: true }).listen(3002, done));
79+
before(done => koaApp = createKoaServer({ classTransformer: true, useResponseClassTransformer: true }).listen(3002, done));
6980
after(done => koaApp.close(done));
7081

71-
assertRequest([3001, 3002], "post", "users", { firstName: "Umed", lastName: "Khudoiberdiev" }, response => {
82+
assertRequest([3001, 3002], "post", "users", { firstName: "Umed", lastName: "Khudoiberdiev", password: "1234" }, response => {
7283
expect(initializedUser).to.be.instanceOf(User);
7384
expect(response).to.have.status(200);
85+
expect(response.body.password).to.be.defined;
7486
});
7587
});
7688

@@ -81,22 +93,38 @@ describe("routing-controllers global options", () => {
8193
after(done => expressApp.close(done));
8294
before(done => koaApp = createKoaServer({ classTransformer: false }).listen(3002, done));
8395
after(done => koaApp.close(done));
84-
85-
assertRequest([3001, 3002], "post", "users", { firstName: "Umed", lastName: "Khudoiberdiev" }, response => {
96+
97+
assertRequest([3001, 3002], "post", "users", { firstName: "Umed", lastName: "Khudoiberdiev", password: "1234" }, response => {
8698
expect(initializedUser).not.to.be.instanceOf(User);
8799
expect(response).to.have.status(200);
100+
expect(response.body.password).to.be.undefined;
101+
});
102+
});
103+
104+
describe("when useClassTransformer is set but useResponseClassTransformer is not", () => {
105+
106+
let expressApp: any, koaApp: any;
107+
before(done => expressApp = createExpressServer({ useResponseClassTransformer: false }).listen(3001, done));
108+
after(done => expressApp.close(done));
109+
before(done => koaApp = createKoaServer({ useResponseClassTransformer: false }).listen(3002, done));
110+
after(done => koaApp.close(done));
111+
112+
assertRequest([3001, 3002], "post", "users", { firstName: "Umed", lastName: "Khudoiberdiev", password: "1234" }, response => {
113+
expect(initializedUser).to.be.instanceOf(User);
114+
expect(response).to.have.status(200);
115+
expect(response.body.password).to.be.undefined;
88116
});
89117
});
90118

91119
describe("when routePrefix is used all controller routes should be appended by it", () => {
92-
120+
93121
let apps: any[] = [];
94122
before(done => apps.push(createExpressServer({ routePrefix: "/api" }).listen(3001, done)));
95123
before(done => apps.push(createExpressServer({ routePrefix: "api" }).listen(3002, done)));
96124
before(done => apps.push(createKoaServer({ routePrefix: "/api" }).listen(3003, done)));
97125
before(done => apps.push(createKoaServer({ routePrefix: "api" }).listen(3004, done)));
98126
after(done => { apps.forEach(app => app.close()); done(); });
99-
127+
100128
assertRequest([3001, 3002, 3003, 3004], "post", "api/users", { firstName: "Umed", lastName: "Khudoiberdiev" }, response => {
101129
expect(initializedUser).to.be.instanceOf(User);
102130
expect(response).to.have.status(200);
@@ -108,4 +136,4 @@ describe("routing-controllers global options", () => {
108136
});
109137
});
110138

111-
});
139+
});

0 commit comments

Comments
 (0)