The extra ordinary validation package for express applications
There are already validation packages that work with express already, why another package? Well, the problem is they make validation more complicated than it has to be.
This package can be installed from npm in an existing express js app
yarn add @flixtechs/ordinary
Using this package is easy, you just need to register the middleware and error handler. The middleware is registered before you define your routes. Make sure it's registered after you register the body parsers
Then register the error handler after all your middleware has been registered but before your custom error handler so other errors that are not validation errors can be handled
import { ordinary, remarkable } from '@flixtechs/ordinary'
// register body parser and what not
app.use(ordinary(app))
//your routes and other middleware
app.use(remarkable)
//your error handlers
Now you can validate incoming input in your routes like this
router.post('/login', async (req, res, next) => {
const { email, password } = await req.validate({
email: 'required|string|email|max:255',
password: 'required|string|min:8',
})
.catch(error => next(error))
//compare password
return res.redirect('back')
}
If there is a validation error it will throw a ValidationError
exception hence the .catch
on the promise. We then forward that error to the next middleware so the exception is handled by express.
For express 5 and above there's no need to handle the exception because express does this automatically under the hood which leaves us with clean code
The remarkable error handler will return a 422 Unprocessable Entity
if the request is expecting json with the errors in the response body like in the example below
{
"message": "UNPROCESSABLE_ENTITY",
"errors": {
"email": [
"These credentials do not match our records"
]
}
}
If it was a normal traditional web request it will redirect back with errors and old input the session.
Currently it does not have helper functions for template engines but will be available soon. In the meantime you have to manually check the session for the errors key then display errors and old nput accordingly
Errors will be in the errors
key and old input in the old
key
This package also adds a few methods to the req
object to help handle incoming input
Returns a collection instance of safe and validated input. By safe
we mean the input that you specified needs validation for example if your validation rules look like this
{
email: 'required|string|,
password: 'required|string'
}
If the request body comes with extra input say name
then calling safe()
will only return
email
and password
other input will be ignored
Example usage of this method
router.post('/register', async (req, res, next) => {
await req.validate({
email: 'required|string|email|max:255',
password: 'required|string|min:8|confirmed',
name: 'required|string|max:255'
})
.catch(error => next(error))
await User.create(req.safe().all())
return res.redirect('back')
}
Works the same way as safe()
but instead of returning a collection it returns a plain object
router.post('/register', async (req, res, next) => {
await req.validate({
email: 'required|string|email|max:255',
password: 'required|string|min:8|confirmed',
name: 'required|string|max:255'
})
.catch(error => next(error))
await User.create(req.validated())
return res.redirect('back')
}
Also accepts keys to return only those keys
router.post('/register', async (req, res, next) => {
await req.validate({
email: 'required|string|email|max:255',
password: 'required|string|min:8|confirmed',
name: 'required|string|max:255'
})
.catch(error => next(error))
await User.create(req.validated('email', 'password')) //name will not be included
return res.redirect('back')
}
Works the same way as validated()
except as the name suggest it returns only the specified keys
req.only('name', 'email')
Checks if the incoming request body has a specific key
if(req.has('subscribe')) {
// subscribe user to the newsletter
}
Checks if a key is missing in the request body
Returns the value of the specified key
const name = req.input('name')
For more complex validation scenarios, you may wish to create a "form request". Form requests are custom request classes that encapsulate their own validation logic.
All form requests are classes extending the Request
class. Create a directory
requests/
and create a file requests/StorePostRequest
//requests/StorePostRequest.js
export class StorePostRequest extends FormRequest {
rules() {
return {
title: 'required|string|max:255|min:3',
body: 'required|string|max:255|min:3',
status: 'required|string|in:draft,published'
};
}
}
As you might have guessed, the rules method returns the validation rules that should apply to the request's data
So, how are the validation rules evaluated? All you need to do is call the init(req.body)
method of the request on your controller method with the req.body
as argument. The method is a static method called once at the begining of your controller action meaning you won't have to clutter your controller with validation logic. The init method returns a promise like most validation methods in this package
import { StorePostRequest } from '../requests/StorePostRequest.js'
router.post('/posts', (req, res, next) => {
const request = await StorePostRequest.init(req.body).catch(next)
// The incoming request is valid...
// Retrieve the validated input data...
const validated = request.validated()
// Retrieve a portion of the validated input data...
const validated = request.validated('title', 'body')
const validated = request.except('status', 'title')
//store the blog post
return res.redirect('/posts')
})
If validation fails, a redirect response will be generated to send the user back to their previous location. The errors will also be flashed to the session so they are available for display. If the request was an XHR request, an HTTP response with a 422 status code will be returned to the user including a JSON representation of the validation errors.
You may customize the error messages used by the form request by overriding the messages method. This method should return an object of attribute / rule pairs and their corresponding error messages:
//requests/StorePostRequest.js
export class StorePostRequest extends FormRequest {
rules() {
return {
title: 'required|string|max:255|min:3',
body: 'required|string|max:255|min:3',
status: 'required|string|in:draft,published'
};
}
messages() {
return {
'title.required': 'Every blog post needs a good title!'
}
}
}
This package is built on top of simple-body-validator and it uses all the validation rules said in their docs
To run tests, run the following command
npm run test
For support, email given@flixtechs.co.zw