Check username and email availability without a page refresh

Learn how to implement asynchronous form validation for your projects. This allows feedback before tedious forms are completed

Martin Muzatko Written by Martin Muzatko

There is more infrastructure and technology to an inputfield than you might expect at first glance.

In your application, admins shouldn't be able to create a user that has the same username or email of another user. You want to avoid duplicated entries. But when is the right time to display that your input was meaningless? You should get that information ahead of time. Way before you hit the big submit button, just to find out that the user already exists and you have to go search for them. The time you needed to tediously fill in all the fields for that user? Wasted!

Today we are going to build one ot these inputs, to ease filling in those forms.

What are we building?

  • An inputfield to do realtime checking if a user already exists in the system
  • Finding out what data is required for this input field
  • Combining HTTP, Form, Ajax and Validation to get feedback before submission

Below, you can find a little example of how this could work in action.

Fill in the input now and get feedback right away.

<style> input:invalid \{ box-shadow: 0 0 5px 1px red; \} </style> <form method="POST" name="createuser"> <label for="username">Username</label> <small>Try an existing user such as john, jack or marilyn</small> <input required type="text" name="username"> <input type="submit"> </form> <script> var usernameInput = document.querySelector('input[name="username"]') async function getSimilarUser(username) \{ var response = await fetch(`$\{username\}`) return response.json() \} async function isUserValid(target) \{ let username = target.value let users = await getSimilarUser(username) if (users.length) \{ let existingUsername = users[0].name if (existingUsername == username) \{ target.pattern = `^((?!$\{existingUsername\}).)*$` target.setCustomValidity(`The user "$\{username\}" already exists`) return false \} \} target.removeAttribute('pattern') target.setCustomValidity('') return true \} usernameInput.addEventListener('input', async (e)=>\{ let isValid = await isUserValid( console.log(isValid) \}) </script>

What looks like no big deal, requires a lot of prep work. Open your devtools and inspect the network tab. Here you can click XHR to only listen to AJAX calls. When typing another username, you will see the results.

The devtools are your tool, to make sure that the requested data correctly gets to the front-end as expected. To see what that data looks like, click on Preview. I prepared a little REST API with fake data, so you can test for usernames and e-mail addresses. You can open that URL to test how the API works. Click the button below to see all users that start with "jo".

Try the API now

The REST API is made to deliver pure data. No HTML, no extra load. This makes loading additional data on the fly easier and also fast. In our case, we get a bunch of JSON data.

Becoming aware of your information architecture

Note: Before you start designing and developing that fancy input, you should be aware of the information architecture at your disposal.

This step is absolutely crucial!

Are you responsible for both front, and back-end but have little understanding of the underlying system? This sucks!

Do you have a back-end developer in your project you can turn to? Get in touch with them! Let them know, you want to asynchronously fetch data for a more interactive user interface. Let them know that you need a way to find out if that user already exists using a REST API. Most full-stack solutions come with their own way of setting this up. Confluence for instance, has a well documented API. Even Wordpress offers a REST API to retrieve a list of users. All those APIs have in common, that they deliver JSON data.

Do you have trouble finding a way? get in touch with me.

If you have the freedom to create your own API, which is very rarely the case, I recommend to get started with ProcessWire for PHP (The test API is based on it!) or express for NodeJS servers.

If for the sake of the guide, you are out of luck and there is no API at your hands, make sure to follow through, using the testing API I created for you.

Building the asynchronous input validation mechanism

To start, we need a simple form with an input. No magic here. Right now, you do not have to care about what the action of the form is, since we just have to take care about the input. However, it is always recommended to use a form to group together any inputs used on your website.

<form method="POST" name="createuser">
	<label for="username">Username</label>
	<input required type="text" name="username">
	<input type="submit">

Let's evaluate our options for validating the input. We could use the input attribute pattern and create a regex list of all users that are not allowed to pick. However, this would require to place your entire userbase into one input field.

The performance-wise better option you have here, is to perform an AJAX call to the REST API, whenever a username is entered. Optionally, you can debounce the input to avoid flooding the server.

You can do that easily with an async function and fetch.

async function getSimilarUser(username) {
	let response = await fetch(`${username}`)
	return response.json()

When the input matches a name in the database, we set a pattern attribute Validation is then controlled via the constraint validation API.

The only two methods you need for validation

There are two functions that make custom validation and real-time checking possible:

element.setCustomValidity('User already exists')

This will let you set custom error messages for inputfields. Setting this to an empty string, makes the input valid. But only if no other constraints, like the required attribute, mark the input as invalid. By the way, you can style invalid inputs with the :invalid pseudo selector.

async function isUserValid(target) {
	let username = target.value
	let users = await getSimilarUser(username)
	if (users.length) {
		let existingUsername = users[0].name
		if (existingUsername == username) {
			target.setCustomValidity(`The user "${username}" already exists`)
			return false
	return true


A form reports any errors on submission. If we want to, you can manually report if any fields are invalid. This way, you know that the user is not allowed before you fill in any other inputfields.

usernameInput.addEventListener('input', async (e)=>{
	let isValid = await isUserValid(
	// optionally, we can re-use the return value if we need to.

That's it! You can combine your REST API with your forms. You can use your favorite HTTP client like axios, jquery or similar. However, by using the elegant async/await syntax together with fetch, you will only require just a few lines of code. Development comfort is important!

Do you need help with implementing this into your project?

Do you need inspiration how to solve those tricky issues? Issues that when resolved, would ease a lot of daily user interface interaction.

Get on my list! You are welcome to reply to any email with your important questions and get a hand-crafted reply with hands-on guides on how to solve your individual use-cases. Fill in your name below and get in touch today :) . It is about a nice conversation from developer to developer after all.

See other articles

Using noUiSlider as range slider to filter between two numbers

Do you know these fancy range sliders that can filter items with the blink of an eye? You move around a handle and get the desired results right away. In this article, you are going to learn how to implement a range slider to filter your data.

How to group together filter inputs to obtain and combine their values

When you want to filter a set of products or other lists, naturally you have to think how you could wire together the variety of filters. With this article, you will learn how you can retrieve the data and combine the values to filter your items in real-time.

Becoming a zombie

Do you find yourself losing track of time again? Without really knowing, a year passed. As day for day you continue your work, you had some random bursts of motivation, but you know that these kind of motivation spikes won't get you anywhere in the long term.

Updating the riot cheatsheet

It has been roughly a year since I created the riot cheatsheet. With the knowledge acquired by now, it is time to start from scratch. #news 4 #riot 12 #nodejs 2 #webpack 3

See all Articles