Figuro is a full-stack SaaS platform that lets users instantly transform their Figma designs into production-ready frontend code (React, Vue, Angular, Svelte, or plain HTML). It includes a user-facing app with authentication, project management, and conversion history, plus a full admin panel with analytics.
| Layer | Technology |
|---|---|
| Frontend | React 18 + Vite, TypeScript, Tailwind CSS v4, shadcn/ui, Wouter (routing), React Query |
| Backend | Express 5, Node.js 24, TypeScript |
| Auth | Better Auth (email/password + Google OAuth + GitHub OAuth) |
| Database | PostgreSQL (Neon DB or any Postgres) via Drizzle ORM |
| Validation | Zod (v4), drizzle-zod |
| API Contract | OpenAPI 3.1 → Orval codegen (React Query hooks + Zod schemas) |
| Charts | Recharts |
| Build | esbuild (server), Vite (client) |
| Monorepo | pnpm workspaces |
figuro/
├── artifacts/
│ ├── api-server/ # Express 5 backend
│ │ └── src/
│ │ ├── app.ts # Express app, CORS, Better Auth mount
│ │ ├── index.ts # Server entry point
│ │ ├── lib/
│ │ │ ├── auth.ts # Better Auth configuration
│ │ │ ├── session.ts # Session helper
│ │ │ └── logger.ts # Pino structured logger
│ │ └── routes/
│ │ ├── health.ts
│ │ ├── user.ts
│ │ ├── projects.ts
│ │ ├── conversions.ts
│ │ ├── dashboard.ts
│ │ └── admin.ts
│ └── figuro/ # React + Vite frontend
│ └── src/
│ ├── App.tsx # Router setup
│ ├── index.css # Theme variables + Tailwind
│ ├── contexts/
│ │ └── AuthContext.tsx
│ └── pages/
│ ├── landing.tsx
│ ├── login.tsx
│ ├── register.tsx
│ ├── dashboard.tsx
│ ├── projects.tsx
│ ├── project-detail.tsx
│ ├── convert.tsx
│ ├── conversion-result.tsx
│ ├── pricing.tsx
│ ├── settings.tsx
│ ├── admin/
│ │ ├── index.tsx
│ │ ├── users.tsx
│ │ └── conversions.tsx
│ └── not-found.tsx
├── lib/
│ ├── api-spec/
│ │ └── openapi.yaml # API contract (source of truth)
│ ├── api-client-react/ # Generated React Query hooks
│ ├── api-zod/ # Generated Zod validation schemas
│ └── db/
│ └── src/
│ └── schema/
│ ├── users.ts
│ ├── projects.ts
│ ├── conversions.ts
│ └── activity.ts
└── scripts/ # Utility scripts
git clone https://github.com/your-username/figuro.git
cd figuro
pnpm install
Copy the example env file and fill in your values:
cp .env.example .env
See Environment Variables for full details.
Push the schema to your PostgreSQL database:
pnpm --filter @workspace/db run push
pnpm --filter @workspace/scripts run seed
In two separate terminals:
# Terminal 1 — API server (port 5000)
pnpm --filter @workspace/api-server run dev
# Terminal 2 — Frontend (port from .env)
pnpm --filter @workspace/figuro run dev
Open http://localhost:5173 (or whatever port Vite assigns).
Create a .env file in the project root (or set these in your hosting environment):
# ─── Database ───────────────────────────────────────────────────────────────
# Full PostgreSQL connection string (Neon, Supabase, or local)
DATABASE_URL=postgresql://user:password@host:5432/figuro
# ─── Better Auth ─────────────────────────────────────────────────────────────
# Required: random 32+ character secret key
# Generate one: https://generate-secret.vercel.app/32
BETTER_AUTH_SECRET=your-super-secret-key-change-this-in-production
# The base URL where the auth callbacks are served (no trailing slash)
# In development: http://localhost:5000
# In production: https://your-domain.com
BETTER_AUTH_URL=http://localhost:5000
# ─── Google OAuth ────────────────────────────────────────────────────────────
# Create credentials at: https://console.cloud.google.com/apis/credentials
# Authorized redirect URI: {BETTER_AUTH_URL}/api/auth/callback/google
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
# ─── GitHub OAuth ────────────────────────────────────────────────────────────
# Create an OAuth App at: https://github.com/settings/developers
# Callback URL: {BETTER_AUTH_URL}/api/auth/callback/github
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
# ─── Server ──────────────────────────────────────────────────────────────────
# Port for the API server (set automatically in Replit)
PORT=5000
http://localhost:5000/api/auth/callback/googlehttps://your-domain.com/api/auth/callback/google.envhttp://localhost:5173 (or your production URL)http://localhost:5000/api/auth/callback/github.envAuthentication is powered by Better Auth, a modern full-stack auth library.
| Method | Description |
|---|---|
| Email + Password | Standard registration and login |
| Google OAuth | One-click sign-in with Google |
| GitHub OAuth | One-click sign-in with GitHub |
/api/auth/* in the Express app.AuthContext (src/contexts/AuthContext.tsx) wraps the entire app and exposes:user — current user profile (or null)isLoading — true while session is being fetchedisAuthenticated — convenience booleanlogin(email, password) — email/password sign-inregister(name, email, password) — new account creationloginWithGoogle() — redirect to Google OAuth flowloginWithGithub() — redirect to GitHub OAuth flowlogout() — sign out and clear session/login.user.role === 'admin'.After creating your account, run this SQL to promote yourself:
UPDATE users SET role = 'admin' WHERE email = '[email protected]';
Or use the Replit database tool / any SQL client connected to your DATABASE_URL.
The project uses Drizzle ORM with PostgreSQL.
| Table | Purpose |
|---|---|
users |
User accounts (extended Better Auth schema with plan, role, banned) |
sessions |
Active login sessions (managed by Better Auth) |
accounts |
OAuth account links (managed by Better Auth) |
verifications |
Email verification tokens (managed by Better Auth) |
projects |
User Figma projects |
conversions |
Individual conversion jobs |
activity |
User activity feed events |
All schema lives in lib/db/src/schema/. Each table is a separate file, re-exported from index.ts.
This project uses push-based schema management (Drizzle Kit push):
# Apply schema changes to the database
pnpm --filter @workspace/db run push
# Force push (resets conflicting columns — use carefully)
pnpm --filter @workspace/db run push-force
Production note: On Replit, the production database is automatically migrated when you publish. Drizzle diffs the dev schema against prod and applies changes through the Publish UI.
The API is contract-first: all endpoints are defined in lib/api-spec/openapi.yaml and React Query hooks are auto-generated from it.
pnpm --filter @workspace/api-spec run codegen
All endpoints are prefixed with /api.
| Method | Path | Description |
|---|---|---|
| GET | /api/user/me |
Get current user profile |
| PATCH | /api/user/me |
Update profile (name, avatar) |
| Method | Path | Description |
|---|---|---|
| GET | /api/projects |
List user's projects |
| POST | /api/projects |
Create a project |
| GET | /api/projects/:id |
Get project by ID |
| PATCH | /api/projects/:id |
Update project |
| DELETE | /api/projects/:id |
Delete project |
| Method | Path | Description |
|---|---|---|
| GET | /api/conversions |
List user's conversions |
| POST | /api/conversions |
Submit a new conversion |
| GET | /api/conversions/:id |
Get conversion by ID |
| DELETE | /api/conversions/:id |
Delete conversion |
| POST | /api/conversions/:id/regenerate |
Re-run a conversion |
| Method | Path | Description |
|---|---|---|
| GET | /api/dashboard/stats |
User stats (projects, conversions, success rate) |
| GET | /api/dashboard/activity |
Recent activity feed (last 20 events) |
| Method | Path | Description |
|---|---|---|
| GET | /api/admin/stats |
Platform-wide stats |
| GET | /api/admin/users |
All users |
| PATCH | /api/admin/users/:id |
Update user (plan, role, banned) |
| DELETE | /api/admin/users/:id |
Delete user |
| GET | /api/admin/conversions |
All conversions |
| GET | /api/admin/analytics/daily |
Daily conversion chart data (30 days) |
| GET | /api/admin/analytics/frameworks |
Framework usage breakdown |
| Method | Path | Description |
|---|---|---|
| POST | /api/auth/sign-in/email |
Email/password login |
| POST | /api/auth/sign-up/email |
Register with email |
| GET | /api/auth/sign-in/social?provider=google |
Start Google OAuth |
| GET | /api/auth/sign-in/social?provider=github |
Start GitHub OAuth |
| POST | /api/auth/sign-out |
Log out |
| GET | /api/auth/session |
Get current session |
| Route | Auth Required | Description |
|---|---|---|
/ |
No | Marketing landing page |
/login |
No | Login (email/Google/GitHub) |
/register |
No | Register new account |
/dashboard |
Yes | User dashboard with stats |
/projects |
Yes | Projects list |
/projects/:id |
Yes | Project detail + conversions |
/convert |
Yes | New conversion form |
/conversions/:id |
Yes | Conversion result + code viewer |
/pricing |
No | Pricing plans |
/settings |
Yes | User settings |
/admin |
Admin only | Admin analytics dashboard |
/admin/users |
Admin only | User management table |
/admin/conversions |
Admin only | All conversions table |
The admin panel is accessible at /admin and requires role = 'admin'.
UPDATE users SET role = 'admin' WHERE email = '[email protected]';
/admin): KPI cards, daily conversions line chart, framework breakdown pie chart, recent signups/admin/users): Full searchable user table, inline plan/role editing, ban/unban, delete/admin/conversions): All platform conversions with status and framework filterspnpm run typecheck
pnpm run build
lib/api-spec/openapi.yamlpnpm --filter @workspace/api-spec run codegenartifacts/api-server/src/routes/artifacts/api-server/src/routes/index.ts@workspace/api-client-react in your React componentlib/db/src/schema/ (e.g. lib/db/src/schema/invoices.ts)lib/db/src/schema/index.tspnpm --filter @workspace/db run push# Install all workspace dependencies
pnpm install
# Run the API server in development
pnpm --filter @workspace/api-server run dev
# Run the frontend in development
pnpm --filter @workspace/figuro run dev
# Full typecheck (all packages)
pnpm run typecheck
# Build everything
pnpm run build
# Push DB schema changes
pnpm --filter @workspace/db run push
# Re-generate API client hooks from OpenAPI spec
pnpm --filter @workspace/api-spec run codegen
vite build)esbuild).replit.app domain (or a custom domain if configured)pnpm install --frozen-lockfile
pnpm run build
node artifacts/api-server/dist/index.mjs
artifacts/figuro/dist/public via a static file server (nginx, Caddy, etc.) or configure the Express server to serve it.pnpm --filter @workspace/db run push
When deploying to production, update your OAuth app callback URLs:
https://your-domain.com/api/auth/callback/googlehttps://your-domain.com/api/auth/callback/githubhttps://your-domain.comTo push this project to GitHub:
git init
git add .
git commit -m "Initial commit: Figuro SaaS platform"
git branch -M main
git remote add origin https://github.com/your-username/figuro.git
git push -u origin main
git add .
git commit -m "Your commit message"
git push
Make sure your .gitignore includes:
.env
node_modules/
dist/
.DS_Store
*.tsbuildinfo
Never commit your
.envfile. Store secrets in your hosting platform's environment variable manager (Replit Secrets, Railway Variables, etc.).
DATABASE_URL must be set errorEnsure DATABASE_URL is in your environment. On Replit, it's auto-provisioned. Locally, add it to .env.
The callback URL registered in Google/GitHub must exactly match {BETTER_AUTH_URL}/api/auth/callback/{provider}. Double-check BETTER_AUTH_URL has no trailing slash and matches the URL your server is actually running on.
BETTER_AUTH_SECRET not setGenerate a strong secret: openssl rand -base64 32 or use https://generate-secret.vercel.app/32.
pnpm --filter @workspace/db run push-force
Always re-run codegen after spec changes:
pnpm --filter @workspace/api-spec run codegen
The API server reads PORT from the environment. Make sure nothing else is running on the same port. Vite's port is separately configured in vite.config.ts.
Your user account needs role = 'admin'. Run:
UPDATE users SET role = 'admin' WHERE email = '[email protected]';
MIT — feel free to use, modify, and distribute this project.