$ cat ~/projects/smartcampus.mdx

SmartCampus.co.id

smartcampus.co.id

Multi-tenant backend architecture supporting multiple universities under a unified SmartCampus ecosystem.

  • Next.js
  • Go
  • MySQL

Context

SmartCampus wanted a single platform that multiple universities could share, with each university feeling like it had its own instance. The product surface was wide — auth, course delivery, content management, payments — and the team was small enough that I was touching most of it.

What I built

  • Multi-tenant backend in Go. Every request carries a tenant context (resolved from the subdomain), and every database query is scoped by tenant_id. The schema is shared across tenants but logically partitioned via the tenant_id column on every table; we considered schema-per-tenant early on and decided against it because migrations across N schemas were going to be a nightmare.
  • Authentication with JWT + refresh tokens, role-based access (admin, lecturer, student), and tenant-scoped permissions. The auth service is the only one that knows about password hashes; everything else trusts the JWT claims.
  • Schema design for institutions, users, courses, enrollments, learning modules, content blocks, and resource metadata. Every foreign key has a tenant_id column to make "show me everything for this university" a single index lookup.
  • Next.js frontend for the student and lecturer dashboards, with the Go backend serving a JSON API.

Outcome

Three universities were onboarded on the same codebase without any of them seeing each other's data. Adding a fourth took about two weeks — most of which was content migration, not code changes.

What I'd do differently: the multi-tenant query pattern is great until someone forgets the WHERE tenant_id = ? clause. I'd add a per-tenant test fixture and a lint rule (or a query wrapper that makes tenant scoping mandatory) from the start. We caught the omissions in code review, but a belt-and-suspenders approach would have been safer.