TUSHAR.STUDIO
ProductOct 2026 · 8 min

Aurelia: What It Is, What It Can Do, and What It's Made Of

Aurelia is a shop for private palaces, villas and residences. A catalogue, a rupee quote, Razorpay checkout, and a folio you can cancel — built with Next.js, Prisma, PostgreSQL and TypeScript.

Aurelia is a booking product dressed as a house, and built as a shop. The live site is called Aurelia House. It sells stays in private residences the same way a store sells goods: you pick a chamber, you see the full price, you pay, and you keep an order. In Aurelia that order is called a folio.

This note is the plain version of the build. What the product is, what a guest can actually do on it, and the stack that holds it up.

What it is

It is a catalogue of ten private houses across ten cities: Udaipur, Jaisalmer, Dubai, Gstaad, Kyoto, Malé, Oia, Paris, Positano and Ubud. Palaces, villas, a chalet, a ryokan, an estate, an atelier. Prices are per chamber, per night, in Indian rupees. The writing treats the stay as something you buy, and the confirmation as a receipt you can open later.

Around the shop there is a destinations index, an experiences page, a concierge desk, and a Circle membership enquiry. Legal pages cover how a stay is bought, the terms of sale, refunds, privacy and a merchant contact. The public site is the storefront. The database is the inventory and the ledger of orders.

Choose a chamber, see the full price, and pay at checkout. The folio is your order.

What it can do

A chamber is shown only when it can still be reserved. The catalogue does not invent urgency, and it does not list dates that are already spoken for.

How a stay is held

Availability is a count, not a flag on the hotel. Each chamber has an inventory. A confirmed reservation that overlaps the requested nights counts against that inventory. If the taken count is already at the inventory, those dates are refused with a simple line: those dates have already been spoken for.

const taken = await overlappingCount(room.id, checkIn, checkOut);
const free = taken < room.inventory;

The same check runs again inside a serializable transaction when the folio is written, so two guests cannot both take the last chamber for the same nights. The price the guest pays is recomputed on the server: nights times the chamber rate, plus extras, plus tax at 18%. The browser can ask for a stay. It cannot decide the total.

What it's made of

Aurelia is one Next.js application. The pages and the API live in the same TypeScript codebase, and the whole thing deploys to Vercel. There is no separate Express server.

LayerChoiceWhy
AppNext.js 16, React 19, TypeScriptApp Router pages, route handlers, one deploy
InterfaceTailwind CSS v4, shadcn/ui, Base UIThe house look: Playfair Display, Jost, gold on near-black
DataPostgreSQL + PrismaHouses, chambers, extras, reviews, folios and payments as real relations
ValidationZodQuotes, guests, payments and forms checked before they touch the database
Authjose, magic links, ResendA signed session cookie. A 15-minute link by email. No password to forget
PaymentsRazorpayOrder, signature check, and a webhook before a folio is confirmed
LimitsUpstash RedisRate limits on the routes that take money and mail
DeliveryVercelThe live shop is hotel-booked-frontend.vercel.app

The models that matter

The schema is small on purpose. A Hotel has rooms, extras, reviews and reservations. A Room knows its occupancy, its nightly price and how many of that chamber exist. A Reservation is the folio: dates, guests, nights, room total, extras, tax, status, and the payment row beside it. Concierge letters and Circle enquiries are their own tables, so a message is a record and not an email that disappeared.

Sessions are HS256 JWTs in an httpOnly cookie, good for seven days, with a guest role and an admin role. The magic link is a separate token that expires in fifteen minutes and can only be used to sign the session. When Resend is configured, the link is emailed. When it is not, the link is logged, which keeps local development honest.

Payments that agree with the folio

Checkout creates a Razorpay order for the quoted amount in paise, in INR. The client never marks a stay as paid. The verify route checks the Razorpay signature, reads the payment method back from Razorpay, and only then writes the reservation. A webhook covers the same fulfillment path, so a closed tab does not leave a paid order without a folio. If the signature fails, the stay stays unconfirmed.

What I would keep

Aurelia is the project where a luxury page and a real checkout had to be the same product. The type is quiet. The inventory, the quote and the payment are not.

Enjoyed this?

Let's build something meaningful together.

Get In Touch