UNPKG

@loopback/docs

Version:

Documentation files rendered at [https://loopback.io](https://loopback.io)

141 lines (113 loc) 4.76 kB
--- lang: en title: 'Validation in the Controller, Repository and Service Layer' keywords: LoopBack 4.0, LoopBack 4, Node.js, TypeScript, OpenAPI sidebar: lb4_sidebar permalink: /doc/en/lb4/Validation-controller-repo-service-layer.html --- In the case where validation rules are not static, validation cannot be specified at the model level. Hence, validation can be added in the controller layer. Take an example of a promo code in an order, it is usually a defined value that is only valid for a certain period of time. And in the CoffeeShop example, the area code of a phone number usually depends on the geolocation. ## Add validation function in the Controller method The simplest way is to apply the validation function in the controller method. For example: {% include code-caption.html content="/src/controllers/coffee-shop.controller.ts" %} ```ts // create a validatePhoneNum function and call it here if (!this.validatePhoneNum(coffeeShop.phoneNum, coffeeShop.city)) throw new Error('Area code in phone number and city do not match.'); return this.coffeeShopRepository.create(coffeeShop); ``` ## Add interceptor for validation Another way is to use [interceptors](Interceptor.md). Interceptors are reusable functions to provide aspect-oriented logic around method invocations. Interceptors have access to the invocation context, including parameter values for the method call. It can perform more specific validation, for example, calling a service to check if an address is valid. There are three types of interceptors for different scopes: global, class-level and method-level interceptors. Interceptors can be created using the [interceptor generator](https://loopback.io/doc/en/lb4/Interceptor-generator.html) `lb4 interceptor` command. In the CoffeeShop example, the `phoneNum` in the `CoffeeShop` request body will be validated for the `POST` and `PUT` calls whether the area code in the phone number matches the specified city. Since this is only applicable to the CoffeeShop endpoints, a non-global interceptor is created, i.e. specify `No` in the `Is it a global interceptor` prompt. ```sh $ lb4 interceptor ? Interceptor name: validatePhoneNum ? Is it a global interceptor? No create src/interceptors/validate-phone-num.interceptor.ts update src/interceptors/index.ts Interceptor ValidatePhoneNum was created in src/interceptors/ ``` In the newly created interceptor `ValidatePhoneNumInterceptor`, the function `intercept` is the place where the pre-invocation and post-invocation logic can be added. {% include code-caption.html content="/src/interceptors/validate-phone-num-interceptor.ts" %} ```ts async intercept( invocationCtx: InvocationContext, next: () => ValueOrPromise<InvocationResult>, ) { // Add pre-invocation logic here // ------ VALIDATE PHONE NUMBER ---------- let coffeeShop: CoffeeShop | undefined; if (invocationCtx.methodName === 'create') coffeeShop = invocationCtx.args[0]; else if (invocationCtx.methodName === 'updateById') coffeeShop = invocationCtx.args[1]; if ( coffeeShop && !this.isAreaCodeValid(coffeeShop.phoneNum, coffeeShop.city) ) { const err: ValidationError = new ValidationError( 'Area code and city do not match', ); err.statusCode = 422; throw err; } // ---------------------------------------- const result = await next(); // Add post-invocation logic here return result; } catch (err) { // Add error handling logic here throw err; } } isAreaCodeValid(phoneNum: string, city: string): Boolean { // add some dummy logic const areaCode: string = phoneNum.slice(0, 3); if ( !( city.toLowerCase() === 'toronto' && (areaCode === '416' || areaCode === '647') ) ) return false; // otherwise it always return true return true; } ``` Now that the interceptor is created, we are going to apply this to the controller with the `CoffeeShop` endpoints, `CoffeeShopController`. {% include code-caption.html content="/src/controllers/coffee-shop.controller.ts" %} ```ts // Add these imports for interceptors import {inject, intercept} from '@loopback/core'; import {ValidatePhoneNumInterceptor} from '../interceptors'; // Add this line to apply interceptor to this class @intercept(ValidatePhoneNumInterceptor.BINDING_KEY) export class CoffeeShopController { // .... } ``` ## Reference To find out more about interceptors, check out the blog posts below: - [Learning LoopBack 4 Interceptors (Part 1) - Global Interceptors](https://strongloop.com/strongblog/loopback4-interceptors-part1/) - [Learning LoopBack 4 Interceptors (Part 2) - Method Level and Class Level Interceptors](https://strongloop.com/strongblog/loopback4-interceptors-part2/)