# I Built an AI-Powered Education Platform as a Solo Founder - Here's My Full Stack

> Source: <https://dev.to/jackhan312/i-built-an-ai-powered-education-platform-as-a-solo-founder-heres-my-full-stack-3557>
> Published: 2026-07-08 08:50:42+00:00

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<string> {
  return client.signatureUrl(objectKey, { expires: 3600 }); // 1 hour
}
```

When I first deployed, videos were returning 404 errors. I was confused for hours. Turns out I had forgotten to set the `OSS_ACCESS_KEY_ID`

, `OSS_ACCESS_KEY_SECRET`

, and `OSS_BUCKET`

environment variables. Without them, OSS was returning unsigned URLs - which means public access is denied. Now I have a startup check that validates all three exist before the server starts.

**Verdict**: Great value, but triple-check your env vars. The error messages are not helpful.

Phone verification is standard in China, not email. I use Aliyun SMS for sending verification codes.

You need to configure a template in the Aliyun SMS console, then reference it in your code:

``` python
import Dysmsapi from '@alicloud/dysmsapi20170525';

const client = new Dysmsapi({
  accessKeyId: process.env.ALIYUN_SMS_ACCESS_KEY_ID!,
  accessKeySecret: process.env.ALIYUN_SMS_ACCESS_KEY_SECRET!,
});

async function sendVerificationCode(phone: string, code: string) {
  await client.sendSms({
    phoneNumbers: phone,
    signName: process.env.ALIYUN_SMS_SIGN_NAME!,
    templateCode: process.env.ALIYUN_SMS_TEMPLATE_CODE!,
    templateParam: JSON.stringify({ code }),
  });
}
```

The template code and sign name must match exactly what you configured in the Aliyun console. If either is wrong, the API returns a cryptic "template not found" error. I wasted half a day on this.

**Verdict**: Works well once configured, but the setup is finicky. Double-check every string.

I deploy both the frontend and the Koa backend on Vercel. The frontend uses Vercel's native Next.js hosting, and the Koa API runs as a serverless function on the same Vercel project - no separate server, no dual-infrastructure headache.

`topforest.cn`

)`/api/v1`

`NEXT_PUBLIC_BACKEND_URL=https://topforest.cn`

in Vercel's environment variablesI don't want to think about infrastructure. Vercel handles SSL, CDN, and the deployment pipeline. I push to `main`

, and it's live in 2 minutes. For a solo developer, that's priceless.

Keeping everything under one roof means:

**Verdict**: If you're a solo dev, put everything on Vercel. You have better things to do than configure nginx.

No project is perfect. Here's what I'd change if I were starting over:

I shipped this as a solo founder. Here's what that looks like in practice:

Is it profitable yet? Not yet. But it's shipping, it's getting users, and I'm learning more than any tutorial could teach me.

If you're on the fence about building your own SaaS as a solo developer, here's my honest take:

**The tech stack is not the hard part.** Next.js, Prisma, PostgreSQL - these are all well-documented, battle-tested tools. The hard part is everything else: figuring out what to build, staying motivated when nobody's using it, and shipping through the discomfort of "this isn't ready yet."

But if you can push through that, the tech side is genuinely fun. There's nothing quite like watching a user sign up for something you built entirely by yourself.

I'm currently working on:

If any of this resonates, I'd love to hear your story. What's your tech stack? What did you build? Drop a comment - I read every single one.

*You can check out TopoForest at topforest.cn. I'm also on X (Twitter) sharing my build-in-public journey - come say hi!*
