I Built an AI-Powered Education Platform as a Solo Founder - Here's My Full Stack A solo developer built TopoForest, an AI-driven business education platform for indie builders, using Next.js, Koa, PostgreSQL, and Prisma. The platform integrates WeChat OAuth and Aliyun services to operate within China's internet ecosystem. The developer chose Koa over Next.js API routes for handling WeChat's complex authentication flows. After 6 months of nights and weekends, I shipped TopoForest https://topforest.cn - an AI-driven business education platform for indie builders going global. No co-founders, no VC funding, no team. Just me, a keyboard, and an unhealthy amount of coffee. If you're a solo dev wondering whether you can actually pull off a SaaS by yourself, the answer is yes - but you'll need to be surgical about your tech choices. Here's the full stack I used, the trade-offs I made, and what I'd do differently. TopoForest is a learning platform that teaches OPC builders solo entrepreneurs running a one-person business how to leverage AI in their daily operations. It has video courses, user accounts, phone verification, and WeChat integration. All of it needs to work in China, which means dealing with the Chinese internet ecosystem - a whole different beast. Here's the architecture at a glance: ???????????????????? ???????????????? ? Next.js + Koa ? ??? ? PostgreSQL ? ? Vercel ? ? Prisma ? ???????????????????? ???????????????? ? ? ???????????????????????????????? ? Aliyun OSS Video/Image ? ? WeChat OAuth + Aliyun SMS ? ???????????????????????????????? Simple enough, right? Let me walk through each piece. I picked Next.js for two reasons: it's boring technology in the best way , and the App Router handles both frontend and API routes in one codebase. I started with Pages Router, but App Router won me over with: route.ts files for lightweight API endpoints that don't need the full backend treatment.App Router is not all sunshine. The caching behavior is confusing at first - I spent a full evening debugging why a page kept showing stale data. Turns out I needed to call revalidatePath in the right place. Once you internalize the mental model, it's fine, but the learning curve is real. js // Example: revalidating after a course progress update import { revalidatePath } from 'next/cache'; export async function updateProgress courseId: string, data: ProgressData { await prisma.courseProgress.upsert { where: { userId courseId: { userId: data.userId, courseId } }, update: data, create: { ...data, courseId }, } ; revalidatePath /courses/${courseId} ; } Verdict : Worth it. The DX is great once you're past the initial hump. "Why a separate backend?" - this is the question I get asked most. The answer: WeChat OAuth and ecosystem integration . Next.js API routes are fantastic for simple CRUD, but when you're dealing with WeChat's callback mechanism, token exchange flows, and the need to track redirect statistics click-through rates, visit sources, user agents , a dedicated backend framework keeps your codebase organized. I chose Koa for the API layer while keeping the frontend on Next.js - both running on Vercel. Honest answer: I just like Koa better. The middleware is cleaner - no callback hell, proper async/await from the ground up. Express middleware often feels like you're fighting the framework. Koa gets out of your way. // Koa middleware for tracking redirect visits async function trackRedirect ctx: Context, next: Next { const { source, menuKey, openid } = ctx.query; await prisma.trackVisit.create { data: { source: source as string, menuKey: menuKey as string, openid: openid as string, ip: ctx.ip, userAgent: ctx.headers 'user-agent' , referer: ctx.headers.referer, }, } ; await next ; } I initially tried using koa-connect to wrap Express middleware in Koa. Don't do this. It caused mysterious context leaks that took me a full weekend to track down. Write native Koa middleware. Yes, it's more work upfront, but you'll thank yourself later. Verdict : If you don't need WeChat-level ecosystem integration, Next.js API routes alone would be fine. But for my use case, Koa was the right call. I've used raw SQL, Sequelize, TypeORM, and Drizzle. Prisma is the first ORM I actively enjoy using. prisma db push , and instantly get fully typed queries. My IDE autocompletes every field. I haven't written a misspelled column name in months. schema.prisma file is a single source of truth. When I onboard a contributor someday , they can read one file and understand the entire data model.One rule I set early: never physically delete data . Every table has an enabled boolean field. Deletion is just setting enabled = false . model Course { id Int @id @default autoincrement title String enabled Boolean @default true createdAt DateTime @default now updatedAt DateTime @updatedAt } This means every query needs a where: { enabled: true } filter. It's a minor annoyance, but it's saved me multiple times when a user accidentally deleted something and I could just flip a flag back. db push instead of migrate ? I use prisma db push to sync the schema. No migration files, no versioning headaches. For a solo developer shipping fast, migration files are overhead I don't need. If I ever bring on a team, I'll switch to proper migrations, but for now, db push keeps me fast. prisma db push sync schema, no migration files Verdict : Prisma is a joy. The autocomplete alone is worth the price of admission which is free, so that's a pretty good deal . WeChat is the gateway to the Chinese internet. If your product targets Chinese users, you can't skip WeChat integration. But it's also the most painful part of the stack. Users click a WeChat menu link ? they hit my backend ? I redirect to WeChat's OAuth page ? WeChat sends back a code ? I exchange it for an openid ? I redirect the user to the actual content page. All of this happens silently snsapi base scope , so the user doesn't see an authorization popup. It's fast, it's seamless, and it took me a week to get right. topforest.cn . state=home , state=courses , or state=account to track which menu item the user clicked. This feeds into my analytics.Every WeChat menu click goes through a tracking endpoint. I log the source, menu key, and openid, then issue a 302 redirect: // Simplified redirect handler router.get '/api/v1/track/redirect', trackRedirect, async ctx = { const { state } = ctx.query; const targetUrl = getTargetUrl state as string ; ctx.status = 302; ctx.redirect targetUrl ; } ; Verdict : The hardest part of the stack, but also the most valuable. WeChat integration is a moat - competitors can't just copy it. I host video courses on Aliyun OSS Object Storage Service . It's cheap, fast, and integrates well with the rest of the Alibaba Cloud ecosystem. Videos are private by default. When a user requests a course video, my backend generates a signed URL with a time-limited expiration. This prevents hotlinking and keeps my bandwidth costs under control. python import OSS from 'ali-oss'; const client = new OSS { region: 'oss-cn-hangzhou', accessKeyId: process.env.OSS ACCESS KEY ID , accessKeySecret: process.env.OSS ACCESS KEY SECRET , bucket: process.env.OSS BUCKET , } ; async function getSignedUrl objectKey: string : Promise