Node.js Express, form validation, and keeping previously posted form values

I've been playing with Node.js and the Express webserver framework as a learning experience, it's good fun :)

In the C# ASP MVC world using the Razor view engine I can define my user interface elements like this...

@Html.TextBoxFor(x => x.EmailAddress)
@Html.ValidationMessageFor(x => x.EmailAddress)

This will do three things

  1. It will create the html output for an input element
  2. If the view is being rendered as a result of a POST it will set the value of the input to the value posted.  This is useful for when you have a form validation error and don't want to have to force the user to re-enter all of their input.
  3. If there is an error message registered for EmailAddress it will display the error text
*Note that error messages are registered using ModelState.AddModelError("EmailAddress", "The error message")

Node.js, Express, and Jade

Express is a very light weight framework so doesn't do any of this stuff for you, so I had to hunt around for a solution which I am now blogging for the sake of record.

To satisfy my validation requirement I used express-validator by Christoph Tavan (install it using "npm install express-validator").  It is used like this

app.js
var expressValidator = require('express-validator');
app.use(expressValidator());

mycontroller.js
app.get('/signup', signUp);
app.post('/attemptSignUp', attemptSignUp);

function signUp(req, res) {
    res.render('signup', {
        title: 'Sign up',
        errors: []
    });
}

function attemptSignUp(req, res) {
    req.checkBody('username', 'Required').notEmpty();
    res.locals.errors = req.validationErrors(true);
    res.render('signup', {
        title: 'Sign up',
        errors: req.validationErrors(true)
    });
}

As you can see in the above source I have created two routes. The first will GET the view at /signup, the second will accept a POST from the form generated in the /signup request.  Because we have added the express-validation middleware in the main app we now have access to a new checkBody method on the request object.  Any checks that fail will be reported by the req.validationErrors method.

The result of validationErrors is fed into the Jade view engine with the name "errors", in the /signup action this is an empty array because there are no errors, and in the /attemptSignUp the errors from the validation are passed in.

To display the errors requires a simple addition to your Jade view

signup.jade
if (errors['username'])
    span.text-error #{errors['username'].msg}

If there is an error for "username" then a span with the css-class "text-error" and with the error message as its contents.

Keeping form input

Having to re-enter form data whenever you get something wrong is annoying.  I haven't yet found Express middleware that deals with this so I made my own very simple library.

To use it requires the following addition to your jade view

signup.jade
input(type="text", name="username", value="#{posted('username')}", required)

I have added the "required" keyword for client-side validation, so in this example the username must be invalidated on the server, perhaps because the user name is already in use, but you can remove it for the sake of testing.

For checkboxes you can use this
input(type="checkbox", name="accepttermsandconditions", checked=posted.checked('accepttermsandconditions'))

The new lib is located in ./lib/form-values/index.js

exports = module.exports = function (req, res, next) {
    var body = req.body;
    if (typeof body === 'undefined') {
        return next(new Error('form-values must be used after body-parser'));
    }
    var bodyKeys = Object.keys(req.body);
    res.locals.posted = function (name, defaultValue) {
        if (bodyKeys.indexOf(name) === -1) {
            if (typeof defaultValue === 'undefined')
                return '';
            return defaultValue;
        } else {
            return req.body[name];
        }
    }
    res.locals.posted.checked = function (name) {
        var postedValue = res.locals.posted(name, null);
        if (postedValue === null)
            return undefined;
        return '';
    }
    return next();
};

If you look closely you will see it is also possible to call posted('fieldname') with a second parameter which will act as a default value only if there is no posted value.

Comments

Popular posts from this blog

Connascence

Convert absolute path to relative path

Printing bitmaps using CPCL