Simple example this time.
Let's say we have a NestJS application running a web API. We will have probably a controller that will accept a request that will bring a body. For example:
type CreateResourceContract = {id: number, name: string, description: string}
type CreateResourceResponse = {...}
@Controller('resource')
export class ResourceController {
constructor(
private readonly createUseCase: CreateResourceUseCase,
) {}
//...
@Post()
async createResource(
@Body() params: CreateResourceContract,
): Promise<CreateResourceResponse> {
return this.createUseCase.run(params);
}
}
Here we have a method called createResource that is mapped to POST /resource. We're defining a contract to interact with that method, that should have three fields (id, name and description). The method is quite simple, it just calls an injected service using the body that the request brings.
What happens here?
The contract is defined through a type. This type disappears at runtime, so, nothing checks if the contract has been fulfilled or not when a request to POST /resource is performed. This type here is doing, basically, nothing but a static check .
We could send an empty body, a body without the parameters defined at. CreateResourceContract or with extra ones and nothing would happen (until createUseCase tried to access one of the fields on the contract and finds an undefined instead).
How to solve this?
NestJS provides serialization and validation tools. Using them as guard clauses would imply that the contracts are being fulfilled and then, the type system would work.
Again, you would be tricked by the type system until you notice the failure at runtime