The Same API
Part of the TypeScript and NestJS section of Coddy's Node.js journey. Lesson 24 of 24.
Section 3's clients call /api/v1/tasks and use ?q=, ?sort= and ?limit=. These are the last pieces between your Nest API and that contract.
app.setGlobalPrefix('api/v1') in main.ts moves every route under the prefix, with no controller changed. The query options work like section 3's, with two differences: error codes are Nest's status names, like BAD_REQUEST, and a limit above 50 counts as 50 instead of getting 400.
const byPrice = [...products].sort((a, b) => a.price - b.price);Sort a copy: sort reorders the array it is called on, and the service's array would stay reordered for every later request. Throw BadRequestException for a field you do not sort by; your filter gives it the error shape.
Challenge
EasyThe clients of your section 3 API call /api/v1/tasks, and they can search, sort and choose a page size. Give this Nest API the same paths and the same options.
- In
src/main.ts, insidebootstrapand beforeapp.listen, set a global prefix ofapi/v1withapp.setGlobalPrefix. Every route moves under it:GET /tasksbecomesGET /api/v1/tasks, andGET /statsbecomesGET /api/v1/stats. No controller changes for this. - In
src/tasks/tasks.controller.ts,findAllreads three more query options. Apply them after thedonefilter and before the page is cut, so thattotalandpagescount only the tasks that match. ?q=searches titles.?q=pizzakeeps only the tasks whose title containspizza, ignoring case, soOrder PIZZA for the teammatches.?sort=orders the list.?sort=titlesorts by title, A to Z, and?sort=createdAtsorts oldest first. A-in front reverses the order, so?sort=-titleis Z to A. Compare the values withlocaleCompare, as in section 3;createdAtis ISO text, which sorts in time order. Any other field, like?sort=colour, gets 400: throw aBadRequestException, and your filter turns it into the error shape. Sort a copy of the list, never the service's own array, or every later request comes back sorted.?limit=sets how many tasks fit on a page. Without?limit, the page size staysPAGE_SIZE(2 here). A limit above 50 counts as 50:?limit=500returns 50 tasks per page, not an error. This differs from section 3.page,total,pagesand?done=work as before, now with the new page size.
Try it: send GET /api/v1/tasks?sort=-title&limit=3. You get 200 and the tasks from Z to A, three to a page:
{
"items": [
{ "id": 1, "title": "Write the README", "done": true, "createdAt": "2026-01-05T09:00:00.000Z" },
{ "id": 3, "title": "Plan the sprint", "done": false, "createdAt": "2026-01-07T08:15:00.000Z" },
{ "id": 2, "title": "Fix the login bug", "done": false, "createdAt": "2026-01-06T14:30:00.000Z" }
],
"page": 1,
"total": 3,
"pages": 1
}Send GET /api/v1/tasks?q=readme: only task 1, with "total": 1. Send GET /api/v1/tasks?sort=colour: 400 with { "error": { "code": "BAD_REQUEST", "message": "Cannot sort by colour" } }. Your message may be worded differently; the code is what counts. Send the old path, GET /tasks: 404 with { "error": { "code": "NOT_FOUND", "message": "Cannot GET /tasks" } }. POST and DELETE under /api/v1/tasks still need the header x-api-key: tasks-key-123.
Try it yourself
import { Body, Controller, Delete, Get, HttpCode, Inject, Param, ParseIntPipe, Post, Query, UseGuards } from '@nestjs/common';
import { ApiKeyGuard } from '../common/api-key.guard';
import { CreateTaskDto } from './dto/create-task.dto';
import { Task, TasksService } from './tasks.service';
@Controller('tasks')
export class TasksController {
constructor(
private readonly tasksService: TasksService,
@Inject('PAGE_SIZE') private readonly pageSize: number,
) {}
@Get()
findAll(@Query('done') done?: string, @Query('page') page = '1') {
const tasks = this.tasksService.findAll(done);
const current = Number(page);
const start = (current - 1) * this.pageSize;
return {
items: tasks.slice(start, start + this.pageSize),
page: current,
total: tasks.length,
pages: Math.ceil(tasks.length / this.pageSize),
};
}
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number): Task {
return this.tasksService.findOne(id);
}
@Post()
@UseGuards(ApiKeyGuard)
create(@Body() dto: CreateTaskDto): Task {
return this.tasksService.create(dto);
}
@Delete(':id')
@UseGuards(ApiKeyGuard)
@HttpCode(204)
remove(@Param('id', ParseIntPipe) id: number): void {
this.tasksService.remove(id);
}
}
All lessons in TypeScript and NestJS
Practice on your own: Express playground