Skip to content

FAC-97 perf: add pagination to curriculum list endpoints #209

@y4nder

Description

@y4nder

title: 'Curriculum list endpoints return unbounded result sets (no pagination)'
severity: Major
category: Performance
labels: [performance, enhancement]

Summary

All three curriculum list endpoints (/departments, /programs, /courses) return the full result set with no pagination, unlike other list endpoints in the codebase that implement page/limit.

Location

  • File(s): src/modules/curriculum/services/curriculum.service.ts (lines 28–67, 69–123, 125–225)
  • Function/Class: CurriculumService.ListDepartments, ListPrograms, ListCourses
  • DTOs: src/modules/curriculum/dto/requests/*.dto.ts

Problem

The curriculum service returns all matching records with no upper bound. There are no page or limit query parameters in any of the three request DTOs.

// curriculum.service.ts — ListDepartments (same pattern in ListPrograms, ListCourses)
const departments = await this.em.find(Department, filter, {
  orderBy: { name: QueryOrder.ASC_NULLS_LAST },
});
// No limit or offset — returns ALL matching rows

return departments.map((d) => DepartmentItemResponseDto.Map(d));

Three other modules in the codebase implement pagination on their list endpoints:

  • admin/dto/requests/list-users-query.dto.tspage = 1, limit = 20
  • faculty/dto/requests/list-faculty-query.dto.tspage = 1, limit = 20
  • dimensions/dto/requests/list-dimensions-query.dto.tspage = 1, limit = 20

Impact

  • Memory pressure: A semester with hundreds of departments/programs or thousands of courses would serialize the entire set into memory and transmit it in a single response.
  • Response latency: No cap on payload size means unpredictable response times under real data volumes.
  • Inconsistency: Consumers already paginate other list endpoints — curriculum endpoints break that contract.

Suggested Fix

Add optional page and limit query params to each request DTO and apply them via MikroORM's limit/offset options, following the established codebase pattern.

// In each request DTO (e.g., ListDepartmentsQueryDto)
@ApiPropertyOptional({ description: 'Page number', default: 1 })
@Type(() => Number)
@IsInt()
@Min(1)
@IsOptional()
page?: number = 1;

@ApiPropertyOptional({ description: 'Items per page', default: 20 })
@Type(() => Number)
@IsInt()
@Min(1)
@Max(100)
@IsOptional()
limit?: number = 20;
// In service methods
const departments = await this.em.find(Department, filter, {
  orderBy: { name: QueryOrder.ASC_NULLS_LAST },
  limit: query.limit,
  offset: (query.page - 1) * query.limit,
});

Acceptance Criteria

  • ListDepartmentsQueryDto, ListProgramsQueryDto, and ListCoursesQueryDto accept optional page and limit parameters with sensible defaults
  • Service methods pass limit and offset to em.find()
  • Existing tests updated; new tests cover pagination edge cases (first page, last page, out-of-range page)
  • Existing tests still pass

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions