Skip to content

Commit 3c12e03

Browse files
committed
provide raw input to handlers
1 parent 376bffe commit 3c12e03

File tree

3 files changed

+93
-24
lines changed

3 files changed

+93
-24
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## 1.4.0 (2025-06-13)
4+
5+
### Features
6+
7+
- Provide `rawInput` to handlers with input schemas. This is the request body parsed by NestJS, but before zod.
8+
39
## 1.3.1 (2025-05-26)
410

511
### Bugfixes

packages/nestjs-endpoints/src/endpoint-fn.ts

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ type HandlerMethod<
156156
input: z.output<
157157
ExtractSchemaFromSchemaDef<NonNullable<InputSchema>>
158158
>;
159+
rawInput: unknown;
159160
}) & {
160161
response: <
161162
Status extends OutputMapKey<OutputSchema>,
@@ -444,12 +445,13 @@ export function endpoint<
444445
}
445446
};
446447

447-
// invoke method
448-
(cls.prototype as any).invoke = async function (rawInput: any) {
449-
const handlerParams: Record<string | symbol, any> = {
450-
response,
451-
schemas: {},
452-
};
448+
const response = (s: number, b: any) => new EndpointResponse(s, b);
449+
450+
const commonHandlerLogic = async function (
451+
this: any,
452+
handlerParams: Record<string | symbol, any>,
453+
rawInput: any,
454+
) {
453455
if (inject) {
454456
for (const key of Object.keys(inject)) {
455457
handlerParams[key] = this[key];
@@ -463,10 +465,25 @@ export function endpoint<
463465
throw new ZodValidationException(parsed.error);
464466
}
465467
handlerParams.input = parsed.data;
468+
handlerParams.rawInput = rawInput;
466469
handlerParams.schemas.input = schema;
467470
}
468471
// eslint-disable-next-line @typescript-eslint/await-thenable
469472
const result: any = await handler(handlerParams as any);
473+
return result;
474+
};
475+
476+
// invoke method
477+
(cls.prototype as any).invoke = async function (rawInput: any) {
478+
const handlerParams: Record<string | symbol, any> = {
479+
response,
480+
schemas: {},
481+
};
482+
const result = await commonHandlerLogic.call(
483+
this,
484+
handlerParams,
485+
rawInput,
486+
);
470487
if (result instanceof EndpointResponse) {
471488
validateOutput(result);
472489
return result;
@@ -478,7 +495,6 @@ export function endpoint<
478495
};
479496

480497
// handler method
481-
const response = (s: number, b: any) => new EndpointResponse(s, b);
482498
(cls.prototype as any).handler = async function (...params: any[]) {
483499
const injectedMethodParams: Record<string | symbol, any> =
484500
Object.fromEntries(
@@ -491,28 +507,17 @@ export function endpoint<
491507
response,
492508
schemas: {},
493509
};
494-
if (inject) {
495-
for (const key of Object.keys(inject)) {
496-
handlerParams[key] = this[key];
497-
}
498-
}
499510
if (injectMethod) {
500511
for (const key of Object.keys(injectMethod)) {
501512
handlerParams[key] = injectedMethodParams[key];
502513
}
503514
}
504-
if (input) {
505-
const schema: ZodSchema =
506-
input instanceof SchemaDef ? input.schema : input;
507-
const parsed = schema.safeParse(injectedMethodParams[inputKey]);
508-
if (parsed.error) {
509-
throw new ZodValidationException(parsed.error);
510-
}
511-
handlerParams.input = parsed.data;
512-
handlerParams.schemas.input = schema;
513-
}
514-
// eslint-disable-next-line @typescript-eslint/await-thenable
515-
const result: any = await handler(handlerParams as any);
515+
const rawInput = injectedMethodParams[inputKey];
516+
const result = await commonHandlerLogic.call(
517+
this,
518+
handlerParams,
519+
rawInput,
520+
);
516521
let endpointResponse: EndpointResponse;
517522
if (result instanceof EndpointResponse) {
518523
endpointResponse = result;
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { Module } from '@nestjs/common';
2+
import { Test, TestingModule } from '@nestjs/testing';
3+
import { endpoint, z } from 'nestjs-endpoints';
4+
import request from 'supertest';
5+
6+
describe('raw input', () => {
7+
test('raw input is available in the handler', async () => {
8+
const testEndpoint = endpoint({
9+
method: 'post',
10+
path: '/test',
11+
input: z.object({
12+
name: z.string(),
13+
}),
14+
handler: (params) => {
15+
return {
16+
input: params.input,
17+
rawInput: params.rawInput,
18+
};
19+
},
20+
});
21+
@Module({
22+
controllers: [testEndpoint],
23+
})
24+
class TestModule {}
25+
26+
const moduleFixture: TestingModule = await Test.createTestingModule({
27+
imports: [TestModule],
28+
}).compile();
29+
30+
const app = moduleFixture.createNestApplication();
31+
try {
32+
await app.init();
33+
await app.listen(0);
34+
const req = request(app.getHttpServer());
35+
36+
await req
37+
.post('/test')
38+
.send({
39+
name: 'John',
40+
age: 30,
41+
})
42+
.expect(200)
43+
.then((resp) => {
44+
expect(resp.body).toEqual({
45+
input: {
46+
name: 'John',
47+
},
48+
rawInput: {
49+
name: 'John',
50+
age: 30,
51+
},
52+
});
53+
});
54+
} finally {
55+
await app.close();
56+
}
57+
});
58+
});

0 commit comments

Comments
 (0)