Last active
April 18, 2025 15:25
-
-
Save mtvbrianking/9ce02a64786457ed92a096e0c57f9adb to your computer and use it in GitHub Desktop.
zod client & server side form validation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script setup> | |
import { reactive } from 'vue' | |
import { z } from 'zod' | |
const schema = z.object({ | |
email: z.string().email(), | |
password: z.string().min(6), | |
// remember: z.boolean().optional(), | |
}) | |
const form = reactive({ | |
email: '', | |
password: '', | |
errorMessage: '', | |
errors: {}, | |
}) | |
const onSubmit = () => { | |
form.errors = {} | |
form.errorMessage = '' | |
const result = schema.safeParse(form) | |
if (!result.success) { | |
const { fieldErrors, formErrors } = result.error.flatten() | |
form.errors = fieldErrors | |
form.errorMessage = formErrors?.[0] || '' | |
return | |
} | |
console.log('Form submitted successfully!', form) | |
alert('Form submitted successfully!') | |
} | |
</script> | |
<template> | |
<div class="max-w-md mx-auto p-4 border rounded space-y-4 bg-white shadow"> | |
<h2 class="text-xl font-semibold text-center">Login</h2> | |
<p v-if="form.errorMessage" class="text-red-500"> | |
{{ form.errorMessage }} | |
</p> | |
<div> | |
<label class="block mb-1 font-medium">Email</label> | |
<input | |
v-model="form.email" | |
type="email" | |
class="w-full border px-3 py-2 rounded focus:outline-none focus:ring focus:border-blue-300" | |
placeholder="[email protected]" | |
/> | |
<p v-if="form.errors.email" class="text-red-500 text-sm mt-1">{{ form.errors.email[0] }}</p> | |
</div> | |
<div> | |
<label class="block mb-1 font-medium">Password</label> | |
<input | |
v-model="form.password" | |
type="password" | |
class="w-full border px-3 py-2 rounded focus:outline-none focus:ring focus:border-blue-300" | |
placeholder="********" | |
/> | |
<p v-if="form.errors.password" class="text-red-500 text-sm mt-1">{{ form.errors.password[0] }}</p> | |
</div> | |
<button | |
type="submit" | |
class="w-full bg-blue-600 text-white py-2 px-4 rounded hover:bg-blue-700 transition" | |
@click.prevent="onSubmit" | |
> | |
Login | |
</button> | |
</div> | |
</template> | |
<style scoped> | |
input:focus { | |
border-color: #3b82f6; | |
} | |
</style> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script setup lang="ts"> | |
import { reactive } from 'vue' | |
import { z } from 'zod' | |
import axios from 'axios' | |
const schema = z.object({ | |
email: z.string().email(), | |
password: z.string().min(8), | |
// remember: z.boolean().optional(), | |
}) | |
const form = reactive({ | |
email: '', | |
password: '', | |
errors: {} as Record<string, string[]> | |
}) | |
const onSubmit = async () => { | |
form.errors = {} | |
const formData = { | |
email: form.email, | |
password: form.password, | |
} | |
const result = schema.safeParse(formData) | |
if (!result.success) { | |
// handle client side form validation errors | |
const fieldErrors: Record<string, string[]> = {} | |
for (const [key, val] of Object.entries(result.error.flatten().fieldErrors)) { | |
if (val) fieldErrors[key] = val | |
} | |
form.errors = fieldErrors | |
return | |
} | |
try { | |
const response = await axios.post('/api/login', formData) | |
console.log('Login successful:', response.data) | |
alert('Login successful!') | |
} catch (error: any) { | |
if (error.response?.status === 422) { | |
// handle server side form validation errors | |
form.errors = error.response.data.errors | |
} else { | |
const errMsg = | |
error.response?.data?.message || | |
error.response?.statusText || | |
'Something went wrong.' | |
console.error(errMsg) | |
alert(errMsg) | |
} | |
} | |
} | |
</script> | |
<template> | |
<form @submit.prevent="onSubmit" class="max-w-md mx-auto p-4 border rounded space-y-4 bg-white shadow"> | |
<h2 class="text-xl font-semibold text-center">Login</h2> | |
<div> | |
<label class="block mb-1 font-medium">Email</label> | |
<input | |
v-model="form.email" | |
type="email" | |
class="w-full border px-3 py-2 rounded focus:outline-none focus:ring focus:border-blue-300" | |
placeholder="[email protected]" | |
/> | |
<p v-if="form.errors.email" class="text-red-500 text-sm mt-1">{{ form.errors.email[0] }}</p> | |
</div> | |
<div> | |
<label class="block mb-1 font-medium">Password</label> | |
<input | |
v-model="form.password" | |
type="password" | |
class="w-full border px-3 py-2 rounded focus:outline-none focus:ring focus:border-blue-300" | |
placeholder="********" | |
/> | |
<p v-if="form.errors.password" class="text-red-500 text-sm mt-1">{{ form.errors.password[0] }}</p> | |
</div> | |
<button | |
type="submit" | |
class="w-full bg-blue-600 text-white py-2 px-4 rounded hover:bg-blue-700 transition" | |
> | |
Login | |
</button> | |
</form> | |
</template> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment