Nuxt Precognition is a "precognition" utility for sharing server-side validation with your front-end. This is inspired by Laravel Precognition and helps you provide a good user validation experience, while also helping to make the Nuxt form submission and validation process as smooth as possible.
- Convenient form submission and validation with
useForm
andusePrecognitionForm
composables - Server-side validation of form data before the actual form submission with
definePrecognitionEventHandler
Nuxt server middleware
Install the module to your Nuxt application with one command:
npx nuxi module add @gearbox-solutions/nuxt-precognition
That's it! You can now use Nuxt Precognition in your Nuxt app ✨
The usePrecogitionForm
composable is the core feature for setting up the precognition workflow. This composable extends the useForm
composable (described below)and provides a more robust form submission and validation workflow when used in conjunction with the handlePrecognitionRequest
server middleware.
The useProecognitionForm
composable is automatically imported into your Nuxt app when you install the module, and can be used without needing to manually import it.
This composable is used to create your form object and with handle form submission and validation. It is stateful, and will provide you with the form states and errors for you to nicely display validation and submission state in your components.
Here is an example of using the usePrecognitionForm
composable to create a form object and handle form submission and validation:
<script setup lang="ts">
// const form = useForm({
// description: '',
// })
const entries = ref([])
const form = usePrecognitionForm('post', '/api/todo-precog', {
description: '',
})
const submitForm = async () => {
await form.submit({
onSuccess: (response) => {
entries.value.push(response._data)
},
})
}
</script>
<template>
<div>
<div class="flex gap-x-8">
<form
class="space-y-4"
@submit.prevent="submitForm"
>
<div class="">
<label for="description"
class="block text-sm uppercase">
Description
</label>
<input
id="description"
v-model="form.description"
type="text"
name="description"
@change="form.validate('description')"
/>
<div
v-for="error in form.errors.description"
:key="error"
class="text-red-500"
>
{{ error }}
</div>
</div>
<div>
<button :disabled="form.processing">
Submit
</buttonprimary>
</div>
</form>
<pre>{{ form }}</pre>
</div>
<div class="pt-12">
<div class="text-lg font-bold uppercase">
Entries
</div>
<div
v-for="(entry, index) in entries"
:key="index"
>
{{ entry.description }}
</div>
</div>
</div>
</template>
The useForm
Vue composable provides a convenient way to handle form submission and validation errors in your Nuxt app. A form created through the useForm
composable provide your form submissions with a number of useful features:
- Lifecycle Hooks
onBefore
-onStart
-onSuccess
-onError
-onFinish
-
- Form State
isDirty
hasErrors
processing
wasSuccessful
recentlySuccessful
This form is based on the useForm
composable from Inertia.js and generally implements the same API and can be used in the same way. The documentation for the useForm
composable should be consulted for details on how to use the form.
This composable is automatically imported into your Nuxt app when you install the module, and can be used without needing to manually import it.
The useForm composable can be used without the precognition middleware, and is just a convenient way to handle form submission and validation errors in your Nuxt app in general.
Here is an example of using the form to submit a Todo item to an API endpoint:
<script setup lang="ts">
const entries = ref([])
const form = useForm({
description: '',
})
const submitForm = async () => {
await form.post('/api/todo', {
onSuccess: (response) => {
entries.value.push(response._data)
},
})
}
</script>
<template>
<div>
<div class="flex gap-x-8">
<form
class="space-y-4"
@submit.prevent="submitForm"
>
<div class="space-x-4">
<label for="description">Description</label>
<input
id="description"
v-model="form.description"
label="Description"
type="text"
name="description"
:errors="form.errors.description"
/>
</div>
<div>
<button :disabled="form.processing">
Submit
</button>
</div>
</form>
<pre>{{ form }}</pre>
</div>
<div class="pt-12">
<pre />
<div class="text-lg font-bold uppercase">
Entries
</div>
<div
v-for="(entry, index) in entries"
:key="index"
>
{{ entry.description }}
</div>
</div>
</div>
</template>
The handlePrecognitionRequest
server middleware is a server-side middleware that can be used to handle form submissions and validation errors in your Nuxt app. It is designed to work with the usePrecognitionForm
composable, and will validate the data submitted by that form, and will return validation results before it reaches the main part of your handler.
Note
Your main handler code will not run on a precognition validation request, even though it will be posting to the same endpoint
Validation is configured using a Zod Object which should be designed to handle the fields in your form subimssion.
Your Nuxt server routes should return a default definePrecognitionEventHandler
instead of the usual defineEventHandler
. This new handler takes a Zod object as its first parameter, and then the second parameter is your regular event handler function definition.
This new handler type is automatically imported by Nuxt when the module is installed, and does not need to be manually imported.
Example Todo API endpoint handler:
import { z } from 'zod'
// define the Zod object for validation
const todoRequestSchema = z.object({
description: z.string().trim().min(1, 'Description is required'),
})
// use the Precognition handler with the Zod schema
export default definePrecognitionEventHandler(todoRequestSchema, async (event) => {
const validated = await getValidatedInput(event, todoRequestSchema)
// do something with the body
const newTodo = {
id: 1,
description: validated.description,
}
// simulate a slow response to show the loading state on the front-end
await sleep(1000)
return newTodo
})
function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms))
}
Local development
# Install dependencies
npm install
# Generate type stubs
npm run dev:prepare
# Develop with the playground
npm run dev
# Build the playground
npm run dev:build
# Run ESLint
npm run lint
# Run Vitest
npm run test
npm run test:watch
# Release new version
npm run release