diff --git a/docs/blog/version-5.0-release-notes.md b/docs/blog/version-5.0-release-notes.md index 27c0138f18..2838e97cc0 100644 --- a/docs/blog/version-5.0-release-notes.md +++ b/docs/blog/version-5.0-release-notes.md @@ -66,6 +66,51 @@ Version 5.0 of [Foal](https://foalts.org/) is out! - The return value of the social services `getUserInfoFromTokens` method is now typed. +## Controller parameters + +To facilitate the typing of the request body, path parameters and request parameters in controllers, the request object is now passed as a second argument to controller methods. + +```typescript + interface MyQuery { + // ... + } + + interface MyBody { + // ... + } + + interface MyParams { + // ... + } + + // Version 4 + class MyController { + @Get('/foobar') + foobar(ctx: Context) { + const query = ctx.request.query as MyQuery; + const body = ctx.request.body as MyQuery; + const params = ctx.request.params as MyParams; + + // Do something + } + // OR + @Get('/foobar') + foobar(ctx: Context, params: MyParams, body: MyBody) { + const query = ctx.request.query as MyQuery; + + // Do something + } + } + + // Version 5 + class MyController { + @Get('/foobar') + foobar(ctx: Context, { query, body, params }: { query: MyQuery, body: MyBody, params: MyParams }) { + // Do something + } + } +``` + ## Logging - The `Logger.addLogContext(key, value)` method now accepts a record as parameter: `Logger.addLogContext(context)`. This makes the function's signature more consistent with other logging methods (`info`, `warn`, etc.) and allows multiple keys/values to be passed at once. diff --git a/docs/docs/architecture/controllers.md b/docs/docs/architecture/controllers.md index 6e279ec098..f8161d12f1 100644 --- a/docs/docs/architecture/controllers.md +++ b/docs/docs/architecture/controllers.md @@ -253,14 +253,14 @@ class AppController { #### The Controller Method Arguments -The path paramaters and request body are also passed as second and third arguments to the controller method. +The request body is also passed as second argument to the controller method. ```typescript -import { Context, HttpResponseCreated, Put } from '@foal/core'; +import { Context, HttpResponseCreated, Put, Request } from '@foal/core'; class AppController { @Put('/products/:id') - updateProduct(ctx: Context, { id }, body) { + updateProduct(ctx: Context, request: Request) { // Do something. return new HttpResponseCreated(); } diff --git a/packages/acceptance-tests/src/docs/architecture/controllers/reading-the-request-information.feature.ts b/packages/acceptance-tests/src/docs/architecture/controllers/reading-the-request-information.feature.ts index 928cd58d6a..9412d31cb2 100644 --- a/packages/acceptance-tests/src/docs/architecture/controllers/reading-the-request-information.feature.ts +++ b/packages/acceptance-tests/src/docs/architecture/controllers/reading-the-request-information.feature.ts @@ -150,9 +150,9 @@ describe('Feature: Reading the request information', () => { class AppController { @Put('/products/:id') - updateProduct(ctx: Context, { id }: any, body: any) { + updateProduct(ctx: Context, request: Request) { // Do something. - return new HttpResponseCreated({ id, body }); + return new HttpResponseCreated({ id, body: request.body }); } } diff --git a/packages/core/src/core/http/context.ts b/packages/core/src/core/http/context.ts index 18522ad192..3fe69db2af 100644 --- a/packages/core/src/core/http/context.ts +++ b/packages/core/src/core/http/context.ts @@ -29,9 +29,10 @@ interface IncomingMessage extends Readable { /** * Express Request interface. * + * @export * @interface Request */ -interface Request extends IncomingMessage { +export interface Request extends IncomingMessage { app: any; baseUrl: string; body: any; diff --git a/packages/core/src/core/routes/get-response.spec.ts b/packages/core/src/core/routes/get-response.spec.ts index adc37c9e9b..771fa8af65 100644 --- a/packages/core/src/core/routes/get-response.spec.ts +++ b/packages/core/src/core/routes/get-response.spec.ts @@ -89,17 +89,15 @@ describe('getResponse', () => { strictEqual(actualServices, services); }); - it('should call the controller method with the proper context, request params and request body.', async () => { + it('should call the controller method with the proper context and request.', async () => { let actualCtx: Context|undefined; - let actualParams: any; - let actualBody: any; + let actualRequest: Request|undefined; const route = createRoute({ controller: { - fn: (ctx: Context, params: any, body: any) => { + fn: (ctx: Context, request: Request) => { actualCtx = ctx; - actualParams = params; - actualBody = body; + actualRequest = request; return new HttpResponseOK(); } }, @@ -113,8 +111,7 @@ describe('getResponse', () => { await getResponse(route, ctx, services, appController); strictEqual(actualCtx, ctx); - strictEqual(actualParams, ctx.request.params); - strictEqual(actualBody, ctx.request.body); + strictEqual(actualRequest, ctx.request); }); context('given a hook returns an HttpResponse object', () => { diff --git a/packages/core/src/core/routes/get-response.ts b/packages/core/src/core/routes/get-response.ts index 7be6894a05..9273ec4fc0 100644 --- a/packages/core/src/core/routes/get-response.ts +++ b/packages/core/src/core/routes/get-response.ts @@ -32,7 +32,7 @@ export async function getResponse( if (!isHttpResponse(response)) { try { - response = await route.controller[route.propertyKey](ctx, ctx.request.params, ctx.request.body); + response = await route.controller[route.propertyKey](ctx, ctx.request); } catch (error: any) { response = await convertErrorToResponse(error, ctx, appController, logger); } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 91071f1915..a87a17fccf 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -73,6 +73,7 @@ export { ConfigNotFoundError, ConfigTypeError, Context, + Request, CookieOptions, Delete, Dependency,