User SignUp Error Messages in Node vs. JavaScript vs. Python/Flask

March 07, 2018 0 Comments

User SignUp Error Messages in Node vs. JavaScript vs. Python/Flask

 

 

When building a website, we often may need to create a way for users to sign in. In doing so, we probably also want to avoid users having duplicate user names. We may even want to restrict what the user names can be (longer than ‘x’ characters, etc).

We basically want to take this:

And turn it into this:

Below are a few ways to handle that scenario using client side JavaScript, server side Node and also server side Python/Flask.

First, here’s one possible way to handle this using client side JavaScript only. The great thing about using JavaScript is we can write a function that runs on input instead of on submit, which means that it will update as a user is typing. For example a user intends to input the username “Sammy”, but the username “Sam” is already in the database. Once the user types “Sam” we could create an error that would notify the user that “Sam” is already taken. Of course they could continue to type the rest of the user name, but it may be nice to have that warning in advance of submitting the username and then getting an error. Of course, this might not always be the best option, especially if there are millions of users. The function would have to check against millions of users each time a key is pressed. But with smaller databases, it seems like it should work fine. Here’s an example of how that code might look:

Before we start our ‘on input’ function, there are a few variables I’ll need to create ahead of time, so that I can work with them throughout the ‘on input function’. First, I’ll need to fetch all the current usernames so that I can compare the current input against them. Second, my signup will have a username, password and email. Here, I’m not going to get into handling the email and password, but I am creating a variable for each one like this:

        let userVal = false;
let passVal = false;
let emailVal = false;

I’m setting the value of each to false. If the user enters a username that meets all the criteria I set, then I’ll turn the userVal to true. Then, I’ll do the same with passVal and emailVal. But I’ll prevent a user from being able to submit unless all three are true. That way, even if a username matches the proper criteria, they still can’t sign up unless the other criteria for password and email area also met.

Let’s start with the fetch, it might look something like this:

fetch('urlforusernamesindatabase').then(function(response) {
if (response.status != 200) {
window.alert("oopsie daisy");
return;
}
response.json().then(function(data) {
let api = data;

Next, we’ll select the username field using jQuery and create an on input function:

$('#username').on('input', function() {
///---Do stuff here----///
});

We are also going to have a submit button, so, in the — do stuff here — section, I want to disable the submit button and flash a message if the current input doesn’t meet our criteria. To start, I’ll grab the current value of the user input and store it as a variable called “value”.

$('#username').on('input', function() {
let value2 = document.getElementById('username');
let value = username.value;
});

Next, I’m going to check the value against the criteria I want. In this case, I want usernames to be at least 6 characters long.

$('#username').on('input', function() {
let value2 = document.getElementById('username');
let value = username.value;
if(value.length < 6){
document.getElementById('userMsg').innerHTML = "username must be longer than 6 characters";
let btn = document.getElementById("userBtn");
btn.disabled = true;
btn.className = 'noDisplay';
userVal = false;
}
});

In the above example, if the input value is less than 6 characters then I flash a message to the user in a div below the input that lets them know there’s a problem. I also grab the submit button and disable it, and set it’s class to ‘noDisplay’ which is a CSS class I’ve made that is display:none. This way, the user can’t submit or even see the submit button. This is also the default state of the button, but I’m setting it again here just to be sure. Next, I select that userVal variable that I mentioned earlier and keep it as false.

Next, I’ll create a few more scenarios that would also result in false, preventing the user from submitting the information. The next scenario is that the username is 6 characters, but the password is empty:

if(value.length >= 6 && password  ""){
document.getElementById('userMsg').innerHTML = "please enter a valid password";
let btn = document.getElementById("userBtn");
btn.disabled = true;
btn.className = 'noDisplay';
userVal = false;
}

Next, we’ll make a scenario where the user enters a username that already exists. I’ll check that by seeing if there’s an index of the variable ‘value’ in the database already:

if(usernames.indexOf(value)>-1){
document.getElementById('userMsg').innerHTML = "Sorry, that username is already taken!";
let btn = document.getElementById("userBtn");
btn.disabled = true;
btn.className = 'noDisplay';
userVal = false;
}

Finally, we could have a scenario were all the criteria in each input are met:

if (userVal  true && passVal  true && emailVal  true){
document.getElementById('eMsg').innerHTML = "";
document.getElementById('userMsg').innerHTML = "";
document.getElementById('passMsg').innerHTML = "";
let btn = document.getElementById("userBtn");
btn.disabled = false;
btn.className = 'btn90';
}

We set the message field to empty, enable the submit button and change the class to one that is visible.

Because we don’t necessarily need to be concerned with a back end, if we handle the error messages client side, the only files we really need would be HTML and JavaScript, which could even be one file. Handling errors and user logins server side is a little more complex, and so, before we get started with that, I want to show what a typical file tree might look like, as we will probably need to have several files to keep our data separate and organized. Here is more or less the way I would organize my Node and Python file trees.

The functionality is going to be pretty similar in both Python and Node, with mainly syntactical differences.

In Node, if we want to create a user validation, we’ll first need to create the appropriate route and data model. Let’s start with the model first. I usually like to keep my models in a separate file, in import them into a main app.js file. As you can see, I’m also using the mongoose package to create my models. You can use mongoose as well by doing ‘npm install mongoose’ in the command line, if you prefer to use it to create your models. In mongoose, we create a scheme that represents our data. Here I’m including both username and a password.

const mongoose = require('mongoose');
let Schema = mongoose.Schema;
const userSchema = new Schema({
username: {
type: String,
required: true,
},
password: {
type: String,
},
})
const User = mongoose.model('User', userSchema);
module.exports = User;

now that we’ve exported this module as ‘User’, we’ll want to import this into our app.js file.

const User = require('./models/user.js');

In my file tree, I’ll have a separate folder for all of my models. In my main app.js file, I can import each of my models this way, in case there are multiple data models.

Next, we’ll create a route for our signup page:

app.get('/signup', function(req, res) {
res.render('signup')
});

Then we’ll create a POST that will create a new user:

app.post('/signup', function(req, res) {
User.create({
username: req.body.username,
password: req.body.password,
}).then(function(user) {
req.username = user.username;
req.session.authenticated = true;
}).then(user => {
res.redirect('/index')
});
});

In our POST request here, we’re using the req.body.username and req.body.password. These specify the input names in the HTML body. As such, our HTML input names should be ‘username’ and ‘password’ so that they match our route here. Here’s how our HTML might look in order to make sure it matches our routes in Node:

<body>
<div class="bodywrapper">
<div class="loginContainer">
<div>
<div class="login">
<div class="title2b">Username: </div>
<form class="logininput" action="/signup" method="post">
<input class="input2" required type="text" name="username" placeholder="enter username">
</div>
<div class="login">
<div class="title2b">Password: </div>
<input class="input2" required type="password" name="password" placeholder="enter password">
</div>
</div>
<div>
<input class="loginsubmit-button" type="submit">
</div>
</form>
</div>
<div class="logintext">signup</div>
</div>
</body>

Now, up to this point in node, we’ve simply created a user signup, but haven’t created any of the user validation that we did using JavaScript. But, it’s a good idea to make sure all of this works first, so that our problems are easier to diagnose at each step. Finally, we’ll go back to our model for our User, and add some validation there, which will prevent a user from signing up with the same username. Our updated user model could now look like this:

const mongoose = require('mongoose');
let Schema = mongoose.Schema;
const userSchema = new Schema({
username: {
type: String,
required: true,
validate: {
isAsync: true,
validator: function(value, isValid) {
const self = this;
return self.constructor.findOne({ username: value}).exec(function(err, user){
if(err){
throw err;
}
else if(user) {
if(self.id === user.id) {
return isValid(true);
}
return isValid(false);
}
else{
return isValid(true);
}
})
},
message: 'The Username is already taken!'
},
},
password: {
type: String,
},
})
const User = mongoose.model('User', userSchema);
module.exports = User;

using the findOne method, we basically use the same idea that we used in JavaScript, but look for duplicates in our User Database. If there are duplicates, we’ll get a server side message that will inform us that the Username is already taken.

First, we’ll import a few packages from flask that will make it easier to create the forms needed, then we’ll create the forms on the back end using Python. The packages I want to install are flaskwtf, with which I can access some validators that will do most of the heavy lifting for us.

from flaskwtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo

Then, I’ll create a new class called RegistrationFrom from FlaskForm.

class RegistrationForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
email = StringField('Email', validators=[DataRequired(), Email()])

password = PasswordField('Password', validators=[DataRequired()])
password2 = PasswordField('Repeat Password', validators=[DataRequired(), EqualTo('password')])

submit = SubmitField('Register')

In these forms, we can add validation for the username in a function. when we add any methods that match the pattern validate<fieldname>, WTForms takes those as custom validators that invoke them in addition to the stock validators. In this case, we want to make sure that the username entered is not already in the database, so this method issues a database query expecting there will be no results. In the event a result exists, a validation error is triggered by raising ValidationError. The message included as the argument in the exception will be the message that will be displayed next to the input field for the user to see. That function, we can just include in our class as follows:

def validateusername(self, username):
user = User.query.filterby(username=username.data).first()
if user is not None:
raise ValidationError('Please use a different username.')

Because this achieves the same result as our other examples, we now need to go back and create our form in HTML, and create the route for the form in our routes.py file.

Just as we made some imports in our python forms, we’ll need to import flask here as well.

from flask import rendertemplate, flash, redirect
from flask import request, urlfor
from werkzeug.urls import url
parse
from app import app
from app.forms import LoginForm
from flasklogin import currentuser, loginuser
from app.models import User
from flask
login import logoutuser
from flask
login import loginrequired
from app import db
from app.forms import RegistrationForm

Then, we’ll create our route just like we did using node. Here, we need to make sure that the user that invokes this route is not logged in.

@app.route('/register', methods=['GET', 'POST'])
def register():
if current
user.isauthenticated:
return redirect(url
for('index'))
form = RegistrationForm()
if form.validateonsubmit():
user = User(username=form.username.data, email=form.email.data)
user.setpassword(form.password.data)
db.session.add(user)
db.session.commit()
flash('Congratulations, you are now a registered user!')
return redirect(url
for('login'))
return rendertemplate('register.html', title='Register', form=form)

The logic that is done inside the if validateonsubmit() conditional creates a new user with the username, email and password provided, writes it to the database, and then redirects to the login prompt so that the user can log in.

Obviously in here, we’ll also have routes for our index page, as well as our login page, but for now, we’re only concerned with the issue of registering new users.

Next, we’ll create a user model in models.py, just like we did in our mongoose models earlier. The syntax here will be a little different, but the idea is pretty much the same:

class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary
key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
passwordhash = db.Column(db.String(128))
def _repr(self):
return '<User {}>'.format(self.username)
def setpassword(self, password):
self.password
hash = generatepasswordhash(password)
def checkpassword(self, password):
return check
passwordhash(self.passwordhash, password)

I’ve included some logic here to create a password hash, which we didn’t do in our node model. Now, the last step is to create the HTML. One of the great things about Flask, is that we can create a base template, and then extend that template. For example, if each HTML page is going to have a navigation, we can add a navigation bar as part of a base HTML template. Then, our index, login or main pages will simply extend our base HTML page. This functionality may feel very similar to the way this is handled in react, if you’re familiar with that syntax. Here’s one way we could set up our base template:

<html>
<head>

</head>
<body>
<div>
<a href="{{ urlfor('index') }}">Home</a>
{% if current
user.isanonymous %}
<a href="{{ url
for('login') }}">Login</a>
{% else %}
<a href="{{ urlfor('logout') }}">Logout</a>
{% endif %}
</div>

{% block content %}{% endblock %}
</body>
</html>

This would be contained in our templates folder, and we could name the file base.html. Then, in a second HTML file, we could do something like this:

{% extends "base.html" %}
{% block content %}
<h1>Register</h1>
<form action="" method="post">
{{ form.hidden
tag() }}
<p>
{{ form.username.label }}<br>
{{ form.username(size=32) }}<br>
{% for error in form.username.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>
{{ form.email.label }}<br>
{{ form.email(size=64) }}<br>
{% for error in form.email.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>
{{ form.password.label }}<br>
{{ form.password(size=32) }}<br>
{% for error in form.password.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>
{{ form.password2.label }}<br>
{{ form.password2(size=32) }}<br>
{% for error in form.password2.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>{{ form.submit() }}</p>
</form>
{% endblock %}

Here, the operation that converts a template into a complete HTML page is called rendering. To render the template, we need to import a function that comes with the Flask framework called rendertemplate(), which you may have noticed in the imports we called earlier. This function takes a template filename and a variable list of template arguments and returns the same template, but with all the placeholders in it replaced with actual values. The rendertemplate() function invokes the Jinja2 template engine that comes bundled with the Flask framewwork. Jinja2 substitutes {{ … }} blocks with their values given by the arguments.

With that, we are basically done with that part of our site. Of these three methods, obviously the JavaScript is much faster, and can be done more or less on the fly. However, we could run into a lot of issues with that method, and is probably not generally recommended. However, I do like the idea of preventing a user from even being able to submit until the conditions we need are met. When I started programming, I used mainly node and JavaScript, but recently began learning Python and Flask. My opinion, in just starting out, is that Flask and Python offer a lot of packages that seem to simplify the process of creating a user login much more than Node does. However, the logic in both is basically the same in a lot of ways.


Tag cloud