Bhargav Urs Sumantharaj

FindSpace

unknown

A room rental marketplace where tenants search listings, message landlords directly, and landlords manage their own inventory.

Runs on Render's free tier; the first request after idle takes roughly 30 seconds.

Architecture

  • Next.js 14 client (Vercel, Pages Router), client. Renders search, listing detail, messaging, and the landlord dashboard, and holds the auth token for every API call. Connected to Axios + JWT interceptor. Connected from GitHub Actions CI over build + deploy on push.
  • Axios + JWT interceptor, client. Attaches the bearer token to every outgoing request and funnels 401 responses into a single logout path. Connected from Next.js 14 client. Connected to JWT OncePerRequestFilter over REST + JWT.
  • JWT OncePerRequestFilter, service. Validates the bearer token and populates the Spring Security context before any controller method runs. Connected from Axios + JWT interceptor over REST + JWT. Connected to Service layer.
  • Service layer, service. Holds the business rules: who may edit a listing, when a delete becomes a soft delete, what a conversation may contain. Connected from JWT OncePerRequestFilter. Connected to JPA repositories.
  • JPA repositories, service. Maps the five domain entities to Postgres and generates the queries the service layer asks for. Connected from Service layer. Connected to HikariCP pool.
  • HikariCP pool, service. Bounds concurrent database connections from the API. Connected from JPA repositories. Connected to PostgreSQL 15 over JDBC.
  • PostgreSQL 15 (Neon), datastore. The single durable store: users, listings, conversations, messages, and the soft-delete flags. Connected from HikariCP pool over JDBC.
  • GitHub Actions CI, external. Builds both apps on every push and pull request, so a broken build never reaches a deploy hook. Connected to Next.js 14 client over build + deploy on push.
  • Boundary: Render container, Spring Boot 3.2 REST API, Docker.

Trace

live
Fires one real read-only request at the deployed Spring Boot API on Render and times each hop.

Decisions

  • Delete is a runtime decision

    The decision
    Deleting a listing that already has conversations hides it and preserves the message history; a listing with no history is removed outright; editing a hidden listing reactivates it.
    The constraint that forced it
    Tenants keep message threads that reference listings, so a hard delete would orphan live conversations.
    Rejected
    Always soft-deleting. It leaves orphan rows that nothing will ever read.
    Consequence accepted
    Two delete paths to test instead of one, and every listing query has to respect the hidden flag.
  • Nullable column before backfill

    The decision
    A live schema migration landed the new column as a nullable wrapper type, so the column could deploy ahead of the data.
    The constraint that forced it
    Adding a NOT NULL column to an already-populated table crashed Hibernate on boot in production.
    Rejected
    Wiping and reseeding the table. Fine in development, data loss in production.
    Consequence accepted
    Application code treats the field as optional until a backfill and a follow-up migration tighten the constraint.
  • Configuration lives in the environment

    The decision
    Datasource, JWT secret, and CORS origin are read from environment variables, so dev and prod differ by environment and not by branch.
    The constraint that forced it
    Three hosts (Vercel, Render, Neon) need different origins and credentials for the same build artifact.
    Rejected
    Per-environment property files in the repository. They leak secrets and they drift.
    Consequence accepted
    A missing variable fails at boot rather than at build, so the deploy checklist has to be respected.

Known limitations

  • The backend sleeps on the free tier, so the first request after idle takes roughly 30 seconds.
  • No refresh token rotation. When a token expires, the session simply ends.
  • Search is a SQL LIKE across title, address, and description, which will not survive a real listing volume. The next step is a Postgres full-text index.