At Tutorial Haven, our mission has always been to bridge the gap between education and technology. What began as a multi-tiered platform for schools, tutors, and students has evolved through real-world feedback and strategic refinement. Today, I'm pulling back the curtain on how our registration system transformed—and what the journey taught me about building for scale.
🎯 The Original Vision: Building for Everyone
When I first built Tutorial Haven, I didn't design it as "just another student app." I envisioned it being adopted by schools and tutorial centers to enhance teaching and learning workflows. So registration wasn't simple. It was split into three distinct flows—each with its own logic, validation, and access controls.
Here's a look at how our registration system was initially built: I designed the platform with scalability in mind, envisioning adoption by schools, tutorial centers, tutors, and students alike. The registration was structured into three distinct flows:
🏫 1. School/Institution Registration
- First name, last name, username, password, email
- Tutorial center name, address, discipline (e.g., JAMB, WAEC, JUPEB)
- Profile picture (optional)
Process:
- Basic validation (unique username, email, and center name)
- Profile picture uploaded via
SupabaseStorage()with URL saved to the tutorial center model - Creation of both the user and tutorial center objects
- Redirect to the user's profile page upon success
👨🏫 2. Tutor Registration
- Basic user info + selection of institution and courses (16 available)
- Profile picture
Process:
- Same validation and image upload flow
- Key Feature: Tutors were linked to their institution, and the center owner received an email notification to approve or reject the registration
- Tutors had no platform access until approved—ensuring quality and oversight
👩🎓 3. Student Registration
- Department (Science, Art, Commercial), institution, subjects
- Basic user info + phone number (for platform growth insights)
Process:
- Validation, profile upload, user + student model creation
- Email sent to both the institution owner and student
- Access granted only after approval
Each flow included validation checks, profile image uploads, email notifications, and role-based access control. It was comprehensive. It was scalable. And honestly? It was probably too much.
🔄 The Evolution: What Changed and Why
Over time, I removed the owner and tutor registration flows (the reasoning will be shared soon), but the architecture taught me something important:
💡 The Lesson
Designing for scale early forces you to think beyond "just making it work." But there's a fine line between scalable architecture and over-engineering for problems you don't yet have.
Here's what prompted the changes:
- Feedback User feedback: Institutions found the approval workflow cumbersome and wanted faster onboarding
- Data Usage patterns: Most tutors were registering independently before their institution even joined
- Complexity Technical debt: Maintaining three parallel registration flows with subtle differences created unnecessary complexity
- Product Pivot: Our product strategy shifted toward a more student-first approach
🏗️ Technical Deep Dive: What the Code Looked Like
For the curious developers, here's a simplified version of how we handled the multi-flow registration with Supabase:
// Multi-flow registration handler (simplified)
async function handleRegistration(userData, userType) {
try {
// 1. Basic validation
await validateUser(userData.email, userData.username);
// 2. Upload profile picture if exists
let profileUrl = null;
if (userData.profileImage) {
const { data, error } = await supabase.storage
.from('profiles')
.upload(`${userType}/${Date.now()}.jpg`, userData.profileImage);
profileUrl = data?.publicUrl;
}
// 3. Create auth user
const { data: authUser, error: authError } = await supabase.auth.signUp({
email: userData.email,
password: userData.password,
});
// 4. Create role-specific profile
const profileData = {
user_id: authUser.user.id,
first_name: userData.firstName,
last_name: userData.lastName,
profile_url: profileUrl,
...getRoleSpecificFields(userData, userType)
};
// 5. Insert into appropriate table
const { error: profileError } = await supabase
.from(`${userType}_profiles`)
.insert(profileData);
// 6. Send notifications based on role
await sendRoleBasedNotifications(userType, profileData);
return { success: true };
} catch (error) {
console.error('Registration failed:', error);
return { success: false, error };
}
}
📊 What I'd Do Differently Today
If I were rebuilding this system from scratch with today's knowledge:
- Start simpler: One registration flow with role assignment after email verification
- Progressive onboarding: Collect minimal info first, then gather role-specific details after initial value is delivered
- Feature flags: Build the architecture for multiple roles, but release them gradually based on demand
- Analytics from day one: Track where users drop off in each flow to identify friction points
⚡ Pro Tip for Fellow Builders
Your first architecture should solve today's problems elegantly while leaving hooks for tomorrow's complexity—not implementing it all upfront. Ask yourself: "What's the simplest thing that could possibly work for our current users?"
🔮 What's Next for Tutorial Haven
The registration flow continues to evolve. We're currently experimenting with:
- Social login integration (Google, LinkedIn for institutions)
- Bulk upload for schools importing entire student rosters
- Simplified approval workflows with optional oversight
I built all this quietly. Now I'm finally documenting it—one feature at a time.
📝 Key Takeaway
The best architectures aren't designed perfectly from the start—they evolve through real usage, honest feedback, and the courage to simplify. Tutorial Haven's registration flow taught me that scalability isn't just about handling more users; it's about adapting to what they actually need.