Type-safe and Reliable With Zod
Typescript | October 02, 2024
Introducing a method to make TypeScript code more reliable.
In this post, we explore how to use Zod to apply type safety to API responses.Why Do We Use TypeScript?
The purpose of using TypeScript is to write safer code and prevent unexpected errors. We need to be able to trust the type definitions we create. Otherwise, type definitions might cause bugs in our code, significantly impacting productivity.
To address this issue, I propose a simple yet effective solution: using Zod to manage API responses in a type-safe manner.
Why Should We Validate API Responses?
When developing with TypeScript, it's common to define types for API responses. However, if the actual data returned by the server doesn't match the types we defined, unexpected errors can occur on the frontend.
While it's possible to handle errors globally or with dedicated error-handling logic on the frontend, it's still necessary to have a more structured and reliable method for validating API responses and handling errors more easily.
In this post, I will introduce a method to validate API response types using Zod and handle errors more effectively.
Type Validation with Zod
The following code demonstrates the safeFactory function, which validates the API response types. This function ensures that the API response matches the expected type, and throws an error if it doesn't.
import { z, ZodType } from "zod";
import { del, get, patch, post, put } from "../instance";
type Method = typeof get | typeof post | typeof put | typeof patch | typeof del;
const safeFactory =
<A extends Parameters<Method>>(method: (...args: A) => ReturnType<Method>) =>
<Z extends ZodType>(zodSchema: Z) =>
async (...args: A): Promise<z.infer<Z>> => {
const response = await method(...args);
const parsed = zodSchema.safeParse(response);
if (parsed.error) throw new Error("API_TYPE_NOT_MATCH");
return parsed.data;
};
export const safeGet = safeFactory(get);
export const safePost = safeFactory(post);
export const safePut = safeFactory(put);
export const safePatch = safeFactory(patch);
export const safeDel = safeFactory(del);
safeFactory takes an API request method and a Zod schema, and validates whether the response matches the schema. This simple process ensures that we can trust the data coming from the server by confirming its type at runtime.
How to Use It?
Here’s an example of using safeFactory in practice:
const UserSchema = z.object({
userId: z.number(),
userName: z.string(),
image: z.string().url(),
tags: z.array(z.string()),
});
type User = z.infer<typeof UserSchema>;
const user = await safeGet(UserSchema)("/user");
In this case, we write the API request similarly to how we would with axios.get, but now we also safely validate the response type. This approach significantly enhances the trustworthiness of API responses in our code.
Conclusion
One of the key challenges in frontend development is data validation. It's essential to verify that the type definitions we create are trustworthy and that we can prevent unexpected errors early on. By using tools like Zod to validate API response types, we can improve the overall safety and reliability of our code.
It’s a great time to rethink type safety and consider improving your code to make it even more reliable, don’t you think?