Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

NestJS  Module 5 – Pipes, validation and DTOs

Writing custom pipes in NestJS

Write NestJS pipes that convert and check one argument with PipeTransform and ArgumentMetadata, and decide what belongs in a pipe, a guard or an interceptor.

  • Intermediate
  • 20 minutes
  • Examples run with Node.js 24.21.0, NestJS 12.1.2, TypeScript 6.0.3 and quickjs 0.32.0
  • By MySmartCoPilot
Free core, Pro depth: 8 of 20 minutes free
Free: A pipe is a class with one method, transform(value, metadata) · A pipe changes one argument, and the other enhancers do the rest. With a pass: 2 in-depth parts and 3 interview answers, about 12 minutes.

What you will learn

  • Write synchronous and asynchronous PipeTransform classes that read ArgumentMetadata
  • Choose what belongs in a pipe, a guard, an interceptor, middleware or a service
  • Load an entity by id in a pipe that injects a repository, and turn a miss into a 404
  • Fix the compiler errors that Nest 12's pipe types raise in hand-written pipes, and keep internal details out of pipe errors

Before you start

What you’ll learn · Before you start

What you will learn

  • Write synchronous and asynchronous PipeTransform classes that read ArgumentMetadata
  • Choose what belongs in a pipe, a guard, an interceptor, middleware or a service
  • Load an entity by id in a pipe that injects a repository, and turn a miss into a 404
  • Fix the compiler errors that Nest 12's pipe types raise in hand-written pipes, and keep internal details out of pipe errors

Before you start

On this page

A wallet top-up sends its amount as the text "1234.50", and the handler needs 123450 paise. Converting it inside every handler that takes money repeats the same code and invites the classic slip: in JavaScript 4.35 * 100 is 434.99999999999994, so Math.floor() loses a paisa.

This lesson writes the conversion once, as a custom pipe that runs before the handler, then decides where other work belongs: in a pipe, a A class whose canActivate() method decides, before any pipe runs, whether a request may reach its handler; returning false or throwing refuses the request., an interceptor, middleware or a service. You will write a pipe class, read what Nest tells it about the argument, and turn bad input into a clear 400.

A pipe is a class with one method, transform(value, metadata)

A custom pipe implements PipeTransform: The interface every Nest pipe implements: one method, transform(value, metadata), whose return value replaces the handler's argument and whose thrown exception becomes the response. It may return a promise.: one method, transform(value, metadata), whose return value replaces the argument. ParsePaisePipe accepts rupees as text with at most two decimals and returns whole paise, computed from the digits, so no binary fraction is ever multiplied:

paise-pipe/src/parse-paise.pipe.ts TypeScript · paise-pipe/src/parse-paise.pipe.ts
import { type ArgumentMetadata, BadRequestException, Injectable, type PipeTransform } from '@nestjs/common';

// Rupees as text with at most two decimals, "1234.50" or "99": no sign, no digit grouping, no exponent.
const RUPEES = /^(\d{1,7})(?:\.(\d{1,2}))?$/;

// Turns an amount in rupees, sent as text, into a whole number of paise, or answers 400.
@Injectable()
export class ParsePaisePipe implements PipeTransform<unknown, number> {
  transform(value: unknown, metadata: ArgumentMetadata): number {
    console.log(`  transform(${JSON.stringify(value)}, { type: '${metadata.type}', data: '${metadata.data}', metatype: ${metadata.metatype?.name} })`);
    const field = metadata.data ?? 'amount';
    if (value === undefined) throw new BadRequestException(`${field} is missing`);
    if (typeof value !== 'string') throw new BadRequestException(`${field} must be text such as "1234.50", not a ${typeof value}`);
    const match = RUPEES.exec(value);
    if (!match) throw new BadRequestException(`${field} must be rupees with at most two decimals, such as "1234.50"`);
    const [, rupees, fraction = ''] = match;
    return Number(rupees) * 100 + Number(fraction.padEnd(2, '0'));
  }
}

Step through one amount, then run the endpoint with seven bodies:

Interactive · stage by stage One amount through ParsePaisePipe Step through POST /wallet/top-ups with the amount "1234.50". The side branch is the amount "12.345", which has three decimals.
One amount through ParsePaisePipe, step 8 of 8. Showing The JSON body ({"amount":"12.345"}), @Body('amount') (the text '12.345'), ParsePaisePipe, At most two decimals? (three decimals: no match), The return value, topUp(amountPaise), The answer (400 Bad Request). In focus: refused, The answer.no match: 400The JSON body{"amount":"12.345"}@Body('amount')the text '12.345'ParsePaisePipetransform(value, metadata)At most two decimals?three decimals: no matchThe return value123450, whole paisetopUp(amountPaise)your route handlerThe answer400 Bad Request

The drawing shows the whole scene; its 8 steps are listed below.

Every step as text
  1. The body. A client posts {"amount":"1234.50"}. The amount is text on purpose: a JSON number would arrive as a binary fraction, and 4.35 * 100 is 434.99999999999994 in JavaScript.
  2. The argument. @Body('amount', ParsePaisePipe) takes one property of the parsed body, the string '1234.50', and hands it to the pipe before the handler can run.
  3. The call. Nest calls transform('1234.50', metadata), and metadata says type 'body', data 'amount' and metatype Number: where the value came from, its name and the type the handler wants.
  4. The check. The pipe matches the text against its pattern, at most seven digits of rupees and at most two decimals, and reads the rupees and the paise as two whole numbers.
  5. The result. 1234 rupees times 100, plus 50 paise, is 123450. The return value replaces the argument, so the handler receives a whole number of paise and never the text.
  6. The handler. topUp(123450) runs, and the client gets 201 with {"creditedPaise":123450}.
  7. Three decimals. "12.345" has three decimals, so it does not match the pattern. The pipe throws BadRequestException with a message that names the field and the expected form.
  8. The 400. The handler never runs. The client gets 400: amount must be rupees with at most two decimals, such as "1234.50".

Seven top-ups through ParsePaisePipe JavaScript · paise-pipe/run.mjs
import { session } from '../session.mjs';

// Builds a wallet top-up endpoint whose amount goes through ParsePaisePipe, then sends it seven bodies.
session(`
npx tsc -p tsconfig.json
node dist/main.js
`);

Output

$ npx tsc -p tsconfig.json
$ node dist/main.js
POST /wallet/top-ups {"amount":"1234.50"}
  transform("1234.50", { type: 'body', data: 'amount', metatype: Number })
  -> 201 {"creditedPaise":123450}
POST /wallet/top-ups {"amount":"99"}
  transform("99", { type: 'body', data: 'amount', metatype: Number })
  -> 201 {"creditedPaise":9900}
POST /wallet/top-ups {"amount":"0.5"}
  transform("0.5", { type: 'body', data: 'amount', metatype: Number })
  -> 201 {"creditedPaise":50}
POST /wallet/top-ups {"amount":"12.345"}
  transform("12.345", { type: 'body', data: 'amount', metatype: Number })
  -> 400 {"message":"amount must be rupees with at most two decimals, such as \"1234.50\"","error":"Bad Request","statusCode":400}
POST /wallet/top-ups {"amount":"1,234.50"}
  transform("1,234.50", { type: 'body', data: 'amount', metatype: Number })
  -> 400 {"message":"amount must be rupees with at most two decimals, such as \"1234.50\"","error":"Bad Request","statusCode":400}
POST /wallet/top-ups {"amount":1234.5}
  transform(1234.5, { type: 'body', data: 'amount', metatype: Number })
  -> 400 {"message":"amount must be text such as \"1234.50\", not a number","error":"Bad Request","statusCode":400}
POST /wallet/top-ups {}
  transform(undefined, { type: 'body', data: 'amount', metatype: Number })
  -> 400 {"message":"amount is missing","error":"Bad Request","statusCode":400}
4.35 * 100 in JavaScript is 434.99999999999994, and Math.floor() of it is 434

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node run.mjs

The second argument is ArgumentMetadata: What Nest passes to a pipe about the argument: type (body, query, param or custom), data (the name given to the decorator, if any), metatype (the declared type of the parameter) and, in Nest 12, schema., and the run printed it for every request: type: 'body' because the value came from @Body(), data: 'amount' because the decorator named one property, and metatype: Number, the handler’s declared type, which is what the pipe must produce. The pipe used data to name the field in its messages. "12.345" and "1,234.50" got 400 with an actionable message, the number 1234.5 got 400 because a JSON number cannot promise two exact decimals, and a missing amount got amount is missing. An exception thrown in a pipe is a response: the handler never ran for those four.

transform() may also return a promise, and Nest waits for it before the next pipe or the handler runs, which is how a pipe can look something up in a database. Bind a pipe by class, @Body('amount', ParsePaisePipe), and Nest builds it and can inject its dependencies; bind an instance, as with new ParseEnumPipe(OrderStatus), when the pipe takes options in its constructor.

Checkpoint

Check: The PipeTransform contract

  1. Question 1 of 2 For @Body('amount', ParsePaisePipe) amountPaise: number, the run printed metatype: Number. What does it describe?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The type the handler declared for its parameter, which the pipe must produce

    type says where the value came from (body), data the property the decorator named (amount), and metatype the declared type of the parameter.

  2. Question 2 of 2 ParsePaisePipe throws BadRequestException for "12.345". What does the client get, and does the handler run?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The handler does not run, and the client gets 400 with the pipe's message

    The run answered 400 with amount must be rupees with at most two decimals, such as "1234.50".

A pipe changes one argument, and the other enhancers do the rest

A pipe sees one argument at a time, after Nest has extracted it, and nothing about the user, the route’s metadata or the handler’s result. That decides what belongs in it. Nest runs a request through middleware, then guards, then interceptors, then pipes and the handler, and the response back through the interceptors:

Work Where it belongs Why there
Turn "1234.50" into 123450, check a UUID a pipe it receives the one value, already extracted
Decide whether this caller may use the route a guard it runs before pipes and knows the handler through its execution context
Time the call, map the result, cache a response an interceptor it wraps the handler and sees its result
Read the raw request before routing, for a request id or a body limit middleware it runs first, before Nest knows the handler
Check stock, ownership, a daily limit a service the rule needs data, and one place to keep it

Choose for your own case in the table below; it marks what each answer rules out:

Interactive · answer and compare Pipe, guard, interceptor, middleware or service? Answer two questions about the code you are about to write. The table marks the places your answers rule out or warn about.
What does the code act on?
Does the decision need data from a database or another service?

Answer the questions above: the table then marks what fits your case and says why the rest does not.

Five places for request work in NestJS 12, against four things that decide where it goes
Place Sees one parsed argument Can refuse the request Sees the handler's result Knows which handler runs
A pipe Receives: one argument, extracted Good gets the value after extraction, with its ArgumentMetadata Good a thrown exception becomes the answer; the handler never runs Poor runs before the handler only Poor knows the argument's type and name, not the route or the user
A guard Receives: the execution context Poor runs before pipes, on the raw request Good returns false for 403, or throws its own exception Poor runs before the handler only Good reads the handler and its metadata from the execution context
An interceptor Receives: the context and the result Poor its first part runs before pipes, on the raw request Mixed can throw, or answer without calling the handler Good wraps the handler and maps, times or caches its result Good reads the handler from the execution context
Middleware Receives: the raw request and response Poor runs before routing has chosen a handler Good can end the response itself Mixed only by listening on the response Poor does not know which handler will run
A service Receives: what the handler passes Mixed gets what the handler passes, after the pipes Mixed throws a domain error that a filter or the handler turns into a status Good computes the result Mixed knows the use case, not the HTTP route
How the answers decide
  • “What does the code act on?” One argument's form: rules out A guard, An interceptor and Middleware. Only a pipe receives one extracted argument with its metadata; guards, interceptors and middleware see the raw request before pipes run.
  • “What does the code act on?” Who calls, and what they may do: rules out A pipe and Middleware and adds a caveat to An interceptor. A guard knows the handler and runs before any pipe or interceptor; a pipe cannot see the user, and middleware does not know the route.
  • “What does the code act on?” The handler's result or timing: rules out A pipe and A guard. Only an interceptor wraps the handler and receives what it returns.
  • “What does the code act on?” The raw request, before routing: rules out A pipe, A guard and An interceptor. Middleware runs first, before Nest has chosen a handler, which suits request ids, body limits and CORS.
  • “Does the decision need data from a database or another service?” Yes and “What does the code act on?” One argument's form: adds a caveat to A pipe. A pipe may load a record by id, but a rule about stock, ownership or limits belongs in the service that owns the data.
  • “Does the decision need data from a database or another service?” Yes and “What does the code act on?” Who calls, and what they may do: adds a caveat to A guard. A guard that needs a record must load it itself, because guards run before the pipe that would load it; ownership checks often fit the service better.

Key idea

A pipe converts and checks one argument and refuses bad input with a 400 before the handler runs; access belongs to guards, work around the handler to interceptors, and rules that need data to services.

Three more matters have trade-offs of their own. A pipe bound by class can inject a provider, such as a repository that turns a kitchen id into the kitchen itself. Nest 12 changed the types of the built-in pipes, which hand-written pipes that call them must follow. And whatever a pipe throws reaches the client as its answer, so its messages need the care of any response.

Checkpoint

Check: Pipe, guard or interceptor

  1. Question 1 of 2 Only the kitchen's own staff may change its menu. Where does that check belong?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: In a guard, which knows the caller and the handler and runs before any pipe

    If the decision needs the kitchen record, the guard loads it itself, or the service checks ownership once the handler passes the record on.

  2. Question 2 of 2 What does a pipe receive?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: One argument of the handler, already extracted, with its ArgumentMetadata

    That is why a pipe fits conversion and checks of a single value, and nothing that needs the caller or the result.

Pipes that load data: injection, async lookups and 404s, with a wiring error reproduced Pro: opens with a Pro, Premium or Ultimate pass

A pipe can turn an id into the record it names, so a handler receives a kitchen instead of a number. The run in this part builds such a pipe, counts how often Nest creates it, and reproduces the start-up error of a module that cannot give it its repository.

In this part:

  • A pipe that injects a repository turns a kitchen id into a kitchen
  • A global ValidationPipe checks the raw id against the type the pipe will produce, a trap reproduced
  • Bound by class, a pipe is built in the module of its controller, a wiring error reproduced
  • Guards run before the pipe, so access checks on the loaded record go in the service

2 runnable examples (JavaScript, TypeScript) · about 5 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Upgrading and hardening hand-written pipes, with the Nest 12 type errors and an error leak reproduced Pro: opens with a Pro, Premium or Ultimate pass

Hand-written pipes meet two problems that the examples so far avoided: Nest 12 changed the types of the built-in pipes they call, and a pipe that repeats an internal error tells clients too much. Both are reproduced in this part, with their fixes.

In this part:

  • Code that calls a built-in pipe can stop compiling on Nest 12
  • An error from a pipe is an answer to a client: keep internals out of it, a leak reproduced
  • A checklist for every custom pipe

3 runnable examples (JavaScript, TypeScript) · about 6 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Practise: build a money pipe in two steps

The lab builds the two halves of a production ParsePaisePipe: the function that reads an amount, then the chain that runs a parameter’s pipes the way Nest does. The sample tests of both steps run in your browser.

Rupee amounts with Indian digit grouping

The first step extends the lesson’s pipe to the amounts people type in India: the rupee sign, and digits grouped in lakhs and thousands as in ₹1,23,456, besides the plain 1234.5.

Code lab · 2 steps

Build a money pipe

Parse rupee amounts into whole paise, then run a parameter's pipes in order, as Nest does, with a default before the parse.

  1. Parse rupee amounts into paise To do
  2. Run a parameter's pipes in order To do

Exercise · Medium · JavaScript

Parse rupee amounts into paise

Write parseRupeesToPaise(input), the function a production ParsePaisePipe would call. It returns the amount as a whole number of paise, or null for any input it refuses; the pipe turns null into a 400.

Accept text only, written like this:

  • an optional rupee sign ₹ at the very start, with no space after it;
  • the rupees: digits without separators (123456), or grouped with commas in thousands (123,456 and 1,234,567), or grouped the Indian way, the last three digits together and then pairs (1,23,456 and 12,34,567); at most nine digits in all;
  • optionally a point and one or two digits of paise (1234.5 is 123450 paise, 0.05 is 5).

Refuse everything else, and in particular what a reader could take two ways: 12,34 (is the comma a decimal point?), three digits after the point (12.345), groups that fit neither system (1,2345, 1,234,56), signs, exponents, spaces, an empty string and any value that is not a string. Compute the paise from the digits, never by multiplying a decimal number by 100.

Starter code · rupees.mjs

/**
 * Parses a rupee amount typed by a person into whole paise: "1234.5", "₹1,234.50" and "1,23,456" are accepted;
 * returns null for anything it refuses, so a pipe can answer 400.
 */
export function parseRupeesToPaise(input) {
  // Handles plain amounts only, as the lesson's pipe does: add the rupee sign and both kinds of digit grouping.
  const match = /^(\d{1,9})(?:\.(\d{1,2}))?$/.exec(input);
  if (!match) return null;
  return Number(match[1]) * 100 + Number((match[2] ?? '').padEnd(2, '0'));
}
The sample tests · rupees.test.mjs
import { assert, test } from 'mysmartcopilot:test';
import { parseRupeesToPaise } from './rupees.mjs';

test('plain rupees, with and without paise', () => {
  assert.equal(parseRupeesToPaise('1234.5'), 123450);
  assert.equal(parseRupeesToPaise('1234.50'), 123450);
  assert.equal(parseRupeesToPaise('99'), 9900);
  assert.equal(parseRupeesToPaise('0.05'), 5);
});

test('the rupee sign at the start', () => {
  assert.equal(parseRupeesToPaise('₹1,234.50'), 123450);
  assert.equal(parseRupeesToPaise('₹99'), 9900);
});

test('Indian digit grouping', () => {
  assert.equal(parseRupeesToPaise('1,23,456'), 12345600);
  assert.equal(parseRupeesToPaise('12,34,567.89'), 123456789);
});

test('grouping in thousands', () => {
  assert.equal(parseRupeesToPaise('123,456'), 12345600);
  assert.equal(parseRupeesToPaise('1,234,567'), 123456700);
});

test('a comma before two digits could be a decimal point, so it is refused', () => {
  assert.equal(parseRupeesToPaise('12,34'), null);
});

test('three digits after the point are refused', () => {
  assert.equal(parseRupeesToPaise('12.345'), null);
});

test('groups that fit neither system are refused', () => {
  for (const text of ['1,2345', '1,234,56', ',123', '1,,234', '12,34,56']) assert.equal(parseRupeesToPaise(text), null, text);
});

test('signs, exponents, spaces and empty text are refused', () => {
  for (const text of ['-5', '+5', '1e3', '', '₹', ' 12', '12 ', '₹ 12', '1234.', '.5']) assert.equal(parseRupeesToPaise(text), null, JSON.stringify(text));
});

test('values that are not text are refused', () => {
  for (const value of [1234.5, undefined, null, ['12']]) assert.equal(parseRupeesToPaise(value), null, String(value));
});

test('at most nine digits of rupees', () => {
  assert.equal(parseRupeesToPaise('99,99,99,999'), 99999999900);
  assert.equal(parseRupeesToPaise('1,00,00,00,000'), null);
});
A hint

Take off the rupee sign, then split the text with one regular expression into the rupee part and an optional paise part. Check the rupee part against three patterns, plain digits, groups of three and the Indian pattern (one or two digits, any number of pairs, then three digits), and accept it if one of them matches. Remove the commas, and add the paise padded to two digits.

The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Exercise · Easy · JavaScript

Run a parameter's pipes in order

@Body('amount', new DefaultValuePipe('0'), ParsePaisePipe) gives one argument two pipes, and Nest runs them the way the lesson showed. Write runPipes(value, metadata, pipes), which does the same for a list of pipe objects, each with a method transform(value, metadata):

  • call the pipes in the order of the list; each receives what the previous one returned, and the first receives value;
  • give every pipe the same metadata object;
  • a transform() may return a promise: wait for it before the next pipe runs;
  • when a pipe throws, or its promise is rejected, stop: no later pipe runs, and the promise that runPipes returns is rejected with that same error;
  • return a promise of the last pipe's result, or of value itself when the list is empty.

Starter code · run_pipes.mjs

/**
 * Runs a parameter's pipes as Nest does: in order, each on the result of the one before, awaiting each, with the same
 * metadata, stopping at the first error. Returns a promise of the final value.
 */
export function runPipes(value, metadata, pipes) {
  let current = value;
  for (const pipe of pipes) {
    // Each pipe should receive the previous result, and a promise should be awaited before the next pipe runs.
    current = pipe.transform(value, metadata);
  }
  return Promise.resolve(current);
}
The sample tests · run_pipes.test.mjs
import { assert, test } from 'mysmartcopilot:test';
import { runPipes } from './run_pipes.mjs';

const meta = { type: 'body', data: 'amount' };

/** A pipe that appends a letter, to see the order. */
const append = (letter) => ({ transform: (v) => `${v}${letter}` });

/** The lesson's two pipes for an amount: a default for a missing value, then rupees to whole paise. */
const defaultZero = { transform: (v) => (v === undefined || v === null ? '0' : v) };
const parsePaise = {
  transform(v, m) {
    const match = /^(\d{1,7})(?:\.(\d{1,2}))?$/.exec(typeof v === 'string' ? v : '');
    if (!match) throw new Error(`${m.data} must be rupees with at most two decimals`);
    return Number(match[1]) * 100 + Number((match[2] ?? '').padEnd(2, '0'));
  },
};

test('no pipes: the value as it was', async () => {
  assert.equal(await runPipes('1234.50', meta, []), '1234.50');
});

test('each pipe receives the result of the one before, in order', async () => {
  assert.equal(await runPipes('', meta, [append('a'), append('b'), append('c')]), 'abc');
});

test('a promise is awaited before the next pipe runs', async () => {
  const slowDouble = { transform: (v) => new Promise((done) => setTimeout(() => done(v * 2), 5)) };
  const addOne = { transform: (v) => v + 1 };
  assert.equal(await runPipes(10, meta, [slowDouble, addOne]), 21);
});

test('every pipe gets the same metadata object', async () => {
  const seen = [];
  const spy = { transform: (v, m) => (seen.push(m), v) };
  await runPipes('x', meta, [spy, spy]);
  assert.equal(seen.length, 2);
  assert.ok(seen[0] === meta && seen[1] === meta, 'the metadata passed on unchanged');
});

test('the money chain of the lesson: a default, then paise', async () => {
  assert.equal(await runPipes(undefined, meta, [defaultZero, parsePaise]), 0);
  assert.equal(await runPipes('12.5', meta, [defaultZero, parsePaise]), 1250);
});

test('a pipe that throws stops the chain and rejects with its error', async () => {
  let later = false;
  const after = { transform: (v) => ((later = true), v) };
  await assert.rejects(runPipes('12.345', meta, [parsePaise, after]), 'amount must be rupees with at most two decimals', 'the error of parsePaise');
  assert.equal(later, false, 'no pipe runs after the one that threw');
});

test('a rejected promise stops the chain as well', async () => {
  let later = false;
  const failing = { transform: () => Promise.reject(new Error('kitchen 99 not found')) };
  const after = { transform: (v) => ((later = true), v) };
  await assert.rejects(runPipes(99, meta, [failing, after]), 'kitchen 99 not found', 'the rejection of the lookup');
  assert.equal(later, false, 'no pipe runs after the rejected one');
});
A hint

Make runPipes an async function and keep the current value in a variable. In a for…of loop over the pipes, replace it with await pipe.transform(current, metadata). An error thrown inside an async function already rejects its promise, so the loop needs no try at all.

The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Regex Tester Try patterns for amounts with digit grouping before you write the function.

Then the questions on the pipe contract and on where work belongs:

Practice that adapts to you

Practise this lesson

6 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 6 What happens to the value that transform() returns?

    Choose one answer.

    Show the answer: Question 1 of 6

    Answer: It replaces the argument the handler receives

    A validation pipe returns the value unchanged; a transformation pipe returns a new one.

  2. Question 2 of 6 A pipe's transform() is async and awaits a lookup. What does Nest do?

    Choose one answer.

    Show the answer: Question 2 of 6

    Answer: Waits for the promise before the next pipe or the handler runs

    A rejected promise counts like a thrown exception: an HttpException becomes its response.

  3. Question 3 of 6 A client sends "amount" as the JSON number 4.35, and code computes Math.floor(amount * 100). What happens?

    Choose one answer.

    Show the answer: Question 3 of 6

    Answer: It records 434 paise, because 4.35 * 100 is 434.99999999999994 in JavaScript

    Accept money as text or as an integer of the smallest unit, and convert it in one pipe.

  4. Question 4 of 6 Why does middleware not fit a check that depends on the route's handler?

    Choose one answer.

    Show the answer: Question 4 of 6

    Answer: Middleware runs before Nest has chosen a handler, so it does not know which one will run

    Use middleware for work on the raw request that applies by path, such as request ids or body limits.

  5. Question 5 of 6 Every response of the menu endpoints should have its prices in paise turned into rupee text. Where does it go?

    Choose one answer.

    Show the answer: Question 5 of 6

    Answer: In an interceptor, which wraps the handler and maps its result

    Keep amounts as paise everywhere inside the app, and format them at the edge.

  6. Question 6 of 6 A wallet may receive at most 10,000 rupees of top-ups a day. A teammate puts the check in ParsePaisePipe. What do you say?

    Choose one answer.

    Show the answer: Question 6 of 6

    Answer: Keep the pipe for the form of the amount and put the daily limit in the service, which owns the wallet data

    The table of the lesson marks the pipe with a caveat as soon as a decision needs data.

Interview questions

  1. Fresher to mid · Warm-up

    What is a custom pipe in NestJS, and what can its transform() method do with a handler's argument?

    A custom pipe is a class that implements PipeTransform: one method, transform(value, metadata), which Nest calls with one argument of the handler before the handler runs. What it returns replaces the argument, so a pipe can convert a value, such as the text "1234.50" into 123450 paise in the lesson's run, or check it and return it unchanged. If it throws an HTTP exception, that becomes the response and the handler never runs, as the 400 for "12.345" showed. The metadata says where the value came from (body, query, param or custom), the property name given to the decorator and the declared type of the parameter. transform() may return a promise, which Nest awaits, and a pipe bound by class is built by Nest, so it can inject providers.

  2. Mid to senior

    A teammate proposes OrderByIdPipe, which loads an order by id and also refuses orders that do not belong to the current user. Assess the design: what belongs in the pipe, what does not, and where does the rest go?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Fresher to mid

    After you move OrdersController into a new OrdersModule, the app fails to start with "Nest can't resolve dependencies of the KitchenByIdPipe (?)". The pipe worked in KitchensModule. Explain the error and fix it properly.

    Model answer, Pro. First step and follow-ups

  4. Mid to senior

    A security test found that your NestJS API returned a database host name and a private address in 400 answers during a database outage. The answers come from a custom pipe that loads records. Explain the defect, fix it, and show that the fix works.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A custom pipe implements transform(value, metadata): its return value replaces the argument, a thrown exception becomes the response, and the method may return a promise that Nest awaits.
  • ArgumentMetadata says where the value came from (type), which property the decorator named (data) and the type the handler declared (metatype), which is the type the pipe must produce.
  • Pipes convert and check one argument; guards decide access, interceptors wrap the handler, middleware sees the raw request first, and services keep the rules that need data.

Terms in this lesson

ArgumentMetadata
What Nest passes to a pipe about the argument: type (body, query, param or custom), data (the name given to the decorator, if any), metatype (the declared type of the parameter) and, in Nest 12, schema.
PipeTransform
The interface every Nest pipe implements: one method, transform(value, metadata), whose return value replaces the handler's argument and whose thrown exception becomes the response. It may return a promise.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.