How I replaced 200 lines of Zod refinements with 12
Every project I start ends up with the same validation code. Email with disposable domain blocking. Password with strength rules. Phone number via libphonenumber-js. Username with reserved word che...

Source: DEV Community
Every project I start ends up with the same validation code. Email with disposable domain blocking. Password with strength rules. Phone number via libphonenumber-js. Username with reserved word checks. All built with .refine() and .superRefine() chains on top of Zod. Different projects, different defaults, forgotten edge cases. The same bugs are showing up in different codebases because I copied the wrong version of the email regex. Here's what a typical registration form looked like before: const schema = z.object({ email: z .string() .email() .refine( async (val) => { const domain = val.split('@')[1] const list = await loadDisposableDomains() return !list.includes(domain) }, 'Disposable emails not allowed' ) .refine((val) => !val.includes('+'), 'Plus aliases not allowed'), password: z .string() .min(8) .max(128) .refine((val) => /[A-Z]/.test(val), 'Needs uppercase') .refine((val) => /[a-z]/.test(val), 'Needs lowercase') .refine((val) => /\d/.test(val), 'Needs digit') .