Overview
SparkAI bundles five AI-powered productivity tools — article writing, blog-title brainstorming, text-to-image generation, background/object removal, and resume review — behind a single authenticated dashboard. It's built as a classic MERN-style monorepo with one twist: Neon serverless Postgres stands in for MongoDB. A Vite + React 19 SPA talks to an Express 5 API that proxies every AI call to third-party providers (Google Gemini, Clipdrop, APILayer), so no provider key ever reaches the browser.
Both workspaces ship their own vercel.json and deploy independently as serverless functions, with Clerk handling authentication, billing, and free/premium plan gating end to end.
Usage quotas live in the identity layer, not the database
Free-tier usage (10 generations) is tracked in Clerk's privateMetadata.free_usage
rather than a Postgres column. Billing state and quota travel with the user's
identity itself, so the gate-check middleware never needs to query Postgres to
decide whether a request is allowed — it just reads the verified Clerk session.
The authenticated /ai dashboard — sidebar navigation to every tool, plus a log of the user's recent creations.
Tech Stack
React 19 + Vite 7 single-page app, routed with React Router v7
and styled with Tailwind CSS v4 (via @tailwindcss/vite). Auth, billing,
and plan gating are handled client-side by the Clerk React SDK
(@clerk/clerk-react). API calls go through Axios, notifications through
react-hot-toast, and AI responses render through react-markdown.
Iconography uses lucide-react / react-icons, with tailwind-variants
and class-variance-authority for style composition.
Node.js + Express 5 (ESM). The Clerk Express SDK
(@clerk/express) supplies clerkMiddleware + requireAuth. Postgres access
goes through @neondatabase/serverless. File uploads (images, PDFs) are
handled by Multer, writing to /tmp for Vercel compatibility, and
pdf-parse extracts resume text from uploaded PDFs. Cloudinary is
configured but currently superseded by direct base64 returns.
| Service | Purpose |
|---|---|
Google Gemini (gemini-2.5-flash) | Article & blog title generation, resume review fallback |
Clipdrop text-to-image/v1 | AI image generation |
Clipdrop remove-background/v1 | Background removal |
Clipdrop replace-background/v1 + cleanup/v1 | Object removal (prompt-based) |
APILayer resume/review | Primary resume review (Gemini used as fallback) |
Neon serverless Postgres for persistence, Clerk for auth and billing (free vs. premium plans), and Vercel hosting both the client and server as independent serverless deployments.
Feature Breakdown
Write Article & Blog Titles
A prompt plus a length selector returns a full-length markdown article from Gemini 2.5 Flash; a lighter variant brainstorms SEO-friendly blog title lists from the same model.
Generate Images
Text-to-image generation through Clipdrop, returned as a base64 PNG, with an optional flag to publish the result to the community gallery.
Remove Background & Object
Upload any image to strip its background via Clipdrop, or upload an image with a text description of an object to remove — Clipdrop's replace-background and cleanup endpoints fill the gap contextually.
Review Resume
Upload a PDF (5 MB max), extract its text with pdf-parse, and get
structured feedback — Overall Impression, Strengths, Weaknesses,
Recommendations — from APILayer with Gemini as a fallback.
Community Gallery
Published image creations can be liked by other signed-in users. The route
exists in the codebase but is currently commented out in App.jsx.
Free / Premium Plans
Free accounts get 10 total generations tracked in Clerk metadata; premium
accounts (checked via req.auth.has({ plan: 'premium' })) are unlimited.
Architecture
Every gated AI call follows the same request path from the SPA down to the third-party provider and back.
Client request
The SPA sends an authenticated POST /api/ai/<tool> request — a JSON
payload for text tools, or multipart/form-data for image and resume
uploads — carrying the Clerk session JWT.
Identity & quota gate
clerkMiddleware + requireAuth verify the session and attach req.auth.
A custom server/middleware/auth.js then reads plan and free_usage
from Clerk's privateMetadata and blocks free-tier requests past the
10-generation cap. If Clerk keys are missing entirely, the server falls
back to a dev-user stub so local development still works end to end.
Third-party call
The relevant controller in aiController.js calls Gemini, Clipdrop, or
APILayer, then normalizes the result to either markdown text or a base64
data-URL image.
Persist & respond
The result is written to the creations table in Neon keyed by the
Clerk user_id, free_usage is incremented via
clerkClient.users.updateUser, and the response is returned as
{ success: true, content }.
API Reference
All routes require a Clerk session unless the server is running in dev-fallback mode.
| Method | Path | Body / Form | Description |
|---|---|---|---|
| POST | /api/ai/generate-article | { prompt, length } | Gemini-generated article (markdown). |
| POST | /api/ai/generate-blog-title | { prompt } | Gemini-generated blog title list. |
| POST | /api/ai/generate-image | { prompt, publish } | Clipdrop text-to-image; returns base64 PNG. |
| POST | /api/ai/remove-image-background | multipart: image | Clipdrop background removal; returns base64 PNG. |
| POST | /api/ai/remove-image-object | multipart: image, { object } | Clipdrop replace-background / cleanup fallback. |
| POST | /api/ai/resume-review | multipart: resume (PDF ≤ 5 MB) | APILayer → Gemini → static fallback. |
| POST | /api/ai/test-upload | multipart: image | Unauthenticated upload sanity check. |
| Method | Path | Description |
|---|---|---|
| GET | /api/user/get-user-creations | All creations for the current user. |
| GET | /api/user/get-published-creations | Community feed (publish = true). |
| POST | /api/user/toggle-like-creations | { id } — toggles current user in the likes array. |
Successful responses follow { success: true, content }; errors follow { success: false, error } (or message), with gated failures returning HTTP 403.
Database Schema
A single Neon Postgres table backs every creation across all five tools:
CREATE TABLE creations (
id SERIAL PRIMARY KEY,
user_id TEXT NOT NULL, -- Clerk user id
prompt TEXT NOT NULL,
content TEXT NOT NULL, -- markdown or base64 data URL
type TEXT NOT NULL, -- 'article' | 'blog_title' | 'image' | 'resume-review'
publish BOOLEAN DEFAULT FALSE,
likes TEXT[] DEFAULT '{}', -- array of Clerk user ids
created_at TIMESTAMP DEFAULT NOW()
);Note that generation counts are deliberately not stored here — they live in Clerk privateMetadata.free_usage, keeping billing and quota state attached to the user's identity rather than a separate row that could drift out of sync.
Project Structure
Getting Started
# server
cd server
pnpm install
cp .env.example .env # then fill in keys
# client
cd ../client
pnpm install
cp .env.example .env# terminal 1 — API on :8080
cd server && pnpm dev
# terminal 2 — Vite dev server
cd client && pnpm devcd client && pnpm build # outputs dist/Every external AI key is optional — the controllers degrade gracefully when one is missing (image tools return a helpful error, resume review falls back to a static template).
Frequently Asked Questions
See it in action
Explore the full AI toolkit — article writing, image generation, background removal, and resume review — at the Live Application.
