JavaScript Integration
Learn how to integrate PureSignup into your frontend JavaScript application for real-time email validation.
⚠️ Security Note: For production applications, always validate on the server-side as well. Frontend validation can be bypassed. This guide shows client-side validation for UX improvements only.
Basic Implementation
Here's a simple implementation that checks emails as users type into your signup form:
basic-validation.js
// Note: In production, call from your backend to keep API keys secure
const PureSignup_API_KEY = 'YOUR_API_KEY'; // DO NOT expose this in production
async function checkEmail(email) {
try {
const response = await fetch(
'https://puresignup.com/api/v1/check?q=' + encodeURIComponent(email),
{
headers: {
'Authorization': `Bearer ${PureSignup_API_KEY}`
}
}
);
if (!response.ok) {
throw new Error('API request failed');
}
const data = await response.json();
return data;
} catch (error) {
console.error('Email check failed:', error);
return null; // Fail open - allow signup on error
}
}
// Usage example
const emailInput = document.getElementById('email');
const errorMessage = document.getElementById('email-error');
emailInput.addEventListener('blur', async () => {
const email = emailInput.value;
if (!email) return;
const result = await checkEmail(email);
if (result?.success && result.data.risk_level === 'high') {
errorMessage.textContent = 'Please use a permanent email address';
errorMessage.style.display = 'block';
emailInput.classList.add('error');
} else {
errorMessage.style.display = 'none';
emailInput.classList.remove('error');
}
});Production-Ready Setup
In production, proxy requests through your backend to keep your API key secure:
Frontend Code
frontend.js
async function checkEmail(email) {
try {
// Call your backend endpoint instead of PureSignup directly
const response = await fetch('/api/check-email', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email })
});
if (!response.ok) {
return null; // Fail open
}
const data = await response.json();
return data;
} catch (error) {
console.error('Email check failed:', error);
return null;
}
}
// Form submission handler
document.getElementById('signupForm').addEventListener('submit', async (e) => {
e.preventDefault();
const email = document.getElementById('email').value;
const result = await checkEmail(email);
if (result?.risk_level === 'high') {
alert('Disposable email addresses are not allowed. Please use a permanent email.');
return;
}
// Continue with signup
e.target.submit();
});Backend Proxy (Node.js Example)
backend-proxy.js
// Express.js endpoint
app.post('/api/check-email', async (req, res) => {
const { email } = req.body;
if (!email) {
return res.status(400).json({ error: 'Email required' });
}
try {
const response = await fetch(
`https://puresignup.com/api/v1/check?q=${encodeURIComponent(email)}`,
{
headers: {
'Authorization': `Bearer ${process.env.PureSignup_API_KEY}`
}
}
);
const data = await response.json();
// Only send back what the frontend needs
res.json({
risk_level: data.data?.risk_level || 'low'
});
} catch (error) {
console.error('PureSignup API error:', error);
// Fail open - return low risk on error
res.json({ risk_level: 'low' });
}
});Real-Time Validation with Debouncing
Check emails as users type, but use debouncing to avoid excessive API calls:
debounced-validation.js
// Debounce helper
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func(...args), wait);
};
}
// Check email with visual feedback
async function validateEmailField(emailInput, errorElement) {
const email = emailInput.value;
if (!email || !email.includes('@')) {
errorElement.style.display = 'none';
return;
}
// Show loading state
emailInput.classList.add('validating');
const result = await checkEmail(email);
// Remove loading state
emailInput.classList.remove('validating');
if (result?.risk_level === 'high') {
errorElement.textContent = '⚠️ Disposable email detected. Please use a permanent address.';
errorElement.style.display = 'block';
errorElement.className = 'error-message error';
} else if (result?.risk_level === 'low') {
errorElement.textContent = '✓ Email looks good';
errorElement.style.display = 'block';
errorElement.className = 'error-message success';
}
}
// Setup with debouncing (wait 500ms after user stops typing)
const emailInput = document.getElementById('email');
const errorElement = document.getElementById('email-feedback');
const debouncedValidate = debounce(() => {
validateEmailField(emailInput, errorElement);
}, 500);
emailInput.addEventListener('input', debouncedValidate);React Integration
Here's how to integrate with React using hooks:
EmailInput.jsx
import { useState, useEffect } from 'react';
function EmailInput({ onValidation }) {
const [email, setEmail] = useState('');
const [status, setStatus] = useState(null); // null, 'checking', 'valid', 'invalid'
const [message, setMessage] = useState('');
useEffect(() => {
if (!email || !email.includes('@')) {
setStatus(null);
return;
}
const timeoutId = setTimeout(async () => {
setStatus('checking');
try {
const response = await fetch('/api/check-email', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email })
});
const data = await response.json();
if (data.risk_level === 'high') {
setStatus('invalid');
setMessage('Disposable email detected');
onValidation(false);
} else {
setStatus('valid');
setMessage('Email looks good');
onValidation(true);
}
} catch (error) {
setStatus('valid'); // Fail open
onValidation(true);
}
}, 500); // Debounce 500ms
return () => clearTimeout(timeoutId);
}, [email, onValidation]);
return (
<div className="form-field">
<label htmlFor="email">Email Address</label>
<input
type="email"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className={`input ${status === 'invalid' ? 'error' : ''}`}
/>
{status === 'checking' && (
<span className="help-text">Checking...</span>
)}
{status === 'invalid' && (
<span className="error-text">{message}</span>
)}
{status === 'valid' && (
<span className="success-text">{message}</span>
)}
</div>
);
}
export default EmailInput;Vue.js Integration
EmailInput.vue
<template>
<div class="form-field">
<label for="email">Email Address</label>
<input
type="email"
id="email"
v-model="email"
@input="debouncedCheck"
:class="{ error: isInvalid }"
/>
<span v-if="checking" class="help-text">Checking...</span>
<span v-if="isInvalid" class="error-text">{{ message }}</span>
<span v-if="isValid" class="success-text">{{ message }}</span>
</div>
</template>
<script>
import { ref, watch } from 'vue';
export default {
name: 'EmailInput',
emits: ['validation'],
setup(props, { emit }) {
const email = ref('');
const checking = ref(false);
const isValid = ref(false);
const isInvalid = ref(false);
const message = ref('');
let timeoutId = null;
const checkEmail = async () => {
if (!email.value || !email.value.includes('@')) {
isValid.value = false;
isInvalid.value = false;
return;
}
checking.value = true;
try {
const response = await fetch('/api/check-email', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: email.value })
});
const data = await response.json();
if (data.risk_level === 'high') {
isInvalid.value = true;
isValid.value = false;
message.value = 'Disposable email detected';
emit('validation', false);
} else {
isValid.value = true;
isInvalid.value = false;
message.value = 'Email looks good';
emit('validation', true);
}
} catch (error) {
isValid.value = true;
isInvalid.value = false;
emit('validation', true); // Fail open
} finally {
checking.value = false;
}
};
const debouncedCheck = () => {
clearTimeout(timeoutId);
timeoutId = setTimeout(checkEmail, 500);
};
return {
email,
checking,
isValid,
isInvalid,
message,
debouncedCheck
};
}
};
</script>Best Practices
- Always validate server-side: Client-side validation is for UX only
- Use debouncing: Wait 300-500ms after user stops typing before checking
- Fail open: If API is unavailable, allow signup to proceed
- Provide feedback: Show loading states and clear error messages
- Cache results: Store results in memory to avoid duplicate checks
- Proxy through backend: Never expose API keys in frontend code