I Built a Lost & Found System with Convex (It Was Simpler Than I Thought)
Wanted to learn Convex and TanStack, so I built a simple Lost & Found app. Turns out the 'hard parts' of backend development aren't as hard as they used to be.
Honestly, it felt like cheating.
Report a lost item, add a location, mark it claimed when found. That’s the whole app. Simple enough to be a real learning project, nothing to hide behind.
The Setup
npm create convex and I had a database. No Docker, no connection strings, no mystery .env value I’d forget the meaning of.
export default defineSchema({ items: defineTable({ name: v.string(), location: v.string(), isClaimed: v.boolean(), createdAt: v.string(), })})Four fields, types generated automatically, no migration step to remember. No REST routes, no GraphQL resolvers, just functions:
export const claim = mutation({ args: { id: v.id('items') }, handler: async (ctx, args) => { const item = await ctx.db.get(args.id); if (!item) throw new Error("Item not found") return await ctx.db.patch(args.id, { isClaimed: !item.isClaimed }) }})No controllers, no middleware chain to trace at 11pm. Typed end to end, Convex generates client bindings so the frontend calls it like a regular async function.
What Actually Surprised Me
Real-time updates were free. Someone adds an item, everyone sees it instantly, no WebSocket, no subscription logic written by hand. Baked into how Convex queries work.
Pairing it with TanStack Query meant caching and optimistic updates without hand-writing invalidation logic. Usually the exact part of a side project I quietly give up on around hour three.
const convexQueryClient = new ConvexQueryClient(CONVEX_URL);One line, and the query client and database speak the same language, no adapter code. Convex’s subscriptions already push the update through.
The TypeScript story held up too. Schema changes on the backend showed up as type errors on the frontend before I even ran the app, catching two bugs that would’ve been silent runtime failures.
Where It’d Actually Break
What happens when this needs a complex join, an aggregate with GROUP BY? Convex’s query language is TypeScript functions over indexed reads, not SQL. For a one-table app that difference never showed up, I have no idea how it holds up ten tables deep.
The Honest Take
Is Convex magical? Kind of, a lot of infrastructure work just isn’t there to do. Is it vendor lock-in? Yep, moving off it later isn’t a copy-paste job. Do I care, for a weekend project? Not really.
The velocity from not thinking about infrastructure is real. I spent my time on the feature, not a database I’d forget the details of in a month. Skip the boring parts you’ve already learned, focus on what you’re actually trying to understand.
Start simple if you’re trying this yourself. A todo app, a Lost & Found system, whatever tiny idea you’ve been sitting on. You’ll learn faster shipping something small end to end than reading docs for an hour.
Check it out: Convex Lab on GitHub
Conversation
Discussion
Comments will appear here after GitHub Discussions and Giscus are configured for this repository.