Skip to content

docs: add documentation for controller inheritance #578

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 17, 2020
Merged
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