Now that you know the building blocks, it is time to apply them to the patterns you will encounter constantly in real projects. This lesson walks through the most common validation use cases, explains the reasoning behind each pattern, and gives you honest guidance on when to keep things simple versus when to reach for a library.
Email validation
Simple approach
The technically correct regexWhat is regex?A compact pattern language for matching, searching, and replacing text, built into nearly every programming language and code editor. for email addresses according to RFC 5321 is over a thousand characters long and still does not cover every edge case. For client-side validation, this simple pattern covers the overwhelming majority of real-world email addresses.
const simpleEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
simpleEmail.test('user@example.com'); // true
simpleEmail.test('name+tag@domain.co.uk'); // true
simpleEmail.test('@example.com'); // false - nothing before @
simpleEmail.test('user@'); // false - nothing after @Breaking it down: [^\s@]+ means "one or more characters that are not whitespace or @." That covers the local part, the domain, and the TLDWhat is tld?Top-Level Domain - the last segment of a domain name (.com, .org, .fr), indicating the domain's type or country. without being so strict that you accidentally reject valid addresses.
Stricter version
If you need to be more explicit about which characters are allowed:
const strictEmail = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;URL validation
Basic URL check
// Requires http:// or https://
const basicUrl = /^https?:\/\/[^\s/$.?#].[^\s]*$/i;
basicUrl.test('https://example.com'); // true
basicUrl.test('http://site.org/path?q=1'); // true
basicUrl.test('ftp://files.example.com'); // false - not http/httpsDomain only (no protocolWhat is protocol?An agreed-upon set of rules for how two systems communicate, defining the format of messages and the expected sequence of exchanges.)
const domainOnly = /^(?:[\w-]+\.)+[\w-]{2,}$/;
domainOnly.test('example.com'); // true
domainOnly.test('sub.domain.co.uk'); // true
domainOnly.test('localhost'); // false - no dotURL constructor instead of regex. new URL(input) throws if the URL is malformed, wrap it in a try/catch and you have a reliable validator with zero regex complexity.Phone numbers
Phone numbers are notoriously inconsistent. Users enter them with dashes, dots, spaces, parentheses, country codes, and no separators at all. The practical approach is to accept the formats common in your user base and normalise them server-side.
// US phone - accepts common formats
const usPhone = /^(\+?1[-.\s]?)?\(?([0-9]{3})\)?[-.\s]?([0-9]{3})[-.\s]?([0-9]{4})$/;
// All of these match:
usPhone.test('555-123-4567'); // true
usPhone.test('(555) 123-4567'); // true
usPhone.test('555.123.4567'); // true
usPhone.test('5551234567'); // true
usPhone.test('+1 555-123-4567'); // true
// International format (E.164)
const intlPhone = /^\+[1-9]\d{1,14}$/;
intlPhone.test('+14155552671'); // true
intlPhone.test('+33123456789'); // truePassword validation
Using lookaheads
A lookahead ((?=...)) checks that a condition is satisfied at the current position without actually consuming any characters. This lets you write multiple independent rules into a single pattern.
// Requires: 8+ characters, one lowercase, one uppercase, one digit
const basicPassword = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/;
basicPassword.test('Hello123'); // true
basicPassword.test('hello123'); // false - no uppercase
basicPassword.test('HELLO123'); // false - no lowercase
basicPassword.test('Hello12'); // false - only 7 characters
// Stronger: also requires a special character and 12+ characters
const strongPassword = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&]).{12,}$/;Each (?=.*[x]) says: "somewhere ahead in this string, there must be a character matching [x]." The .* before it means "at any position." The final .{8,} is what actually matches and consumes the full string.
Date and time patterns
| Format | Pattern | Example match |
|---|---|---|
| ISO 8601 (YYYY-MM-DD) | /^\d{4}-(0[1-9]\|1[0-2])-(0[1-9]\|[12]\d\|3[01])$/ | 2024-03-15 |
| US date (MM/DD/YYYY) | /^(0[1-9]\|1[0-2])\/(0[1-9]\|[12]\d\|3[01])\/\d{4}$/ | 03/15/2024 |
| 24-hour time (HH:MM) | /^([01]\d\|2[0-3]):([0-5]\d)$/ | 23:59 |
| 12-hour time with AM/PM | /^(0?[1-9]\|1[0-2]):([0-5]\d)\s?(AM\|PM)$/i | 11:30 PM |
ISO 8601What is iso 8601?An international date and time format standard (e.g., 2024-03-15T10:30:00Z) used in APIs for unambiguous timestamps. date (YYYY-MM-DD)
const isoDate = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/;
isoDate.test('2024-03-15'); // true
isoDate.test('2024-13-15'); // false - month 13 does not exist
isoDate.test('2024-02-30'); // false - no 30th of FebruaryTime formats
// 24-hour: HH:MM
const time24 = /^([01]\d|2[0-3]):([0-5]\d)$/;
time24.test('23:59'); // true
time24.test('24:00'); // false
// 12-hour with AM/PM
const time12 = /^(0?[1-9]|1[0-2]):([0-5]\d)\s?(AM|PM)$/i;
time12.test('11:30 PM'); // true
time12.test('13:00 AM'); // falseIP addressWhat is ip address?A numerical label (e.g., 172.217.14.206) that identifies a device on a network - DNS translates domain names into IP addresses. validation
// IPv4 - validates each octet is 0–255
const ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
ipv4.test('192.168.1.1'); // true
ipv4.test('255.255.255.0'); // true
ipv4.test('256.1.1.1'); // false - 256 is out of rangeThe alternation inside the group, 25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?, handles three ranges: 250–255, 200–249, and 0–199.
Quick reference
| Use case | Pattern | Notes |
|---|---|---|
| Simple email | /^[^\s@]+@[^\s@]+\.[^\s@]+$/ | Good enough for client-side |
| US phone | /^\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$/ | Flexible format |
| ISO date | /^\d{4}-(0[1-9]\|1[0-2])-(0[1-9]\|[12]\d\|3[01])$/ | Month/day bounds |
| Hex color | /^#[a-fA-F0-9]{6}$/ | Standard 6-digit hex |
| URL slug | /^[a-z0-9]+(?:-[a-z0-9]+)*$/ | Lowercase, hyphen-separated |
| Password (basic) | /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/ | Lookaheads for rules |
| IPv4 address | see above | Validates octet range |
// Comprehensive validation library
const validators = {
// Emails
email: (email) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email),
emailStrict: (email) => /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(email),
// URLs
url: (url) => /^https?:\/\/[^\s/$.?#].[^\s]*$/i.test(url),
domain: (domain) => /^(?:[\w-]+\.)+[\w-]{2,}$/.test(domain),
// Phone (US and International)
phoneUS: (phone) => /^\d{3}[-.]?\d{3}[-.]?\d{4}$/.test(phone.replace(/\D/g, '').replace(/^1/, '')),
phoneIntl: (phone) => /^\+[1-9]\d{1,14}$/.test(phone.replace(/\s+/g, '')),
// Passwords
passwordBasic: (pass) => /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/.test(pass),
passwordStrong: (pass) => /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&]).{12,}$/.test(pass),
// Credit Card (format only)
creditCardFormat: (card) => /^\d{13,19}$/.test(card.replace(/\D/g, '')),
// Dates
dateISO: (date) => /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/.test(date),
dateUS: (date) => /^(0[1-9]|1[0-2])\/(0[1-9]|[12]\d|3[01])\/\d{4}$/.test(date),
// Network
ipv4: (ip) => /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(ip),
// User input
username: (username) => /^[a-zA-Z0-9][a-zA-Z0-9_-]{1,18}[a-zA-Z0-9]$/.test(username),
slug: (slug) => /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug),
// Colors
hexColor: (color) => /^#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/.test(color),
// Postal codes
zipUS: (zip) => /^\d{5}(-\d{4})?$/.test(zip)
};
// Test all validators
const testCases = {
email: ['test@example.com', 'invalid-email', '@example.com'],
url: ['https://google.com', 'http://test.org/path', 'not-a-url'],
phoneUS: ['555-123-4567', '5551234567', '123-45-6789'],
passwordBasic: ['Hello123', 'hello123', 'HELLO123'],
dateISO: ['2024-03-15', '2024-13-15', '2024-03-32'],
ipv4: ['192.168.1.1', '10.0.0.1', '256.1.1.1']
};
Object.entries(testCases).forEach(([validator, inputs]) => {
console.log(`\n${validator}:`);
inputs.forEach(input => {
const result = validators[validator](input);
console.log(` ${result ? '✅' : '❌'} ${input}`);
});
});