AS.
← Back to work

09 / 11

AI / Web App

Prompt-O-Phobia

A community home for AI prompts that would otherwise live in a dozen scattered notes apps.

ROLE
Full-stack developer, solo project
BUILT FOR
Personal/portfolio project, open-sourced
STACK
Next.js 15 (App Router), React 19, Tailwind CSS 3, Prisma 6 + PostgreSQL 16, Clerk (@clerk/nextjs 7) for auth, Docker + Docker Compose
PLATFORM
Web app, containerized with Docker Compose for one-command local runs; deployable to Vercel + managed Postgres
Prompt-O-Phobia — sign in

THE PROBLEM

Good prompts are easy to write and easy to lose — they end up buried in chat histories, notes apps, or Slack messages, with no shared place to find one someone else already perfected. Prompt-O-Phobia gives prompt engineers, writers, and AI tinkerers a single searchable feed instead.

The design brief is simple on purpose: no gatekeeping to read (the feed is public), a low-friction way to contribute (sign in once, post a prompt with a tag), and ownership rules enforced where it actually matters — the server, not just by hiding buttons in the UI.

THE IDEA

Treat prompts like a lightweight, taggable social feed: Clerk handles identity so the app never touches passwords, and a thin Postgres schema (User + Prompt) is the only state the app owns.

Every write path resolves the acting user from the Clerk session server-side, so the client can never spoof a creatorId.

WHAT IT DOES

01

Public feed with live search

Every published prompt renders as a card with creator, content, and tag. Search is debounced and filters case-insensitively across tag, username, and prompt content — no account needed to browse.

prompt-o-phobia.vercel.app
Prompt-O-Phobia — Public feed with live search

02

Tag-based discovery and one-click copy

Clicking a tag filters the feed to that tag. Each card has a copy-to-clipboard button with visual confirmation, aimed at the actual use case — grab a prompt and paste it elsewhere.

prompt-o-phobia.vercel.app
Prompt-O-Phobia — Tag-based discovery and one-click copy

03

Auth-gated authoring with a live preview

Users sign in via Clerk, and on a user's first authenticated write, a matching Postgres User row is provisioned keyed by their Clerk id — there's no separate registration step. The create/edit form shares one component and shows a live preview of exactly how the prompt card will appear in the feed.

prompt-o-phobia.vercel.app
Prompt-O-Phobia — Auth-gated authoring with a live preview

HOW IT WORKS

Browser
  │
  ├─ GET / , /profile/[id]      (public, no auth)
  ├─ GET /api/prompt, /api/users/:id/posts
  │
  ▼
middleware.js (clerkMiddleware)
  │  protects only: /create-prompt, /update-prompt, /profile, /api/prompt/new, /api/users/me/*
  ▼
Next.js 15 App Router
  ├─ Server/Client Components (Feed, Form, Nav, Profile)
  └─ Route Handlers (app/api/prompt/*, app/api/users/*)
        │  auth() / currentUser() → Clerk
        │  getOrCreateUser() → sync Clerk session to Postgres User
        ▼
  Prisma Client (singleton, cached on globalThis in dev)
        ▼
  PostgreSQL 16  (User ──< Prompt, cascade delete)

Docker Compose: app (Next.js) + db (Postgres)
  container start → prisma migrate deploy → next start

Middleware protects routes, not pages

middleware.js only wraps the authoring surface (/create-prompt, /update-prompt, /profile, and the */me/* APIs) in isProtectedRoute, so the feed and other users' profiles stay crawlable/public without extra branching in the pages themselves.

Ownership is checked twice, cheaply

Middleware blocks unauthenticated requests early, but the PATCH/DELETE handlers for /api/prompt/:id still re-resolve the creator from the session and compare it to the prompt's creatorId — defense in depth without any extra infrastructure.

Prisma client is a cached singleton

The standard Next.js fix for dev-mode hot-reload exhausting Postgres connections.

Docker build bakes in NEXT_PUBLIC_* Clerk keys as build args

Since Next.js inlines them at build time — the Dockerfile comments call this out explicitly, which suggests it was a real gotcha during setup.

CHALLENGES AND WHAT I LEARNED

Mapping Clerk identity onto a separate Postgres User record

Rather than trusting Clerk as the only source of truth, this meant writing a getOrCreateUser sync path and deciding exactly which fields to mirror (clerkId, email, username, image).

Next.js 15's shift to async params

This broke the usual synchronous pattern in dynamic routes and client pages using useSearchParams — the exact Suspense boundary fix needed for update-prompt was a real issue hit during development, not a hypothetical.

Deciding how much of the auth boundary belongs in middleware

Public GETs on /api/prompt/:id needed to stay reachable while PATCH/DELETE on the same route needed ownership checks, so the enforcement couldn't live in middleware alone.

RESULTS

Full prompt lifecycle (create/read/update/delete) with server-side ownership enforcement, not just UI-level hiding of edit/delete buttons.

Clerk-based auth with zero custom password/session handling.

One-command local environment via docker compose up --build, including automatic migration deploy on container start.

STACK

Next.js 15React 19Tailwind CSS 3Prisma 6PostgreSQL 16ClerkDocker

LINKS

NEXT PROJECT

Moviebase

→

05 / CONTACT

Let'sbuild
somethinggreat.