Validation Schema (Signup & Login)
Introduction
I wanted my frontend let users know the form errors when they typed the wrong input while signing up or logging in.
For this, I used Zod (opens in a new tab) library instead of
react-hook-form
which is more typescript oriented and effectively controls
the validation schema.
There are also other substitutes for Zod and you can check in here (opens in a new tab) for their comparison.
Installation & Configuration
Installation
Let's install (opens in a new tab) package.
npm i zodyarn add zodpnpm add zodbun add zod
Editing tsconfig.json
As Zod library is very typescript oriented, we need to fix the tsconfig.json
as shown in the requirements (opens in a new tab) to
maximize its usage.
{ "compilerOptions": { "strict": true }}
Making Schema File (Signup)
Creating Schema & Type
I created schema variable called signupSchema
.
As data will be in object type, I used .object()
(opens in a new tab)
and objectified them.
import { z } from "zod";export const signupSchema = z.object();export type SignupInput = z.infer<typeof signupSchema>;
firstName
& lastName
Basically I made first name and last name as required value.
Therefore, I used .nonempty()
(opens in a new tab)
for marking them as required, and showed error message
if they didn't pass the validation.
import { z } from "zod";export const signupSchema = z .object({ firstName: z.string().nonempty({ message: "First name is required" }), lastName: z.string().nonempty({ message: "Last name is required" }), email: z .string({ required_error: "Email is required", }) .email({ message: "Invalid email address" }), phoneNumber: z .string() .regex(/^[0-9]+$/, { message: "Invalid phone number form" }), password: z .string({ required_error: "Password is required" }) .min(8, { message: "Should not be less than 8 characters" }) .max(16, { message: "Should not be no more than 16 characters" }), confirmPassword: z .string() .nonempty({ message: "You need to confirm password" }), role: z.string().min(1, { message: "Please choose your role" }), addressFirst: z.string().optional(), addressSecond: z.string().optional(), city: z.string().optional(), country: z.string().optional(), zipCode: z.string().optional(), }) .refine((data) => data.password === data.confirmPassword, { path: ["confirmPassword"], message: "Password do not match", });export type SignupInput = z.infer<typeof signupSchema>;
email
With using .email()
(opens in a new tab) within .string()
,
I could easily check whether email address is in valid format or not.
import { z } from "zod";export const signupSchema = z .object({ firstName: z.string().nonempty({ message: "First name is required" }), lastName: z.string().nonempty({ message: "Last name is required" }), email: z .string({ required_error: "Email is required", }) .email({ message: "Invalid email address" }), phoneNumber: z .string() .regex(/^[0-9]+$/, { message: "Invalid phone number form" }), password: z .string({ required_error: "Password is required" }) .min(8, { message: "Should not be less than 8 characters" }) .max(16, { message: "Should not be no more than 16 characters" }), confirmPassword: z .string() .nonempty({ message: "You need to confirm password" }), role: z.string().min(1, { message: "Please choose your role" }), addressFirst: z.string().optional(), addressSecond: z.string().optional(), city: z.string().optional(), country: z.string().optional(), zipCode: z.string().optional(), }) .refine((data) => data.password === data.confirmPassword, { path: ["confirmPassword"], message: "Password do not match", });export type SignupInput = z.infer<typeof signupSchema>;
phoneNumber
At first I thought phoneNumber
should be a number but ended up saving it
as a string type for unifying types of signup inputs.
So I used .regex()
to check whether it contains other strings (including space) other than numbers.
import { z } from "zod";export const signupSchema = z .object({ firstName: z.string().nonempty({ message: "First name is required" }), lastName: z.string().nonempty({ message: "Last name is required" }), email: z .string({ required_error: "Email is required", }) .email({ message: "Invalid email address" }), phoneNumber: z .string() .regex(/^[0-9]+$/, { message: "Invalid phone number form" }), password: z .string({ required_error: "Password is required" }) .min(8, { message: "Should not be less than 8 characters" }) .max(16, { message: "Should not be no more than 16 characters" }), confirmPassword: z .string() .nonempty({ message: "You need to confirm password" }), role: z.string().min(1, { message: "Please choose your role" }), addressFirst: z.string().optional(), addressSecond: z.string().optional(), city: z.string().optional(), country: z.string().optional(), zipCode: z.string().optional(), }) .refine((data) => data.password === data.confirmPassword, { path: ["confirmPassword"], message: "Password do not match", });export type SignupInput = z.infer<typeof signupSchema>;
password
I used .min()
and .max()
to set minimum and maximum characters of password, respectively.
import { z } from "zod";export const signupSchema = z .object({ firstName: z.string().nonempty({ message: "First name is required" }), lastName: z.string().nonempty({ message: "Last name is required" }), email: z .string({ required_error: "Email is required", }) .email({ message: "Invalid email address" }), phoneNumber: z .string() .regex(/^[0-9]+$/, { message: "Invalid phone number form" }), password: z .string({ required_error: "Password is required" }) .min(8, { message: "Should not be less than 8 characters" }) .max(16, { message: "Should not be no more than 16 characters" }), confirmPassword: z .string() .nonempty({ message: "You need to confirm password" }), role: z.string().min(1, { message: "Please choose your role" }), addressFirst: z.string().optional(), addressSecond: z.string().optional(), city: z.string().optional(), country: z.string().optional(), zipCode: z.string().optional(), }) .refine((data) => data.password === data.confirmPassword, { path: ["confirmPassword"], message: "Password do not match", });export type SignupInput = z.infer<typeof signupSchema>;
confirmPassword
With using .refine()
(opens in a new tab), I checked whether
confirmPassword
is as same as password
, and if not I made it threw an error message
.
import { z } from "zod";export const signupSchema = z .object({ firstName: z.string().nonempty({ message: "First name is required" }), lastName: z.string().nonempty({ message: "Last name is required" }), email: z .string({ required_error: "Email is required", }) .email({ message: "Invalid email address" }), phoneNumber: z .string() .regex(/^[0-9]+$/, { message: "Invalid phone number form" }), password: z .string({ required_error: "Password is required" }) .min(8, { message: "Should not be less than 8 characters" }) .max(16, { message: "Should not be no more than 16 characters" }), confirmPassword: z .string() .nonempty({ message: "You need to confirm password" }), role: z.string().min(1, { message: "Please choose your role" }), addressFirst: z.string().optional(), addressSecond: z.string().optional(), city: z.string().optional(), country: z.string().optional(), zipCode: z.string().optional(), }) .refine((data) => data.password === data.confirmPassword, { path: ["confirmPassword"], message: "Password do not match", });export type SignupInput = z.infer<typeof signupSchema>;
role
As I let users to choose their role
in their frontend with using <select>
and <option>
tags,
I set .min()
to be 1 so that it could be the required value (Default value is empty string).
import { z } from "zod";export const signupSchema = z .object({ firstName: z.string().nonempty({ message: "First name is required" }), lastName: z.string().nonempty({ message: "Last name is required" }), email: z .string({ required_error: "Email is required", }) .email({ message: "Invalid email address" }), phoneNumber: z .string() .regex(/^[0-9]+$/, { message: "Invalid phone number form" }), password: z .string({ required_error: "Password is required" }) .min(8, { message: "Should not be less than 8 characters" }) .max(16, { message: "Should not be no more than 16 characters" }), confirmPassword: z .string() .nonempty({ message: "You need to confirm password" }), role: z.string().min(1, { message: "Please choose your role" }), addressFirst: z.string().optional(), addressSecond: z.string().optional(), city: z.string().optional(), country: z.string().optional(), zipCode: z.string().optional(), }) .refine((data) => data.password === data.confirmPassword, { path: ["confirmPassword"], message: "Password do not match", });export type SignupInput = z.infer<typeof signupSchema>;
Fields For Address
I set all fields for addresss as optional with using .optional()
(opens in a new tab).
import { z } from "zod";export const signupSchema = z .object({ firstName: z.string().nonempty({ message: "First name is required" }), lastName: z.string().nonempty({ message: "Last name is required" }), email: z .string({ required_error: "Email is required", }) .email({ message: "Invalid email address" }), phoneNumber: z .string() .regex(/^[0-9]+$/, { message: "Invalid phone number form" }), password: z .string({ required_error: "Password is required" }) .min(8, { message: "Should not be less than 8 characters" }) .max(16, { message: "Should not be no more than 16 characters" }), confirmPassword: z .string() .nonempty({ message: "You need to confirm password" }), role: z.string().min(1, { message: "Please choose your role" }), addressFirst: z.string().optional(), addressSecond: z.string().optional(), city: z.string().optional(), country: z.string().optional(), zipCode: z.string().optional(), }) .refine((data) => data.password === data.confirmPassword, { path: ["confirmPassword"], message: "Password do not match", });export type SignupInput = z.infer<typeof signupSchema>;
Type Export
I created type
of SignupInput
that could be matched up with signupSchema
.
import { z } from "zod";export const signupSchema = z .object({ firstName: z.string().nonempty({ message: "First name is required" }), lastName: z.string().nonempty({ message: "Last name is required" }), email: z .string({ required_error: "Email is required", }) .email({ message: "Invalid email address" }), phoneNumber: z .string() .regex(/^[0-9]+$/, { message: "Invalid phone number form" }), password: z .string({ required_error: "Password is required" }) .min(8, { message: "Should not be less than 8 characters" }) .max(16, { message: "Should not be no more than 16 characters" }), confirmPassword: z .string() .nonempty({ message: "You need to confirm password" }), role: z.string().min(1, { message: "Please choose your role" }), addressFirst: z.string().optional(), addressSecond: z.string().optional(), city: z.string().optional(), country: z.string().optional(), zipCode: z.string().optional(), }) .refine((data) => data.password === data.confirmPassword, { path: ["confirmPassword"], message: "Password do not match", });export type SignupInput = z.infer<typeof signupSchema>;
Creating Schema & Type
I created schema variable called signupSchema
.
As data will be in object type, I used .object()
(opens in a new tab)
and objectified them.
firstName
& lastName
Basically I made first name and last name as required value.
Therefore, I used .nonempty()
(opens in a new tab)
for marking them as required, and showed error message
if they didn't pass the validation.
email
With using .email()
(opens in a new tab) within .string()
,
I could easily check whether email address is in valid format or not.
phoneNumber
At first I thought phoneNumber
should be a number but ended up saving it
as a string type for unifying types of signup inputs.
So I used .regex()
to check whether it contains other strings (including space) other than numbers.
confirmPassword
With using .refine()
(opens in a new tab), I checked whether
confirmPassword
is as same as password
, and if not I made it threw an error message
.
role
As I let users to choose their role
in their frontend with using <select>
and <option>
tags,
I set .min()
to be 1 so that it could be the required value (Default value is empty string).
Fields For Address
I set all fields for addresss as optional with using .optional()
(opens in a new tab).
import { z } from "zod";export const signupSchema = z.object();export type SignupInput = z.infer<typeof signupSchema>;