Skip to content

User Registration Guide ​

Learn how to implement user registration in your application using Flowless.


Overview ​

User registration is the process of creating a new user account. Flowless provides a simple API endpoint that handles:

  • Email/username validation
  • Password hashing (Argon2id)
  • Session creation
  • Email verification (optional)
  • User profile creation

Basic Registration Flow ​


Implementation ​

Frontend (React) ​

typescript
import { useState } from 'react';

interface RegisterFormData {
  email: string;
  password: string;
  name: string;
  last_name: string;
}

function RegisterForm() {
  const [formData, setFormData] = useState<RegisterFormData>({
    email: '',
    password: '',
    name: '',
    last_name: '',
  });
  const [errors, setErrors] = useState<string[]>([]);
  const [loading, setLoading] = useState(false);

  const validateForm = (): boolean => {
    const newErrors: string[] = [];

    // Email validation
    if (!formData.email) {
      newErrors.push('Email is required');
    } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
      newErrors.push('Invalid email format');
    }

    // Password validation
    if (!formData.password) {
      newErrors.push('Password is required');
    } else if (formData.password.length < 8) {
      newErrors.push('Password must be at least 8 characters');
    } else if (!/[A-Z]/.test(formData.password)) {
      newErrors.push('Password must contain uppercase letter');
    } else if (!/[a-z]/.test(formData.password)) {
      newErrors.push('Password must contain lowercase letter');
    } else if (!/[0-9]/.test(formData.password)) {
      newErrors.push('Password must contain number');
    } else if (!/[!@#$%^&*]/.test(formData.password)) {
      newErrors.push('Password must contain special character');
    }

    // Name validation
    if (!formData.name) {
      newErrors.push('First name is required');
    }

    setErrors(newErrors);
    return newErrors.length === 0;
  };

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();

    if (!validateForm()) {
      return;
    }

    setLoading(true);
    setErrors([]);

    try {
      const response = await fetch(
        `${import.meta.env.VITE_FLOWLESS_URL}/auth/register`,
        {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            email: formData.email.toLowerCase(),
            password: formData.password,
            name: formData.name,
            last_name: formData.last_name,
          }),
        }
      );

      const data = await response.json();

      if (!data.success) {
        setErrors([data.error]);
        return;
      }

      // Store session ID
      localStorage.setItem('session_id', data.data.session.session_id);
      localStorage.setItem('user_data', JSON.stringify(data.data.user));

      // Redirect to dashboard or show verification message
      if (data.data.user.is_verified) {
        window.location.href = '/dashboard';
      } else {
        window.location.href = '/verify-email';
      }
    } catch (error) {
      setErrors(['Registration failed. Please try again.']);
    } finally {
      setLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <h2>Create Account</h2>

      {errors.length > 0 && (
        <div className="errors">
          {errors.map((error, index) => (
            <p key={index}>{error}</p>
          ))}
        </div>
      )}

      <input
        type="email"
        placeholder="Email"
        value={formData.email}
        onChange={(e) => setFormData({ ...formData, email: e.target.value })}
        required
      />

      <input
        type="password"
        placeholder="Password"
        value={formData.password}
        onChange={(e) => setFormData({ ...formData, password: e.target.value })}
        required
      />

      <input
        type="text"
        placeholder="First Name"
        value={formData.name}
        onChange={(e) => setFormData({ ...formData, name: e.target.value })}
        required
      />

      <input
        type="text"
        placeholder="Last Name"
        value={formData.last_name}
        onChange={(e) => setFormData({ ...formData, last_name: e.target.value })}
      />

      <button type="submit" disabled={loading}>
        {loading ? 'Creating Account...' : 'Sign Up'}
      </button>
    </form>
  );
}

export default RegisterForm;

API Reference ​

Endpoint ​

http
POST /auth/register

Request Body ​

json
{
  "email": "user@example.com",
  "password": "SecurePassword123!",
  "name": "John",
  "last_name": "Doe",
  "username": "johndoe",
  "phone": "+1234567890"
}

Response ​

json
{
  "success": true,
  "data": {
    "user": {
      "id": "usr_1234567890",
      "email": "user@example.com",
      "name": "John",
      "last_name": "Doe",
      "is_verified": false,
      "created_at": "2025-12-07T10:00:00Z"
    },
    "session": {
      "session_id": "ses_abcdefghijk",
      "expires_at": "2025-12-14T10:00:00Z"
    }
  }
}

Best Practices ​

1. Validate on Frontend ​

Always validate user input before sending to the API:

typescript
function validateEmail(email: string): boolean {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

function validatePassword(password: string): boolean {
  return (
    password.length >= 8 &&
    /[A-Z]/.test(password) &&
    /[a-z]/.test(password) &&
    /[0-9]/.test(password) &&
    /[!@#$%^&*]/.test(password)
  );
}

2. Normalize Email ​

Always convert email to lowercase:

typescript
const email = userInput.toLowerCase().trim();

3. Show Password Strength ​

typescript
function getPasswordStrength(password: string): string {
  let strength = 0;
  
  if (password.length >= 8) strength++;
  if (password.length >= 12) strength++;
  if (/[A-Z]/.test(password)) strength++;
  if (/[a-z]/.test(password)) strength++;
  if (/[0-9]/.test(password)) strength++;
  if (/[!@#$%^&*]/.test(password)) strength++;
  
  if (strength <= 2) return 'Weak';
  if (strength <= 4) return 'Medium';
  return 'Strong';
}

4. Handle Errors Gracefully ​

typescript
try {
  const response = await register(formData);
} catch (error) {
  if (error.message === 'Email already registered') {
    setErrors(['This email is already in use. Try logging in instead.']);
  } else {
    setErrors(['Registration failed. Please try again.']);
  }
}

Next Steps ​