Skip to content

Instantly share code, notes, and snippets.

@mtvbrianking
Last active April 18, 2025 15:25
Show Gist options
  • Save mtvbrianking/9ce02a64786457ed92a096e0c57f9adb to your computer and use it in GitHub Desktop.
Save mtvbrianking/9ce02a64786457ed92a096e0c57f9adb to your computer and use it in GitHub Desktop.
zod client & server side form validation
<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>
<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