Plan (high level)
- Add a small DTO for pagination parameters (transform strings to numbers and validate).
- Update
modules.controller.tsGET handlers to accept the DTO and pass it to the service. - Update
modules.service.ts(or repository) to usepage/limitto computeskipandtakeand perform the query (preferfindAndCountif you need total). - Return items plus pagination metadata (page, limit, total or hasMore).
- Add/update tests and docs.
Step-by-step instructions
- Create a pagination DTO (recommended location)
- File:
src/common/dto/pagination-query.dto.ts(orsrc/modules/dto/pagination-query.dto.tsif you prefer module-scoped). - Purpose: accept
pageandlimitfrom query, convert to numbers, and validate defaults and ranges. - Implementation notes:
- Use
class-transformer's@Type(() => Number)or Nest'sParseIntPipedownstream. - Use
class-validatordecorators:@IsOptional()@IsInt()@Min(1)forpage@Min(1),@Max(100)forlimit(choose a sensible max)
- Provide defaults (e.g.,
page = 1,limit = 20) either in the DTO or in controller defaults.
- Use
- Update the controller to accept the DTO and validate
- Open modules.controller.ts.
- Import the DTO and add
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))to the controller or method. Alternatively, use global validation pipe if your app already has it (likely already set inmain.ts). - Update the GET handler signature:
- Before:
findAll()orgetModules() - After example:
@Get()findAll(@Query() pagination: PaginationQueryDto) { return this.modulesService.findAll(pagination); }
- Before:
- If you prefer simpler pipes without DTO:
- Use
@Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number - Use
@Query('limit', new DefaultValuePipe(20), ParseIntPipe) limit: number - But the DTO approach centralizes validation.
- Use
- Update the service to accept pagination and apply skip/take
- Open modules.service.ts.
- Add parameters to the
findAllmethod:findAll(pagination: PaginationQueryDto)or(page: number, limit: number). - Compute
skip = (page - 1) * limit,take = limit. - If using TypeORM repository:
- Simple:
const [items, total] = await this.moduleRepository.findAndCount({ skip, take, order: { createdAt: 'DESC' }}); - If not using
findAndCount, you can run query builder:const items = await this.moduleRepository.createQueryBuilder('m').orderBy('m.createdAt', 'DESC').skip(skip).take(take).getMany();- To get total count:
.getCount()or.getManyAndCount().
- Simple:
- Return a shaped response:
return { items, meta: { page, limit, total, hasMore: skip + items.length < total } }- If you don't want to do
total(expensive), returnitemswith{ nextPage: page + 1, hasMore: items.length === limit }.
- Update repository (if you have a dedicated repository file)
- If entities and
src/modules/modules.repository.tsexist, move DB logic there. The service calls the repository with{ skip, take }.
- Add tests
- Update
src/modules/modules.controller.spec.ts(or create) to call theGET /modules?limit=2&page=2and mock the service to confirm it receives(page, limit)and returns expected subset. - Add service-level tests to assert correct
skip/takecalculations and repository call.
- Add documentation
- Update module README or central API docs to include
?pageand?limitexamples and default/max values.
- Edge cases & best-practices
- Enforce maximum
limit(e.g., 100) to avoid huge responses. - Use
transform: truefor DTOs so strings in query convert to numbers automatically. - Consider cursor-based pagination if the dataset is large or frequently changing.
- Return consistent ordering (always specify
orderBy) so paging is repeatable.
Example code snippets (concise)
-
DTO (using
class-validator+ transform):-
class PaginationQueryDto { @IsOptional() @Type(() => Number) @IsInt() @Min(1) page?: number = 1;
@IsOptional() @Type(() => Number) @IsInt() @Min(1) @Max(100) limit?: number = 20; }
-
-
Controller method (method-level snippet):
- @Get() findAll(@Query() pagination: PaginationQueryDto) { return this.modulesService.findAll(pagination); }
-
Service logic (method-level snippet):
- async findAll({ page = 1, limit = 20 }: PaginationQueryDto) { const skip = (page - 1) * limit; const [items, total] = await this.moduleRepository.findAndCount({ skip, take: limit, order: { createdAt: 'DESC' }}); return { items, meta: { page, limit, total, hasMore: skip + items.length < total } }; }
What to run & check
- Run unit tests:
npm test(or your repo's test command). - Verify controller validation: request
GET /modules?page=abcshould return a 400 validation error.
Wrap-up / Next step for me
- I already created a todo plan. If you want, I can implement this for the
modulesendpoints now: create the DTO, patch the controller and service, and add a unit test. Tell me to proceed and whether you prefer DTO-based or direct pipes approach.