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 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 Image from "next/image"; import { FcGoogle } from "react-icons/fc"; import Link from "next/link"; import { addUser, } from "@/src/utils/actions"; 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 ( <> <div className="flex w-full bg-white items-center justify-center dark:bg-dark py-32 p-16 lg:flex lg:flex-col-reverse md:p-10"> <div className="m-2 w-full rounded-2xl bg-gray-400 bg-cover bg-center text-white md:hidden"> <Image height={500} width={500} priority className="w-full h-full object-fill rounded-2xl" alt="image_login" src={ "https://i.ibb.co/9VFgfg0/organic-flat-blog-post-illustration.jpg" } /> </div> <div className="w-full sm:w-full md:mt-16"> {/* form action register */} <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> <div className="bg-gray-400 h-[1px] w-full mt-5 mb-5"></div> <h4 className="flex justify-center underline text-gray-500 dark:text-light"> Login with Google or Github </h4> <div className="flex-col"> </div> <div className="mt-4 text-center"> <Link href="/login" className="font-bold text-blue-600 no-underline hover:text-blue-400" > <span className="text-sm text-gray-600 dark:text-light"> Do you have an account ? <span className="text-blue-500 hover:underline"> Login</span> </span> </Link> </div> </div> </div> </div> </> ); }; export default Page;
Create CreateButton.tsx
export function CreateButton() { const status = useFormStatus(); 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:
install mongoDb and mongoose
npm install mongoose && npm install mongoose --save
create MongoDB connect.tsx
// 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.