Banner Background
Web App

SparkAI

Built as a MERN-style monorepo deployed on Vercel, SparkAI leverages a Neon serverless Postgres database for scalable tracking of AI asset creations. It features an Express REST API backend proxying third-party providers (Google Gemini, Clipdrop, APILayer) combined with a highly styled React SPA client. It handles everything safely using Clerk middlewares to protect tools, track free tiers, and prevent abuse without managing users and payments manually.

React JSTailwind CSSNode.jsNode JSMongoDBPostgreSQLExpress JSGoogle GeminiClerkClerkGitGithubPostmanPostmanVercelpnpm
SparkAI Preview

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.

SparkAI dashboard — AI tools sidebar and recent creations

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.

ServicePurpose
Google Gemini (gemini-2.5-flash)Article & blog title generation, resume review fallback
Clipdrop text-to-image/v1AI image generation
Clipdrop remove-background/v1Background removal
Clipdrop replace-background/v1 + cleanup/v1Object removal (prompt-based)
APILayer resume/reviewPrimary 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.

MethodPathBody / FormDescription
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-backgroundmultipart: imageClipdrop background removal; returns base64 PNG.
POST/api/ai/remove-image-objectmultipart: image, { object }Clipdrop replace-background / cleanup fallback.
POST/api/ai/resume-reviewmultipart: resume (PDF ≤ 5 MB)APILayer → Gemini → static fallback.
POST/api/ai/test-uploadmultipart: imageUnauthenticated upload sanity check.
MethodPathDescription
GET/api/user/get-user-creationsAll creations for the current user.
GET/api/user/get-published-creationsCommunity 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

App.jsx
main.jsx
index.css
vite.config.js
vercel.json
server.js
vercel.json

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 dev
cd 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.