What's the problem
We usually check what the user sends to our server when developing a form in front-end, but we might overlook if the data we received is incompatible with what we expected.
With the help of the Yup, we can make sure everything runs as we expected, and our backend service, provides the data we requested correctly with the schema we wanted.
Yup
According to the Yup documentation :
Yup is a schema builder for runtime value parsing and validation. Define a schema, transform a value to match, assert the shape of an existing value, or both. Yup schema are extremely expressive and allow modeling complex, interdependent validations, or value transformation.
So with the Yup, we can define how an object should look, change a value to match that object, and validate if an object is identical to our object or not.
Another important thing about the above definition worth mentioning is the Yup act in the runtime.
Why API Response Validation is Important?
These are some reasons to demonstrate why it is important to validate the responses we get from the server:
- Protecting against breaking changes: protect the client from the backend breaking changes
- Type safety: while Typescript helps us use correct data types before we actually run our code, runtime validation helps us to be sure the actual data matches our expectations
- Better error handling: validation helps us catch any inconsistency in data and handle situations gracefully, instead of leaving users with unexpected runtime errors
- Documentation: the schema definitions serve as living documentation in our front-end codebase
How to use YUP to validate our API Responses
We can simply define a base response schema for multiple endpoints, then extend our schema for each endpoint accordingly:
For example:
const generalResponseSchema = Yup.Object().shape({
status: Yup.string().oneOf(['success', 'error']).required(),
message: Yup.string(),
data: Yup.mixed() // Will be refined in specific endpoints
})
Create endpoint-specific schemas:
const userResponseSchema = generalResponseSchema.shape({
data: Yup.object().shape({
id: Yup.string().required(),
username: Yup.string().required(),
profile: Yup.string().shape({
firstName: Yup.string(),
lastName: Yup.string(),
})
})
})
We can make a wrapper function to validate our responses in our application:
export async function validateAPIResponse(schema, response) {
try {
const validatedResponse = schema.validate(response, {
// Skip coercion and transformation attempts
strict: true,
// Collect all errors instead of stopping at first failure
abortEarly: false,
})
return {
isValid: true,
data: validatedResponse,
errors: null,
}
} catch (error) {
return {
isValid: false,
data: null,
errors: error.errors,
}
}
}
Now let's use our validation function and our sample schema ( userResponseSchema ):
async function fetchUserData(userId) {
try {
const response = await fetch(`api/users/${userId}`);
const data = await response.json();
const validation = await validateAPIRResponse(data, userResponseSchema);
return validation.data
} catch(error) {
// Handle errors appropriately
throw error;
}
}
Organized and type-safe
One good approach we could take is to create a sample.schema.ts
and locate the schema definition there.
There is also the possibility of inferring the schema type and using it in our app, importing InferType
const userResponseSchema = Yup.object().shape({
id: Yup.string().required(),
username: Yup.string().required(),
profile: Yup.string().shape({
firstName: Yup.string(),
lastName: Yup.string(),
})
})
export type User = InferType<typeof userResponseSchema>
And this is how easy it is to check if the fetched response from our backend services is correct.
References
Validating API Response with Yup
Yup repo
Response validation with Yup
Top comments (0)