Skip to content

Commit dbf2d9b

Browse files
authored
Merge pull request #14 from storyofams/feat/req-res-decorators
feat: request and response decorators
2 parents 092db89 + bf5c84d commit dbf2d9b

File tree

5 files changed

+98
-9
lines changed

5 files changed

+98
-9
lines changed

lib/decorators/parameter.decorator.spec.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
/* eslint-disable @typescript-eslint/no-unused-vars */
22
/* eslint-disable @typescript-eslint/no-empty-function */
33
import 'reflect-metadata';
4-
import { Body, PARAMETER_TOKEN, Header, Query } from './parameter.decorators';
4+
import type { NextApiRequest, NextApiResponse } from 'next';
5+
import { Body, PARAMETER_TOKEN, Req, Request, Res, Response, Header, Query } from './parameter.decorators';
56

67
describe('Parameter decorators', () => {
78
it('Body should be set.', () => {
@@ -51,4 +52,42 @@ describe('Parameter decorators', () => {
5152
])
5253
);
5354
});
55+
56+
it('Req should be set.', () => {
57+
class Test {
58+
public index(@Req() req: NextApiRequest) {}
59+
}
60+
61+
const meta = Reflect.getMetadata(PARAMETER_TOKEN, Test, 'index');
62+
expect(Array.isArray(meta)).toBe(true);
63+
expect(meta).toHaveLength(1);
64+
expect(meta).toMatchObject(expect.arrayContaining([{ index: 0, location: 'request' }]));
65+
});
66+
67+
it('Res should be set.', () => {
68+
class Test {
69+
public index(@Res() res: NextApiResponse) {}
70+
}
71+
72+
const meta = Reflect.getMetadata(PARAMETER_TOKEN, Test, 'index');
73+
expect(Array.isArray(meta)).toBe(true);
74+
expect(meta).toHaveLength(1);
75+
expect(meta).toMatchObject(expect.arrayContaining([{ index: 0, location: 'response' }]));
76+
});
77+
78+
it('Request and Response should be set.', () => {
79+
class Test {
80+
public index(@Request() req: NextApiRequest, @Response() res: NextApiResponse) {}
81+
}
82+
83+
const meta = Reflect.getMetadata(PARAMETER_TOKEN, Test, 'index');
84+
expect(Array.isArray(meta)).toBe(true);
85+
expect(meta).toHaveLength(2);
86+
expect(meta).toMatchObject(
87+
expect.arrayContaining([
88+
{ index: 0, location: 'request' },
89+
{ index: 1, location: 'response' }
90+
])
91+
);
92+
});
5493
});

lib/decorators/parameter.decorators.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { ParameterPipe } from '../pipes/ParameterPipe';
22

33
export interface MetaParameter {
44
index: number;
5-
location: 'query' | 'body' | 'header' | 'method';
5+
location: 'query' | 'body' | 'header' | 'method' | 'request' | 'response';
66
name?: string;
77
pipes?: ParameterPipe<any>[];
88
}
@@ -41,3 +41,23 @@ export function Body(): ParameterDecorator {
4141
export function Header(name: string): ParameterDecorator {
4242
return addParameter('header', name);
4343
}
44+
45+
/** Returns the `req` object. */
46+
export function Req(): ParameterDecorator {
47+
return addParameter('request');
48+
}
49+
50+
/** Returns the `req` object. */
51+
export function Request(): ParameterDecorator {
52+
return Req();
53+
}
54+
55+
/** Returns the `res` object. */
56+
export function Res(): ParameterDecorator {
57+
return addParameter('response');
58+
}
59+
60+
/** Returns the `res` object. */
61+
export function Response(): ParameterDecorator {
62+
return Res();
63+
}

lib/e2e.test.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import 'reflect-metadata';
22
import { IsBoolean, IsDate, IsEnum, IsInt, IsNotEmpty, IsOptional } from 'class-validator';
33
import express from 'express';
4+
import type { NextApiRequest, NextApiResponse } from 'next';
45
import request from 'supertest';
56
import { createHandler } from './createHandler';
6-
import { Body, Delete, Get, Header, HttpCode, Post, Put, Query, SetHeader } from './decorators';
7+
import { Body, Delete, Get, Header, HttpCode, Post, Put, Query, Req, Response, SetHeader } from './decorators';
78
import { ParseBooleanPipe } from './pipes/parseBoolean.pipe';
89
import { ParseNumberPipe } from './pipes/parseNumber.pipe';
910

@@ -67,8 +68,12 @@ class TestHandler {
6768

6869
@Delete()
6970
@SetHeader('X-Method', 'delete')
70-
public delete(@Header('Content-Type') contentType: string, @Query('id') id: string, @Body() body: any) {
71-
return { contentType, id, receivedBody: body, test: this.testField };
71+
public delete(@Req() req: NextApiRequest, @Response() res: NextApiResponse) {
72+
const { headers, query, body } = req;
73+
const { 'content-type': contentType } = headers;
74+
const { id } = query;
75+
76+
return res.status(200).json({ contentType, id, receivedBody: body, test: this.testField });
7277
}
7378
}
7479

lib/index.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
import 'reflect-metadata';
22

33
export * from './createHandler';
4-
export { Body, Delete, Get, Header, HttpCode, HttpVerb, Post, Put, Query, SetHeader } from './decorators';
4+
export {
5+
Body,
6+
Delete,
7+
Get,
8+
Header,
9+
HttpCode,
10+
HttpVerb,
11+
Post,
12+
Put,
13+
Query,
14+
SetHeader,
15+
Req,
16+
Request,
17+
Res,
18+
Response
19+
} from './decorators';
520
export * from './exceptions';
621
export * from './pipes';

lib/internals/handler.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ServerResponse } from 'http';
12
import { Stream } from 'stream';
23
import type { ClassConstructor } from 'class-transformer';
34
import type { NextApiRequest, NextApiResponse } from 'next';
@@ -8,6 +9,7 @@ import { notFound } from './notFound';
89

910
async function getParameterValue(
1011
req: NextApiRequest,
12+
res: NextApiResponse,
1113
bodyParamType: ClassConstructor<any>[],
1214
{ location, name, index }: MetaParameter
1315
): Promise<string | object | undefined> {
@@ -25,6 +27,10 @@ async function getParameterValue(
2527
return name ? req.headers[name.toLowerCase()] : req.headers;
2628
case 'method':
2729
return req.method;
30+
case 'request':
31+
return req;
32+
case 'response':
33+
return res;
2834
default:
2935
return undefined;
3036
}
@@ -59,7 +65,7 @@ export function Handler(method?: HttpVerb): MethodDecorator {
5965
try {
6066
const parameters = await Promise.all(
6167
metaParameters.map(async ({ location, name, pipes, index }) => {
62-
let returnValue = await getParameterValue(req, bodyParamType, { location, name, index });
68+
let returnValue = await getParameterValue(req, res, bodyParamType, { location, name, index });
6369

6470
if (returnValue && pipes && pipes.length) {
6571
pipes.forEach(pipeFn => (returnValue = pipeFn(returnValue)));
@@ -69,11 +75,15 @@ export function Handler(method?: HttpVerb): MethodDecorator {
6975
})
7076
);
7177

72-
const returnValue = await originalHandler.call(this, ...parameters);
73-
7478
classHeaders?.forEach((value, name) => res.setHeader(name, value));
7579
methodHeaders?.forEach((value, name) => res.setHeader(name, value));
7680

81+
const returnValue = await originalHandler.call(this, ...parameters);
82+
83+
if (returnValue instanceof ServerResponse) {
84+
return;
85+
}
86+
7787
res.status(httpCode ?? (returnValue != null ? 200 : 204));
7888

7989
if (returnValue instanceof Stream) {

0 commit comments

Comments
 (0)