Docs / Architecture▾

Start here

Architecture

One stateless API, one relational source of truth, a fast in-memory layer, and background workers for anything that can wait. Small enough for one person to run, built so each piece can scale on its own.

Parkezy architectureCLIENTSMobile appExpo · React Native · Android / iOSLanding siteNext.js on VercelGoogle sign-inClerk (OIDC tokens)EDGERender edge · HTTPS · request size cap · security headersAPI (STATELESS)FastAPI · 89 routes · Pydantic validation · rate limitsAuthSearchBookingsPaymentsAccessHostAdmindomain rules · pricing · booking state machine · ledger · outbox writerDATAPostgreSQL + PostGISNeon · source of truthRediscache · limits · one-time tokens · queueObject storageBackblaze B2 (S3 API)WORKERS & PROVIDERSCelery worker + beatoutbox · expiries · reconcileRazorpaypaymentsResendemailExpo Pushnotificationspresignphotos go straight from the phone to storage (presigned PUT)claims eventswebhooks

Swipe sideways to see the whole figure.

Every box on the API tier is a Python module in one FastAPI service. They share one database transaction per request, which keeps cross-module rules (booking + payment + ledger) simple and correct.

Components#

LayerTechnologyWhat it is responsible for
Mobile appExpo SDK 57, React Native, Expo Router, TanStack QueryMap search, booking, payments, QR pass, host dashboard, QR scanner
Landing siteNext.js 16 on VercelThis website, the APK download, a health page that checks the API and database
APIPython 3.12, FastAPI, Pydantic v2, SQLAlchemy 2 (async)89 routes: validation, auth, business rules, pricing, the booking state machine
DatabasePostgreSQL + PostGIS on NeonUsers, listings, slots, bookings, payments, ledger, outbox. Constraints enforce the invariants
Cache & queueRedis (Redis Cloud)Search cache, rate-limit counters, one-time QR/challenge tokens, bloom filter, Celery broker
WorkersCelery worker + beatDeliver outbox events, expire unpaid holds, mark no-shows, reconcile payments, cleanup
StorageBackblaze B2 (S3 API)Listing and dispute photos, uploaded directly from the phone with presigned URLs
ProvidersRazorpay, Resend, Expo Push, ClerkPayments, transactional email, push notifications, Google sign-in

Life of a request#

What happens between a tap in the app and the response, using POST /v1/bookings as the example. Each step can stop the request early with a clear JSON error, so expensive work only happens for requests that deserve it.

Request guard

middleware
Rejects bodies over 1 MiB (413) before reading them; adds request id, timing and security headers.

Validation

Pydantic
Types, ranges and formats checked: bad input is a 422 with field-level messages. NUL bytes anywhere are a 400.

Authentication

JWT
Short-lived access token verified; token version compared so a password change signs out every device.

Rate limit

Redis
Sliding-window counter per user or IP. Over the limit: 429 with Retry-After.

Service + domain rules

one transaction
Pricing, state machine, availability. Domain events are written to the outbox in the same transaction.

Commit succeeds

201 with the booking. A worker is woken to deliver the outbox events.

Constraint violated

Overlap or duplicate key becomes a clean 409, never a 500.

Database unavailable

Pool wait capped at 3 s, then 503 with Retry-After instead of hanging.
Middleware and dependencies run in this order on every request. The database is touched only after validation, authentication and rate limiting pass.

Code layout#

The backend separates HTTP from business rules, so the same rules run in the API and in background workers.

parkezy-innotech/backend/apptext
api/ # HTTP only: routes, dependencies, error mapping, middleware
v1/ # auth, me, public, bookings, host, reviews, support, admin, webhooks …
core/ # config, security (argon2id, JWT), errors, logging, clock
domain/ # pure rules: pricing, booking_state, availability, geo, access codes
services/ # use cases: booking, payment, ledger, listing, auth, login protection …
infra/ # Redis helpers: cache, rate limiter, bloom filter, one-time tokens, metrics
integrations/ # ports to the outside: payments, email, push, storage, identity, CAPTCHA
workers/ # Celery tasks: outbox processor, maintenance, reconciliation
container.py # composition root: builds every dependency once, tests swap pieces

Deployment#

PieceWhere it runsNotes
API + workerRender (Docker, Oregon)One container runs uvicorn and the Celery worker, with migrations applied on start
DatabaseNeon Postgres (us-east-2)Pooled connections via PgBouncer
RedisRedis Cloud (us-east-2)Next to the database, so a login's two Redis round trips stay short
Landing + docsVercelStatic pages plus one server route for the health check
Android appEAS BuildRelease APK; secrets live in EAS environment variables, never in the repo

Configuration and secrets#

  • Infrastructure settings come from environment variables, validated at startup. The app refuses to boot with, say, Resend selected but no key, or short JWT secrets in production.
  • Business rules (GST, commission, hold times, overstay fees) live in a platform_settings table, so an admin can change them without a deploy.
  • No key of any kind is committed to the repository: deployment files mark secrets sync: false and they are set in each platform's dashboard.