Introduction to Zod and Its Role in Next.js:
Modern web applications demand precise data validation to ensure a seamless user experience and robust backend processes. This is where Zod, a powerful TypeScript-first schema declaration and validation library, comes into play. In this article, we’ll explore how to integrate Zod into a Next.js project, ensuring efficient validation for both client and server.
Overview of Zod and Its Benefits
Zod is a lightweight and intuitive library that allows developers to define schemas for data validation. Unlike traditional libraries like Yup or Joi, Zod is fully compatible with TypeScript, enabling strong type safety alongside runtime validation.
Here are some benefits of using Zod:
- Seamless TypeScript integration.
- Concise and readable syntax.
- Support for nested schemas and advanced validations.
- Auto-generated TypeScript types from schemas.
Comparison with Other Validation Libraries
While other libraries like Yup or Joi are popular, they often require additional effort to integrate with TypeScript. Zod, on the other hand, is designed with TypeScript in mind, making it a go-to choice for projects that prioritize type safety.
Installing Zod
Steps to Integrate Zod
To add Zod to your project, run the following command:
npm i zod
Testing the Installation
To confirm that Zod is installed, you can create a simple user schema
export const createAccountSchema=z.object({
name:z
.string({ message:"name is required" })
.min(6, "name must be more then 6 characters")
.max(50, "name must be leas then 50 characters"),
email:z
.string({ message:"Email is required" })
.min(16, "Email is required ")
.email("Invalid email"),
password:z
.string({ message:"Password is required" })
.min(1, "Password is required")
.min(8, "Password must be more than 8 characters")
.max(32, "Password must be less than 32 characters")
.transform(hashPassword),
});
Install this package for more settings :
npm install @conform-to/react @conform-to/zod --save
Create User sign-up component using typescript
"use client";
import React, { useActionState } from "react";
import { CreateButton } from "../components/SearchButton";
import { parseWithZod } from "@conform-to/zod";
import { createAccountSchema } from "@/src/utils/ZodSchema";
import { useForm } from "@conform-to/react";
const Page = () => {
const [lastResult, action] = useActionState(addUser, undefined);
const [form, fields] = useForm({
lastResult,
onValidate({ formData }) {
return parseWithZod(formData, {
schema: createAccountSchema,})},
shouldValidate: "onBlur",
shouldRevalidate: "onInput",
});
return (
<form id={form.id} onSubmit={form.onSubmit} action={action} noValidate
className="mt-4">
<div className="flex flex-col justify-center items-center">
<input className="w-full dark:bg-dark px-8 py-3 rounded-lg font-medium bg-gray-100
border border-gray-200 placeholder-gray-500 text-sm
focus:outline-none focus:border-gray-400 focus:bg-white"
type="text" placeholder="full name" name={fields.name.name}
defaultValue={fields.name.initialValue as string}
key={fields.name.key} maxLength={40} required/>
<p className="text-red-600 text-sm">{fields.name.errors}</p>
<input className="w-full px-8 dark:bg-dark py-3 mt-2 rounded-lg font-medium bg-gray-100
border border-gray-200 placeholder-gray-500 text-sm focus:outline-none
focus:border-gray-400 focus:bg-white" type="email" placeholder="Email"
name={fields.email.name} defaultValue={fields.email.initialValue as string}
key={fields.email.key} required/>
<p className="text-red-600 text-sm">{fields.email.errors}</p>
<input className="w-full px-8 dark:bg-dark ml-2 sm:ml-0 py-3 mt-2 rounded-lg font-medium
bg-gray-100 border border-gray-200 placeholder-gray-500 text-sm
focus:outline-none focus:border-gray-400 focus:bg-white" type="password"
placeholder="Password" name={fields.password.name} key={fields.password.key} />
<p className="text-red-600 text-sm">{fields.password.errors}</p> </div>
<CreateButton />
</form>
)}
export default Page;
Create CreateButton.tsx
"use client";
import { useFormStatus } from "react-dom";
export function CreateButton() {
const status = useFormStatus();
return (
<button type="submit" className="mt-5 tracking-wide font-semibold bg-[#2b7aa6] text-white-500 w-full py-2 rounded-lg
hover:bg-[#2b9aa2] transition-all duration-300 ease-in-out flex items-center justify-center
focus:shadow-outline focus:outline-none"><span className="text-light">
{status.pending ? "Creating..." : "Create Account"}</span>
</button>
)}
Create addUser as a server function
import { redirect } from"next/navigation";
import { revalidatePath } from"next/cache";
import { connect } from"./ConnectDB";
import { parseWithZod } from"@conform-to/zod";
import { createAccountSchema } from"./ZodSchema";
export const addUser=async (prevState:unknown, formData:FormData) => {
const submission=parseWithZod(formData, {
schema:createAccountSchema,
});
if (submission.status!=="success") {
return submission.reply();
}
try {
connect();
const newUser=newUser({
name:submission.value.name,
email:submission.value.email,
password:submission.value.password,
});
await newUser.save();
console.log("user is save");
} catch (error) {
console.log(error);
}
revalidatePath("/create-account");
redirect("/login");
};
Create connect mongoDb Function:
// Importing mongoose library along with Connection type from it
importmongoose, { Connection } from"mongoose";
// Declaring a variable to store the cached database connection
let cachedConnection:Connection|null=null;
// Function to establish a connection to MongoDB
export asyncfunction connect() {
// If a cached connection exists, return it
if (cachedConnection) {
console.log("Using cached db connection");
return cachedConnection;
}
try {
// If no cached connection exists, establish a new connection to MongoDB
if (!process.env.MONGO) {
throw new Error("MONGO environment variable is not defined");
}
const cnx=await mongoose.connect(process.env.MONGO);
// Cache the connection for future use
cached Connection=cnx.connection;
// Log message indicating a new MongoDB connection is established
console.log("New mongodb connection established");
// Return the newly established connection
return cached Connection;
} catch (error) {
// If an error occurs during connection, log the error and throw it
console.log(error);
throw error;
}
}
Conclusion
Zod, combined with TypeScript and Next.js, is a game-changer for modern web development. It simplifies validation, improves type safety, and ensures your applications are robust and maintainable. Whether you're validating API requests or form inputs, Zod makes it a breeze to keep your codebase clean and error-free.