Building Extendable Generic Nestjs Controller
Tags: nestjs, django, webdev, typescript
Published: 2022-10-14 09:29:52

When I was working with the **Django rest framework**, there was a class called **ModelViewSet**, just inheriting this class and specifying your database model name, all the CRUD endpoints would be created for you automatically. I really liked this idea as it gives you consistency look to your API, less code has to be written and finally lets you focus more on the business logic.

This article will provide implementation to a generic class that when it is extended will provide similar functionality to **ModelViewSet**.

In order to do that **TypeORM** library is used, as our model library. As an example, I will do the classic Article model and will show how I will do the endpoints for it, as the model would be:

```typescript
@Entity('article')
export class ArticleEntity extends BaseEntity {
  @PrimaryGeneratedColumn({ name: 'article_id' })
  articleId: number;

  @Column()
  title: string;

  @Column()
  body: string;
}
```
The controller of this model with a GET endpoint would be normally like this:

```typescript
@Controller({ path: 'article' })
class ArticleController {
 constructor(
   @InjectRepository(ArticleEntity)
   protected readonly repository: Repository<ArticleEntity>,
  ) {}
  @Get(':id')
  async get(@Param('id', ParseIntPipe) id: number):
  Promise<ArticleEntity> { 
   return this.repository.findOne({ where: { articleId: id } });
  }
}
```
Now we have to create an abstract generic class with a GET function, I will call it **EndPointSet**, and it would look like this without implementation:

```typescript
abstract class EndPointSet<T extends BaseEntity> { 
  protected abstract readonly repository: Repository<T>;
  @Get(':id')
  async get(@Param('id', ParseIntPipe) id: number): Promise<T> {
    // IMPLEMENTATION HERE, KEEP READING ^^
  }
}
```
But now we have a problem, each different model would have a different id field name, so we can not use the **findOne** method, luckily Typeform provides the **findOneById** method, which needs only the id value, not its name, so the method implementation would be:
```typescript
return this.repository.findOneById(id);
```
And now we can inherit the **EndPointSet** not only in the **ArticleController** but any controller as follows:

```typescript
@Controller({ path: 'article' })
export class ArticleController extends EndPointSet<ArticleEntity> {
  constructor(
  @InjectRepository(ArticleEntity)
     protected readonly repository: Repository<ArticleEntity>,
  ) {super();}
}
```
The other endpoints will be similar to what we did in the Get function, so the final class at the end would be like that:

```typescript
abstract class EndPointSet<T extends BaseEntity> {
  protected abstract readonly repository: Repository<T>;
  @Get(':id')
  async get(@Param('id', ParseIntPipe) id: number): Promise<T> {
    return this.repository.findOneById(id);
  }

  @Get('list')
  async list(): Promise<T[]> {
    return this.repository.find();
  }

  @Post()
  async post(@Body() dto: DeepPartial<T>): Promise<T> {
    return this.repository.save(dto);
  }

  @Delete()
  async delete(@Param('id', ParseIntPipe) id: number): Promise<void> {
    this.repository.delete(id);
  }

  @Patch(':id')
  async update(
    @Param('id', ParseIntPipe) id: number,
    @Body() dto: QueryDeepPartialEntity<T>,
  ): Promise<void> {
    this.repository.update(id, dto);
  }
}

```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bxad29b7g1pei0jo17bj.gif)
So yeah that is it, we now made our generic controller with all the CRUD endpoints. That is it for the first part, in the next part, I will do more work on the list function, in order to do pagination, ordering, filter.. etc. So follow for more ^^.