Lendr — Borrow from the dorm next door 💡 Inspiration Every college dorm is quietly full of stuff that gets used once. A blazer for one interview. A power drill for one IKEA build. A textbook for one chapter. We buy these things new, use them a handful of times, and let them gather dust — while the person two doors down needs the exact same thing this week and is about to buy their own.

The frustrating part is that the supply and the demand are already in the same building. General marketplaces don't help: they aren't campus-scoped, they have no trust layer, and they surface listings across a whole city rather than the few people you'll actually meet. I kept coming back to one idea:

The unit of trust on a campus isn't a city. It's a walk across the quad.

Lendr is built around that sentence — a college-email-gated, proximity-first marketplace where students lend and borrow the things they already own, surfaced by who's nearest, not who paid for placement.

📦 What it does Verified, campus-only sign-up. Auth is gated to college email addresses, so every lender and borrower is a real, enrolled student. Proximity-first discovery. The feed is ranked by real geospatial distance — the nearest useful item wins. The full borrow loop in one app. List an item, request it, chat in-app, drop a live pickup pin on a map, and track the hand-off through return. Cross-platform. One codebase ships to iOS, Android, and the web. 🛠️ How I built it Lendr is a full-stack, deployed system — not a prototype.

Frontend. A single TypeScript + Expo / React Native tree (SDK 54) that ships to iOS, Android, and web (exported with react-native-web and hosted on Netlify). Navigation is a React Navigation native stack whose routes switch on session × needsOnboarding. Auth runs through Supabase Auth using the PKCE flow, with session persistence split between AsyncStorage on native and the browser's localStorage on web.

Backend. A TypeScript Express REST API wrapped for AWS Lambda with @vendia/serverless-express and fronted by an API Gateway HTTP API, deployed via the Serverless Framework. Requests pass through JWT auth middleware (verifying the Supabase token) and Zod validation before hitting hand-written SQL route handlers — no ORM.

Data & services. Supabase Postgres + PostGIS is the core, reached through the pg driver. Photos go to Supabase Storage via multer, and a small server-side proxy to the Google Places API (New) powers the "share a pickup spot" map cards.

The recommendation engine. Instead of an opaque feed, Lendr composes three signals into one ordered result set, computed in the database on every query:

Scope — hard filters on campus and category. Radius — ST_DWithin keeps only items inside a chosen radius of the borrower's live coordinates. Rank — ST_Distance orders the survivors nearest-first (items without coordinates fall to the end with NULLS LAST). Conceptually, each candidate item is scored by the great-circle distance between the borrower at $(\phi_1, \lambda_1)$ and the item at $(\phi_2, \lambda_2)$:

The feed is then simply the candidate set ordered by ascending $d$ — PostGIS does this on the geography type for us, so the math runs at the database layer rather than the client.

📚 What I learned PostGIS is a superpower for "near me" features. Storing a geography column and leaning on ST_DWithin / ST_Distance turned "rank by proximity" into a few lines of SQL instead of a client-side mess. Serverless databases have sharp edges. Lambda couldn't reach Supabase's direct database host (it's IPv6-only), so I had to route through the Supavisor transaction pooler on port 6543. That one networking detail cost real debugging time. PKCE behaves differently on web vs native. On web the OAuth redirect lands back on the same origin and supabase-js exchanges the code; on native it needs a deep-link listener to do the exchange manually. Security can let you relax other constraints. Because auth is a Bearer token in a header — not an ambient cookie — CORS could be scoped loosely without opening a real hole. Idempotency matters for messaging. Tying exactly one conversation to each request (created in the same transaction as the request) avoided a whole class of duplicate-thread bugs. 🧗 Challenges I ran into Lambda → Supabase connectivity. As above: the direct DB host is unreachable from Lambda, and the fix (the transaction pooler) isn't obvious until you hit it. CORS for Netlify deploy previews. API Gateway's HTTP API only allows an exact origin or a bare * — it rejects subdomain wildcards. Since every Netlify deploy preview gets its own subdomain, I moved origin matching into the Express app with a regex, where I could allow deploy-preview-*--.netlify.app and localhost safely. One codebase, three platforms. Small platform forks added up: AsyncStorage vs localStorage, a persistent web top-nav vs native stack navigation, and a benign "Invalid Refresh Token" message that hijacked the whole screen with a red overlay on launch until I suppressed just that log. Encoding rich messages without a schema change. "Share a location" had to ride on the existing message pipeline, so a location message is a normal row whose body is sentinel-encoded JSON — the client decodes it into a map card, and every other message stays plain text. 🚀 What's next Post-borrow ratings (the table is already modeled) to deepen the trust layer. Push notifications — the delivery seam is wired and ready for Expo / FCM. Smarter ranking that blends proximity with availability, owner rating, and recency. Built by Yash Ponnaganti · React Native · Express · AWS Lambda · Supabase · PostGIS · Google Places

Built With

Share this project:

Updates