DEV Community

Cover image for How to validate fetched response
Borzoo Moazami
Borzoo Moazami

Posted on

How to validate fetched response

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
})
Enter fullscreen mode Exit fullscreen mode

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(),
        })
    })
})
Enter fullscreen mode Exit fullscreen mode

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,
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
      }
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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)