example
Base usage
- First step is to define you errors parsers. Usually you can do in
utilsorshared/utilsdirectory.
import type { ValidationErrorParser } from 'nuxt-precognition'
// app/utils/precognition.ts or shared/utils/precognition.ts
export const customErrorParser: ValidationErrorParser = (error) => {
if (error instanceof CustomError) {
const errors: Record<string, string[]> = {}
for (const issue of error.issues) {
const key = issue.path.join('.')
if (key in errors) {
errors[key].push(issue.message)
continue
}
errors[key] = [issue.message]
}
return { errors, message: error.message }
}
return null
}
- Add the parsers globally
// app/plugins/precognition.ts
export default defineNuxtPlugin(() => {
const { $precognition } = useNuxtApp()
$precognition.errorParsers.push(customErrorParser)
// ..
})
Thats it!
From now on, the module knows how to parse ValidationErrors.
Note: Remember that global parsers will be used all times when
Erroris intercepted.
Example usage
Use the composable in setup method.
const UserSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
})
const form = useForm(
(): z.infer<typeof UserSchema> => ({
email: '',
password: '',
}),
(body, headers) => $fetch('/api/login', {
method: 'POST',
headers,
body,
}),
{
clientValidation(data) {
UserSchema.parse(data)
},
},
)
function login() {
form.submit()
}
function reset() {
form.reset()
document.getElementById('email')?.focus()
}
<form @submit.prevent="login" @reset.prevent="reset">
<div>
<label for="email">Email address</label>
<input id="email" v-model="form.email" name="email" type="email" @change="form.validate('email')" />
<span v-if="form.valid('email')">OK!!</span>
<span v-if="form.invalid('email')">{{ form.errors.email }}</span>
</div>
<div>
<label for="password">Password</label>
<input
id="password"
v-model="form.password"
name="password"
type="password"
autocomplete="current-password"
required
@change="form.validate('password')"
/>
<span v-if="form.valid('password')">OK!!</span>
<span v-if="form.invalid('password')">{{ form.errors.password }}</span>
</div>
<div>
<button type="submit">Sign in</button>
<button type="reset">Reset</button>
</div>
</form>
Server side validation
Wait what about http errors? And how can we validate data but skipping next steps?
- update the default configuration.
// nuxt.config.ts
export default defineNuxtConfig({
modules: [
'nuxt-precognition'
],
precognition: {
backendValidation: true,
enableNuxtClientErrorParser: true,
},
})
Here we are instructing the module to:
- add backend validation (
precognitive-requests) when we request single key validation. - add global parser to translate
NuxtErrorstoValidationErrors.
- Create a Nitro plugin to parse server errors:
// server/plugins/precognition.ts
import { ZodError } from 'zod'
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('request', (event) => {
event.context.$precognition.errorParsers = [
zodErrorParser
]
})
})
Assuming we are using same validation library on backend (in this example zod), we need to translate zod errors to NuxtErrors the module will understand.
- Use
definePrecognitiveEventHandlerin the object way and add validation in theonRequesthook.
import { definePrecognitiveEventHandler, readBody } from '#imports'
// server/api/login.post.ts
import { z } from 'zod'
const loginSchema = z.object({
email: z.string().email(),
password: z.string()
}).refine((_data) => {
// Check for email and password match
// ...
return true
}, { message: 'invalid credentials', path: ['email'] },)
export default definePrecognitiveEventHandler({
async onRequest(event) {
const body = await readBody(event)
loginSchema.parse(body)
},
handler: () => {
return {
status: 200,
body: {
message: 'Success',
},
}
},
})
Splitting the handler in different hooks, we can isolate validation from the main functionality.
When the precognitive-request is detected, only the validation function will run.
Custom Parsers per request
It happens you have a specific apis, where you can have errors in different shapes. For example using common authentication services with specific sdks.
No problem, you can define the parser on the useForm composable.
const form = useForm(
(): z.infer<typeof UserSchema> => ({
email: '',
password: '',
}),
(body, headers) => $fetch('/api/login', {
method: 'POST',
headers,
body,
}),
{
clientValidation(data) {
UserSchema.parse(data)
},
clientErrorParsers: [
(error) => {
if (error instanceof CustomError) {
// ....
return { errors, message: error.message }
}
//
return null
}
]
},
)
This parser will be used only for this form submission.
Same thing on backend.
export default definePrecognitiveEventHandler({
async onRequest(event) {
const body = await readBody(event)
loginSchema.parse(body)
},
handler: () => {
return {
status: 200,
body: {
message: 'Success',
},
}
},
}, {
errorParsers: [
(error) => {
if (error instanceof z.ZodError) {
return {
message: 'Invalid data',
errors: error.issues.map(issue => ({
path: issue.path.join('.'),
message: issue.message,
}))
}
}
}
]
})