typescript
43 lines · 7 steps
Validating paginated queries in NestJS
A NestJS controller parses, defaults, and validates query params before delegating a paginated product lookup.
Explained by
highlit
1import {
2 Controller,
3 Get,
4 Query,
5 DefaultValuePipe,
6 ParseIntPipe,
7 BadRequestException,
8} from '@nestjs/common';
9import { ProductsService } from './products.service';
10
11@Controller('products')
12export class ProductsController {
13 constructor(private readonly productsService: ProductsService) {}
14
15 @Get()
16 async findAll(
17 @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,
18 @Query('limit', new DefaultValuePipe(20), ParseIntPipe) limit: number,
19 @Query('minPrice', new DefaultValuePipe(0), ParseIntPipe) minPrice: number,
20 @Query('maxPrice', new DefaultValuePipe(Number.MAX_SAFE_INTEGER), ParseIntPipe)
21 maxPrice: number,
22 ) {
23 if (page < 1) {
24 throw new BadRequestException('page must be greater than or equal to 1');
25 }
26
27 const boundedLimit = Math.min(Math.max(limit, 1), 100);
28
29 if (minPrice < 0) {
30 throw new BadRequestException('minPrice cannot be negative');
31 }
32
33 if (maxPrice < minPrice) {
34 throw new BadRequestException('maxPrice must be greater than or equal to minPrice');
35 }
36
37 return this.productsService.findAll({
38 skip: (page - 1) * boundedLimit,
39 take: boundedLimit,
40 priceRange: { min: minPrice, max: maxPrice },
41 });
42 }
43}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Pipes give you type-safe query params with sensible defaults before your handler body ever runs.
- 2Clamp untrusted numeric inputs to safe bounds instead of trusting client-supplied values directly.
- 3Convert page/limit into skip/take at the boundary so the service layer stays framework-agnostic.
Related explainers
python
import pytest from fastapi.testclient import TestClient from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker
Testing FastAPI routes with dependency overrides
testing
fixtures
dependency-injection
Intermediate
7 steps
typescript
import { AsyncLocalStorage } from 'node:async_hooks'; import { randomUUID } from 'node:crypto'; import { Injectable, NestMiddleware } from '@nestjs/common'; import { Request, Response, NextFunction } from 'express';
Request correlation IDs in NestJS with AsyncLocalStorage
async-local-storage
middleware
logging
Advanced
8 steps
typescript
import { Controller, Post, UploadedFile,
Handling avatar uploads in NestJS
file-upload
validation
interceptors
Intermediate
7 steps
java
public record ServerConfig( String host, int port, Duration timeout,
Loading typed config into a Java record
records
factory-methods
parsing
Intermediate
7 steps
java
public final class IbanValidator { private static final Map<String, Integer> COUNTRY_LENGTHS = Map.ofEntries( Map.entry("DE", 22), Map.entry("FR", 27), Map.entry("GB", 22),
Validating IBANs with the mod-97 checksum
validation
checksum
modular-arithmetic
Intermediate
8 steps
typescript
@Component({ selector: 'app-article-editor', templateUrl: './article-editor.component.html', })
Autosaving a form with RxJS in Angular
reactive-forms
rxjs-operators
debouncing
Advanced
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/validating-paginated-queries-in-nestjs-explained-typescript-84a4/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.