Please hold on for a moment while the content loads.
Please hold on for a moment while the content loads.
Language Learning Marketplace
Full-stack language-learning marketplace with live video tutoring via Whereby API, Socket.IO real-time messaging, multi-gateway payments (Stripe, Iyzipay, Paymax), and 7 user roles. Now evolving from a Next.js 14 monolith into a Laravel + TanStack Start architecture with AI-powered chat.
A full-stack language-learning marketplace connecting students with instructors for live video lessons, real-time messaging, and multi-currency payments. Built as a Next.js 14 monolith evolving into SpeakSail a Laravel 11 + TanStack Start (React 19) architecture with AI-powered chat.
User counts, revenue, and transaction volumes are confidential to the client. What I can share: the platform served real students and paying instructors across multiple countries, and user adoption and revenue grew steadily over the engagement. Active development is currently paused due to client-side budget and time constraints not product failure and may resume if conditions allow.
Original Enlightall v1.7.8 architecture: Next.js 14 monolith with Firebase, Socket.IO, and multi-gateway payments
Four-generation evolution: Pages Router monolith → App Router → Supabase headless → Laravel + TanStack Start
Whereby API integration for 1-on-1 and group tutoring sessions. Teachers set available time slots, students book appointments, and sessions are tracked with start/end times and feedback from both sides.
Socket.IO for in-app conversations with room-based message delivery. Firebase Cloud Messaging for push notifications when users are offline. Dual notification system ensures messages always reach users.
Stripe for USD payments (primary), Iyzipay for Turkish Lira, Paymax as alternative. Full payment lifecycle: checkout → session → status tracking → order history. Coupon system for teacher and platform discounts.
Firebase Auth for identity (email/password + Google OAuth), JWT (jose) for API authorization. 7 roles: USER, STUDENT, INSTRUCTOR, MENTOR, ADMIN, SUPER_ADMIN, TESTER. Client-side route guards with PrivateRoute/PublicRoute.
Full internationalization support for English and Turkish. Locale-aware routing, translated UI strings, and region-specific payment methods (Stripe for USD, Iyzipay for TRY).
Full course lifecycle: instructor creates → admin reviews → published. Modules, lessons with video content, quizzes, and course completion certificates generated via jsPDF.
Firebase handles identity management (email/password, Google OAuth, email verification) with minimal setup. But Firebase tokens have limited server-side control and can't be used as API authorization tokens.
Firebase Auth on the client-side for identity. On login, Firebase user data is POSTed to /api/token which upserts the user in MongoDB and issues a JWT (jose, HS256, 1-hour expiry). JWT is stored in HTTP-only cookie and injected into every API request via RTK Query interceptor.
// Firebase Auth for identity + JWT for API authorization.
// JWT issued after Firebase login, stored in HTTP-only cookie.
export async function POST(req: Request) {
const { firebaseToken } = await req.json()
const decoded = await admin.auth().verifyIdToken(firebaseToken)
const user = await db.user.upsert({
where: { firebaseUid: decoded.uid },
update: { email: decoded.email!, lastLogin: new Date() },
create: { firebaseUid: decoded.uid, email: decoded.email!, role: "student" },
})
const jwt = await new jose.SignJWT({ sub: user.id, role: user.role })
.setProtectedHeader({ alg: "HS256" })
.setExpirationTime("1h")
.sign(secret)
return new Response(JSON.stringify({ user }), {
headers: {
"Set-Cookie": cookie.serialize("token", jwt, {
httpOnly: true, secure: true, sameSite: "lax", path: "/",
}),
},
})
}In-app messaging needs real-time delivery when users are online. But push notifications are needed for offline users and background scenarios. Neither system alone covers all cases.
Socket.IO handles in-app conversations with room-based delivery (joinRoom/leaveRoom). Firebase Cloud Messaging handles push notifications with a service worker for background delivery. Notifications are saved to both MongoDB and Firebase Firestore. Preact Signals provide reactive conversation state updates.
A small team (1-2 engineers) building a marketplace with courses, bookings, messaging, payments, and dashboards. Microservices would be over-engineering for this team size.
Single Next.js 14 app with Pages Router API routes. Business logic in server/Services/ (29 controllers/services). Mongoose ODM for MongoDB with 27 models. next-connect for Express-like middleware chains. Deployed via PM2 with Firebase Hosting as alternative.
The platform serves students in different regions: USD for international students (Stripe) and TRY for Turkish students (Iyzipay). A single payment gateway can't handle both currencies efficiently.
Stripe for USD payments with checkout sessions and webhook handling. Iyzipay for TRY payments with 3D secure support. Paymax as alternative gateway. Payment controller routes to the correct gateway based on currency. Separate live/dev keys based on NODE_ENV.
Revenue and user-count figures are confidential, so the numbers below reflect engineering scope rather than business metrics.
Single deployment, shared types, simpler debugging. One codebase to maintain for a small team.
90+ API routes in one app is approaching the limit. Scaling individual features (e.g., messaging) independently is not possible.
Firebase handles identity (Google OAuth, email verification) with minimal setup. JWT gives server-side control over API auth.
Two auth systems to maintain. Edge cases around Firebase token expiry vs JWT expiry. Token refresh logic duplicated.
Covers online (Socket.IO) and offline (FCM) scenarios. Notifications reach users regardless of app state.
Two notification infrastructures to manage. Firestore + MongoDB double-storage. Socket.IO server needs scaling consideration.
Schema flexibility for rapid iteration. Mongoose ODM with 27 models covers the domain well. No migration complexity.
No relational joins. Cross-document queries are limited. Referential integrity enforced at application level only.
Video session failure (Whereby API)
Students and teachers can't join scheduled sessions. Booking is wasted.
Retry on Whereby API failure. Fallback to manual Google Meet/Zoom links stored in teacher profile. Session feedback captures failure for reconciliation.
Payment processing error (Stripe/Iyzipay)
Student charged but enrollment not created. Revenue collected without service delivery.
Stripe webhooks for async status confirmation. Payment model tracks status (OK/FAILED/PENDING). Admin dashboard for manual payment reconciliation.
Socket.IO disconnect
Real-time messages lost during connection drop. Users see stale conversation state.
Room-based reconnection with automatic room rejoin. Messages persisted to MongoDB before broadcast. Preact Signals for reactive state recovery on reconnect.
Multi-timezone booking conflict
Double-booked instructor. Two students scheduled for the same time slot.
Server-side availability validation on booking creation. Available model tracks booked slots with year/week/day/time granularity. Booking model enforces status transitions.
Original production monolith. Next.js 14 Pages Router with MongoDB, Firebase Auth, Socket.IO, Stripe/Iyzipay, Redux Toolkit. 90+ API routes, 27 models.
Ground-up rewrite to Next.js App Router with better separation of concerns (server/ vs client/). Same MongoDB backend, same feature set, cleaner architecture.
Headless architecture pivot: Strapi CMS (PostgreSQL) for content + Supabase for auth + NextUI frontend. Decouples content management from application logic.
Complete platform rebuild: Laravel 11 backend with Filament admin panel + TanStack Start (React 19) frontend. AI-powered chat via Anthropic SDK. Proper API/SPA separation. On hold while client budget and priorities allow resumption.
90+ API routes in one app is manageable for a small team but approaching the limit. Scaling individual features independently requires a rewrite.
Firebase + JWT works but creates edge cases around token expiry and refresh. The next iteration should pick one auth system and commit to it.
Socket.IO for messaging + FCM for push covers the use case. Event-driven infrastructure would be over-engineering for this team size and product stage.
The evolution from Pages Router → App Router → Supabase → Laravel shows that rewriting is justified when the architecture no longer supports the product direction.
This is a production system that served real students and paying customers. Additional context: