Rental Marketplace
Ijara
A peer-to-peer rental marketplace for Tashkent where owners list items — tools, electronics, event gear, vehicles — and renters browse, book, and pay online. Built with a Django REST Framework backend and a Nuxt 3 + Pinia frontend.
Overview
Tashkent has a lot of things people own once and then loan to neighbours informally. Ijara formalizes that: owners list tools, electronics, event gear, and vehicles; renters browse by date, book, and pay online. I built the full stack — from the Django API handling booking logic to the Nuxt 3 frontend where transactions happen.
What I built
Designed and built the full Django REST API — five application modules covering accounts, listings, bookings, payments, and search, all with an OpenAPI schema for clear contracts.
Built the booking state machine and double-booking prevention — a Postgres range constraint that makes it physically impossible to book the same item for overlapping dates.
Built the Nuxt 3 frontend — page routing, auth with JWT tokens in memory plus secure refresh cookies, data fetching, form validation, and the Stripe checkout flow.
Added an OpenAI-powered listing assistant that suggests title, description, and category from uploaded photos — reducing the barrier for owners to list their first item.
Architecture
The Django API and Nuxt frontend run in separate containers, health-checked so they start in the right order. The Nuxt server acts as a proxy for all API calls, so the browser only ever talks to one origin. JWT tokens live in memory (secure but wiped on refresh), and refresh tokens live in an httpOnly cookie — the auth pattern that balances security with a smooth user experience.
Under the hood
Double-booking prevention enforced at the database level using a Postgres range overlap constraint — so even if two requests arrive simultaneously, the database rejects the second one. Cancelled or completed bookings fall outside the constraint, so the same time window can be re-booked.
Booking state machine with a full audit trail: every state change goes through a single function that checks role permissions and appends a transition record — so there's a complete history of who changed what and when.
Full-text search with trigram fallback: listings are indexed for fast keyword search, and a trigram similarity check catches typos and partial matches — so renters find what they're looking for even when their spelling is off.
What I learned
Enforcing business rules at the database level — not just in application code — is the only way to stay safe when multiple services or requests touch the same data simultaneously.
The AI listing suggestion feature had an outsized impact on the listing creation experience relative to the effort it took — sometimes the highest-leverage features are the smallest ones.
Built with
Frontend
- Nuxt 3
- Vue 3
- TypeScript
- Pinia
- TanStack Query
- vee-validate
- Zod
- @stripe/stripe-js
Backend
- Django 5.2
- Django REST Framework 3.16
- SimpleJWT
- drf-spectacular
- Stripe Python SDK v10
- OpenAI Python SDK v1
Data
- PostgreSQL 16
- btree_gist
- pg_trgm
- SearchVectorField
- GinIndex
Infra & Testing
- Docker Compose
- uv
- pytest-django
- factory-boy
- Playwright
- Ruff