Node.js
ts-node
routing-controllers

routing-controllersを使用したNode.jsアプリでtscをts-nodeに切り替えるとParameterParseJsonError.HttpError


現象

Node.js (Express & routing-controllers & TypeORM)プロジェクトを、tscでビルドして起動からts-node直接起動に変えると下記のエラーが発生する

Error

at ParameterParseJsonError.HttpError [as constructor] (c:\workspace\liberapp.net\src\http-error\HttpError.ts:19:22)
at ParameterParseJsonError.BadRequestError [as constructor] (c:\workspace\liberapp.net\src\http-error\BadRequestError.ts:10:9)
at new ParameterParseJsonError (c:\workspace\liberapp.net\src\error\ParameterParseJsonError.ts:10:9)
at ActionParameterHandler.parseValue (c:\workspace\liberapp.net\node_modules\routing-controllers\ActionParameterHandler.js:151:15)
at ActionParameterHandler.normalizeParamValue (c:\workspace\liberapp.net\src\ActionParameterHandler.ts:163:15)
at ActionParameterHandler.handle (c:\workspace\liberapp.net\src\ActionParameterHandler.ts:40:19)
at c:\workspace\liberapp.net\src\RoutingControllers.ts:116:49
at Array.map (<anonymous>)
at RoutingControllers.executeAction (c:\workspace\liberapp.net\src\RoutingControllers.ts:116:14)
at c:\workspace\liberapp.net\src\RoutingControllers.ts:83:33
(node:9720) UnhandledPromiseRejectionWarning: Error
at AuthorizationRequiredError.HttpError [as constructor] (c:\workspace\liberapp.net\src\http-error\HttpError.ts:19:22)
at AuthorizationRequiredError.UnauthorizedError [as constructor] (c:\workspace\liberapp.net\src\http-error\UnauthorizedError.ts:10:9)
at new AuthorizationRequiredError (c:\workspace\liberapp.net\src\error\AuthorizationRequiredError.ts:12:9)
at c:\workspace\liberapp.net\src\ActionParameterHandler.ts:98:10
at <anonymous>
at process._tickDomainCallback (internal/process/next_tick.js:228:7)


旧コード


controller.ts

  @Post("/changeApplicationStatus")

async updateStatus(
@CurrentUser({ required: true }) user: User,
@BodyParam("applicationId", { required: true }) applicationId: number,
@BodyParam("status", { required: true }) status: ApplicationStatus
) {
return jsonApiResult(
await Applications.updateStatus(user.publisherId, applicationId, status)
);
}


entitiy.ts

export enum ApplicationStatus {

PublicReleased = "PublicReleased",
LimitedReleased = "LimitedReleased",
Private = "Private",
Archived = "Archived",
Banned = "Banned"
}


gulp

gulp.task(

"start-dev",
gulp.series("build", gulp.parallel("build", "start-nodemon"))
);

gulp.task("start-nodemon", function() {
nodemon({
script: "./dist/www",
env: { NODE_ENV: "development", DEBUG: "*:*" },
delay: 3000
});
});

gulp.task("build", () => {
return gulp
.src(["./src/**/*.ts"])
.pipe(tsProject())
.pipe(gulp.dest("./dist"));
});



tsconfig.json

{

"compilerOptions": {
"module": "commonjs",
"target": "es6",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true,
"types": ["node"],
"rootDirs": ["src"],
"outDir": "dist"
},
"include": ["src/**/*.ts"],
"exclude": ["dist","node_modules"]
}


新コード

  @Post("/changeApplicationStatus")

async updateStatus(
@CurrentUser({ required: true }) userSession: UserSession,
@BodyParam("applicationId", { required: true }) applicationId: number,
@BodyParam("status", { required: true }) status: ApplicationStatus
) {
getCustomRepository(ApplicationRepository).updateStatus(
applicationId,
status
);

return jsonApiResult(
await Applications.updateStatus(
userSession.currentPublisherId,
applicationId,
status
)
);
}

export enum ApplicationStatus {

PublicReleased = "PublicReleased",
LimitedReleased = "LimitedReleased",
Private = "Private",
Archived = "Archived",
Banned = "Banned"
}


gulp.js

gulp.task(

"start-dev",
function() {
nodemon({
watch: ["./src"],
ext: "ts",
exec: "./node_modules/.bin/ts-node ./src/www.ts",
env: { NODE_ENV: "development", DEBUG: "*:*" },
delay: 3000
});
});


tsconfig.json

{

"compilerOptions": {
"module": "commonjs",
"target": "es6",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true,
"types": ["node"],
"rootDirs": ["src"],
"outDir": "dist"
},
"include": ["src/**/*.ts"],
"exclude": ["dist""node_modules"]
}


調査

routing-controllersにダンプするコードを埋める

https://github.com/typestack/routing-controllers/blob/master/src/ActionParameterHandler.ts


ActionParameterHandler.js

  ActionParameterHandler.prototype.normalizeParamValue = function(

value,
param
) {
++ console.log(value, ",", param);


旧コード


PublicReleased,

ParamMetadata {
targetName: 'string',
isTargetObject: true,
actionMetadata:
ActionMetadata {
options:
{ controllers: [Array],
authorizationChecker: [Function: authorizationChecker],
currentUserChecker: [Function: currentUserChecker] },
controllerMetadata:
ControllerMetadata {
target: [Function: ApiController],
route: '/api',
type: 'json',
isAuthorizedUsed: false,
authorizedRoles: [],
actions: [Array],
uses: [],
interceptors: [Array] },
route: '/changeApplicationStatus',
target: [Function: ApiController],
method: 'updateStatus',
type: 'post',
appendParams: undefined,
methodOverride: undefined,
params: [ [Object], [Object], [Circular] ],
uses: [],
interceptors: [],
undefinedResultCode: undefined,
nullResultCode: undefined,
bodyExtraOptions: undefined,
isBodyUsed: true,
isFilesUsed: false,
isFileUsed: false,
isJsonTyped: true,
fullRoute: '/api/changeApplicationStatus',
headers: {},
isAuthorizedUsed: false,
authorizedRoles: [] },
target: [Function: ApiController],
method: 'updateStatus',
extraOptions: undefined,
index: 2,
type: 'body-param',
name: 'status',
parse: undefined,
required: true,
transform: undefined,
classTransform: undefined,
validate: undefined,
targetType: [Function: String] }


ts-node書き換え後のコード

 LimitedReleased,

ParamMetadata {
targetName: 'object',
isTargetObject: true,
actionMetadata:
ActionMetadata {
options:
{ controllers: [Array],
authorizationChecker: [Function: authorizationChecker],
currentUserChecker: [Function: currentUserChecker] },
controllerMetadata:
ControllerMetadata {
target: [Function: ApiController],
route: '/api',
type: 'json',
isAuthorizedUsed: false,
authorizedRoles: [],
actions: [Array],
uses: [],
interceptors: [Array] },
route: '/changeApplicationStatus',
target: [Function: ApiController],
method: 'updateStatus',
type: 'post',
appendParams: undefined,
methodOverride: undefined,
params: [ [Object], [Object], [Circular] ],
uses: [],
interceptors: [],
undefinedResultCode: undefined,
nullResultCode: undefined,
bodyExtraOptions: undefined,
isBodyUsed: true,
isFilesUsed: false,
isFileUsed: false,
isJsonTyped: true,
fullRoute: '/api/changeApplicationStatus',
headers: {},
isAuthorizedUsed: false,
authorizedRoles: [] },
target: [Function: ApiController],
method: 'updateStatus',
extraOptions: undefined,
index: 2,
type: 'body-param',
name: 'status',
parse: undefined,
required: true,
transform: undefined,
classTransform: undefined,
validate: undefined,
targetType: [Function: Object] }


解決

コチラの記事のコレで解決

https://github.com/TypeStrong/ts-node/issues/515


Running ts-node in type check mode should fix this problem:

$ TS_NODE_TYPE_CHECK=Y ts-node



gulp.js

gulp.task(

"start-dev",
function() {
nodemon({
watch: ["./src"],
ext: "ts",
exec: "./node_modules/.bin/ts-node ./src/www.ts",
- env: { NODE_ENV: "development", DEBUG: "*:*" },
+ env: { NODE_ENV: "development", DEBUG: "*:*", TS_NODE_TYPE_CHECK: "Y" },
delay: 3000
});
});