@loopback/docs
Version:
Documentation files rendered at [https://loopback.io](https://loopback.io)
141 lines (113 loc) • 4.76 kB
Markdown
---
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
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/)