Validating server side forms

Author - Virk

Published on Tue May 05 2020

This guide covers the usage of validator to validate the forms rendered on the server using Edge templates. We will use using session flash messages to access the validator errors.

Creating the form

The final example is hosted on codesandbox. Click here to preview the outcome or edit the project directly on codesandbox.

AdonisJS does not interfere with your HTML and you define the forms using the standard HTML syntax. In other words, AdonisJS doesn't have any form builders doing magic behind the scenes, and hence you have the complete freedom to structure the HTML the way you want.

The following is the HTML form to create a new blog post by accepting the post title and body.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Create a new blog post</title>
</head>
<body>
<form action="/posts" method="POST">
<div>
<p>
<label for="title"> Post title </label>
</p>
<input type="text" name="title" />
</div>
<div>
<p>
<label for="body"> Post body </label>
</p>
<textarea name="body" cols="30" rows="10"></textarea>
</div>
<div>
<button type="submit">Create Post</button>
</div>
</form>
</body>
</html>

As you can notice, the entire document is vanilla HTML with no special syntax inside it. As a small improvement, you can replace the hard-coded form action /posts with the route helper method.

Assuming the following route declarations.

import Route from '@ioc:Adonis/Core/Route'
Route.get('posts/create', 'PostsController.create')
Route.post('posts', 'PostsController.store')

You can get the route URL for storing a new post using its controller.action name.

<form action="/posts" method="POST">
<form action="{{ route('PostsController.store') }}" method="POST">
<!-- Rest of the form -->
</form>

Validating the form

Let's continue with the same form we created above and implement the PostsController.store method to validate the incoming request.

import { schema, rules } from '@ioc:Adonis/Core/Validator'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export class PostsController {
public async store({ request }: HttpContextContract) {
const postSchema = schema.create({
title: schema.string({ trim: true }, [
rules.minLength(10)
]),
body: schema.string(),
})
const payload = await request.validate({ schema: postSchema })
console.log(payload)
return 'Post created'
}
}

schema.create

The schema.create method creates a new schema definition. The schema defines the shape of the data you expect.


schema.string

The schema.string method enforces the value to be a valid string. There are other similar methods to enforce different data types like a boolean, a number and so on.

Optionally, you can define additional validations on a property using the rules object.


request.validate

The request.validate method accepts the pre-defined schema and validates the request body against it.

If the validation fails, the validator will redirect the client back to the form along with the error messages and the form values.

If the validation succeeds, you can access the validated properties as the method return value.

Displaying validation errors

The validation errors are shared with the form using session flash messages . Inside your edge templates, you can access them using the flashMessages global property.

Errors structure inside flash messages

{
errors: {
title: ['required validation failed'],
body: ['required validation failed'],
}
}

Access error messages

{{ flashMessages.get('errors.title') }}
{{ flashMessages.get('errors.body') }}

Retaining input values

Post redirect, the browser re-renders the page and the form values are reset to their initial state. However, you can access the submitted values using flash messages.

You can access the form input values using the field name. For example:

<div>
<p>
<label for="title"> Post title </label>
</p>
<input
type="text"
name="title"
value="{{ flashMessages.get('title', '') }}"
/>
</div>

You can also nested values using the field name

<input
type="text"
name="user[email]"
value="{{ flashMessages.get('user[email]', '') }}"
/>

Displaying success message

The usage of flash messages is not only limited to the validation errors. You can also use them to display the success message post form submission. For example:

Controller
import { schema, rules } from '@ioc:Adonis/Core/Validator'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class PostsController {
// Create method
public async store({ request }: HttpContextContract) {
public async store({ request, session, response }: HttpContextContract) {
// ... Collapsed existing code
console.log(payload)
return 'Post created'
session.flash('success', 'Post created successfully')
response.redirect().back()
}
}

Access the success message inside edge template

@if(flashMessages.has('success'))
<p>{{ flashMessages.get('success') }}</p>
@endif

Form method spoofing

Standard HTML forms cannot make use of all the HTTP verbs beyond GET and POST. It means you cannot create a form with the method PUT.

However, AdonisJS allows you to spoof the HTTP method using the _method query string. In the following example, the request will be routed to the route listening for the PUT request.

<form method="POST" action="/posts/1?_method=PUT"></form>

Form method spoofing only works:

  • When the value of http.allowMethodSpoofing is set to true inside the config/app.ts file.
  • And the original request method is POST.