Skip to content

docs: add documentation for controller inheritance #578

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 7 commits into from
Jun 17, 2020
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ You can use routing-controllers with [express.js][1] or [koa.js][2].
+ [Interceptor classes](#interceptor-classes)
+ [Global interceptors](#global-interceptors)
* [Creating instances of classes from action params](#creating-instances-of-classes-from-action-params)
* [Controller inheritance](#controller-inheritance)
* [Auto validating action params](#auto-validating-action-params)
* [Using authorization features](#using-authorization-features)
- [@Authorized decorator](#authorized-decorator)
Expand Down Expand Up @@ -1236,6 +1237,33 @@ Learn more about class-transformer and how to handle more complex object constru
This behaviour is enabled by default.
If you want to disable it simply pass `classTransformer: false` to createExpressServer method. Alternatively you can disable transforming for [individual controllers or routes](#selectively-disable-requestresponse-transforming).

## Controller Inheritance
Often your application may need to have an option to inherit controller from another to reuse code and void duplication.
A good example of the use is the CRUD operations which can be hidden inside `AbstractBaseController` with the possibility to add new and overload methods, the template method pattern.

```typescript
@Controller(`/product`)
class ProductController extends AbstractControllerTemplate {}
@Controller(`/category`)
class CategoryController extends AbstractControllerTemplate {}
abstract class AbstractControllerTemplate {
@Post()
public create() {}

@Read()
public read() {}

@Put()
public update() {}

@Delete()
public delete() {}
}

```
https://en.wikipedia.org/wiki/Template_method_pattern


## Auto validating action params

Sometimes parsing a json object into instance of some class is not enough.
Expand Down
10 changes: 9 additions & 1 deletion sample/sample17-controllers-inheritance/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,12 @@ useExpressServer(app, {
});
app.listen(3001); // run express app

console.log("Express server is running on port 3001. Open http://localhost:3001/blogs/ or http://localhost:3002/posts/");
console.log(
"Possible GET endpoints you may see from a browser",
"http://localhost:3001/article",
"http://localhost:3001/article/1000",
"http://localhost:3001/product",
"http://localhost:3001/product/1000",
"http://localhost:3001/category",
"http://localhost:3001/category/1000",
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import {Res} from "../../../src/decorator/Res";
import {Put} from "../../../src/decorator/Put";
import {Post} from "../../../src/decorator/Post";
import {Param} from "../../../src/decorator/Param";
import {Get} from "../../../src/decorator/Get";
import {Delete} from "../../../src/decorator/Delete";
import {Body} from "../../../src/decorator/Body";

import {MockedRepository} from "../repository/MockedRepository";
import {IInstance} from "../interface/IInstance";

/**
* @description the base controller class used by derivatives
*/
export abstract class AbstractControllerTemplate {
/**
* @description domain part of a system, also called object|entity|model
*/
protected domain: string;
protected repository: MockedRepository;

@Post()
public async create(
@Body() payload: any,
@Res() res: any
): Promise<{}> {
const item = await this.repository.create(payload);

res.status(201);
res.location(`/${this.domain}/${item.id}`);

return {};
}

@Put("/:id")
public async updated(
@Param("id") id: number,
@Body() payload: any,
@Res() res: any
): Promise<{}> {
await this.repository.update(id, payload);
res.status(204);

return {};
}

@Get("/:id")
public read(
@Param("id") id: number,
@Res() res: any
): Promise<IInstance> {
return this.repository.find(id);
}

@Get()
public readCollection(
@Res() res: any
): Promise<IInstance[]> {
return this.repository.getCollection();
}

@Delete("/:id")
public async delete(
@Param("id") id: number,
@Res() res: any
): Promise<{}> {
await this.repository.delete(id);

return {};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {Controller} from "../../../src/decorator/Controller";
import {AbstractControllerTemplate} from "./AbstractContollerTemplate";
import {MockedRepository} from "../repository/MockedRepository";

const domain = "article";

@Controller(`/${domain}`)
export class ArticleController extends AbstractControllerTemplate {
protected constructor() {
super();

this.domain = domain;
this.repository = new MockedRepository(domain);
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {Controller} from "../../../src/decorator/Controller";
import {AbstractControllerTemplate} from "./AbstractContollerTemplate";
import {MockedRepository} from "../repository/MockedRepository";

const domain = "category";

@Controller(`/${domain}`)
export class CategoryController extends AbstractControllerTemplate {
protected constructor() {
super();

this.domain = domain;
this.repository = new MockedRepository(domain);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {Controller} from "../../../src/decorator/Controller";
import {AbstractControllerTemplate} from "./AbstractContollerTemplate";
import {MockedRepository} from "../repository/MockedRepository";

const domain = "product";

@Controller(`/${domain}`)
export class ProductController extends AbstractControllerTemplate {
protected constructor() {
super();

this.domain = domain;
this.repository = new MockedRepository(domain);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface IInstance {
id: number;
type: string;
}
3 changes: 3 additions & 0 deletions sample/sample17-controllers-inheritance/interface/IPayload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface IPayload {
id: number;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import {IInstance} from "../interface/IInstance";
import {IPayload} from "../interface/IPayload";

export class MockedRepository {
protected domain: string;

constructor(domain: string) {
this.domain = domain;
}

/**
* @description Dummy method to return collection of items
*/
public getCollection(): Promise<IInstance[]> {
return Promise.resolve([
{
id: 10020,
type: this.domain
},
{
id: 10001,
type: this.domain
},
{
id: 10002,
type: this.domain
},
]);
}

/**
* @description Dummy method to create a new item in storage and return its instance
*/
public create(payload: IPayload): Promise<IInstance> {
return Promise.resolve(
{
id: 10000,
type: this.domain
}
);
}

/**
* @description Dummy method to find item in storage
*/
public find(id: number): Promise<IInstance> {
return Promise.resolve(
{
id: id,
type: this.domain
}
);
}

/**
* @description Dummy method to delete item in storage by id
*/
public delete(id: number): Promise<void> {
return Promise.resolve();
}

/**
* @description Dummy method to update item in storage by id
*/
public update(id: number, payload: IPayload): Promise<IInstance> {
return Promise.resolve(
{
id: 10000,
type: this.domain
}
);
}

}

2 changes: 1 addition & 1 deletion src/metadata-builder/MetadataBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class MetadataBuilder {
/**
* Builds controller metadata from a registered controller metadata args.
*/
buildControllerMetadata(classes?: Function[]) {
buildControllerMetadata(classes?: Function[]): ControllerMetadata[] {
return this.createControllers(classes);
}

Expand Down
Loading