AWS Builder Center

Building Another Turn with AWS Aurora PostgreSQL and Vercel

This piece of content is created as part of my submission for H0: Hack the Zero Stack with Vercel v0 and AWS Databases.

Every toy deserves another turn
Another Turn started from a very everyday parenting moment.
I would often see big toys sitting outside homes, in apartment corridors, or near play areas slides, ride-on cars, walkers, activity tables and many of them still looked perfectly usable. At the same time, I was about to buy one of those toys for my own child, knowing very well that it might be used for a short time and then take up space at home.
That is where the idea came from.
Parents often have toys that are still valuable, but once a child outgrows them, those toys usually sit unused. Another Turn is a hyperlocal marketplace that helps parents rent, resell, lend, and discover unused children’s toys nearby.
The tagline is simple:
Every toy deserves another turn.

What Another Turn does

Another Turn lets parents browse toys nearby and book them for rental or resale.
A parent can:
  • Browse nearby toy listings
  • Search by category, age, price, and location
  • Save toys for later
  • Request a booking
  • Pay through Razorpay
  • Message another user
  • View booking and payment status
A toy owner can:
  • List a toy with photos
  • Add price, condition, availability, and pickup details
  • Choose rental, resale, or both
  • Manage incoming requests
An admin can:
  • Review listings before they go public
  • Verify owners
  • Inspect payment records
  • Manage refunds
  • Handle reports and moderation workflows
  • Review trust and safety records
The goal was not to build only a static marketplace UI. I wanted the project to feel like a real product with state, roles, payments, moderation, and operational workflows.

Tech stack

The application uses:
  • Next.js App Router
  • Vercel for deployment
  • Amazon Aurora PostgreSQL as the primary database
  • AWS S3 for listing images and avatars
  • AWS SES for email flows
  • Razorpay sandbox for checkout, payment verification, and refunds
  • Raw SQL with pg instead of an ORM

Architecture overview

The browser only talks to the Next.js application deployed on Vercel. The UI interacts with route handlers and server-only service modules, and those services connect to AWS Aurora PostgreSQL as the core system of record. S3 handles images, SES handles email, and Razorpay handles payments.

How we built it

The app is built with Next.js App Router and deployed on Vercel. Vercel was a natural choice because it made it easy to ship a modern Next.js application quickly with server rendering, route handlers, preview deployments, and a clean production deployment flow.
The frontend handles the marketplace experience: browsing toys, viewing listings, creating bookings, managing owner flows, and accessing admin screens.
On the backend side, the browser never talks directly to the database. All mutations and protected operations happen through Next.js route handlers and server-only service modules.
The data flow is simple and deliberate:
  • Users interact with the UI
  • UI calls route handlers or server-side logic
  • Route handlers call server-only services
  • S3 stores uploaded files
  • SES sends transactional emails
  • Razorpay handles checkout, callbacks, and refunds
This helped keep the trust boundary clean: no client-side code can query Aurora directly, and all important state changes stay on the server.

Why Vercel

Vercel worked well for the application because the project is built on Next.js.
It gave us:
  • fast frontend deployment
  • preview builds
  • built-in Next.js support
  • route handlers for server-side mutations
  • easy environment variable handling
  • a simple way to host the user-facing marketplace quickly
The user-facing marketplace experience, browse pages, toy detail pages, booking flows, owner dashboards, and admin screens are deployed on Vercel.
So while AWS Aurora handles the database side, Vercel handles the application delivery and frontend runtime.

Why Amazon Aurora PostgreSQL

The biggest architectural decision in the project was the database.
Another Turn is not a simple key-value app. It is a relational marketplace where one action affects many related pieces of data.
For example, a booking touches:
  • the user
  • the toy listing
  • the toy owner
  • availability
  • payment state
  • refund state
  • review eligibility
  • moderation and trust state
This is why Amazon Aurora PostgreSQL was the right choice.
We needed:
  • complex relational queries
  • transactions
  • joins across marketplace entities
  • flexible search and filtering
  • auditability for moderation and payments
  • a system of record for booking and payment state
PostgreSQL suited the product because the core challenge is correctness, not just storage.
For example, when a parent tries to rent a toy, the system needs to:
  1. check whether the listing is approved
  2. confirm the toy is still available
  3. ensure the user is not the owner
  4. check for overlapping bookings
  5. calculate rental, deposit, and fees
  6. create booking and payment state together
That is exactly the kind of workflow PostgreSQL is good at.
Aurora made that even stronger by giving us managed PostgreSQL on AWS, so we get the relational model we need along with a production-ready AWS database foundation.

Why not DynamoDB or Aurora DSQL

We considered the broader AWS database options carefully.

Why not DynamoDB

DynamoDB is excellent for high-throughput key-value workloads. But Another Turn’s main data model is relational.
A query in this app is rarely just “get item by ID.” It is more likely to be something like:
Show approved ride-on toys near this neighborhood, suitable for ages two to four, available this weekend, under a weekly price, from verified owners.
Those queries evolve as the product evolves. PostgreSQL gives us more flexibility for that type of search, filtering, reporting, and admin querying.

Why not Aurora DSQL

Aurora DSQL would make more sense if the product needed distributed writes across multiple regions.
But Another Turn is intentionally hyperlocal:
  • toys are local
  • pickup is local
  • bookings are local
  • the first launch is region-focused
We do not currently need active-active multi-region writes.
So the choice was deliberate:
  • Aurora PostgreSQL for relational marketplace workflows
  • not DynamoDB because our core workload is not high-throughput NoSQL
  • not Aurora DSQL because we do not yet need distributed multi-region writes

How the database is structured

The schema is organized around the actual product flows.
Important data areas include:
  • users and sessions
  • toy listings
  • listing images
  • categories
  • saved items
  • bookings
  • availability
  • messages
  • notifications
  • payments
  • Razorpay callbacks
  • refunds
  • owner verification
  • admin moderation
  • trust and safety reports
The schema is bootstrapped through ordered SQL scripts in the scripts/ folder.
I used raw SQL with pg instead of an ORM because I wanted the database behavior to stay explicit. That made it easier to reason about:
  • joins
  • indexes
  • constraints
  • idempotency keys
  • payment callback handling
  • admin access checks
  • approval-gated public queries
For this kind of app, clarity in the database mattered more than abstraction.

Keeping listings safe with moderation

Because the app deals with children’s toys and parent-to-parent exchange, listings are not treated as automatically public.
A listing has an approval state in Aurora PostgreSQL. Public browse and search queries only return approved listings.
Admins can:
  • approve listings
  • reject listings
  • record rejection reasons
  • review owner verification
  • inspect reports
  • handle moderation issues
This means the database is not just storing toy data. It is also storing the trust and safety layer of the marketplace.

Handling payments with idempotency

Payments are handled through Razorpay.
One important learning was that payment callbacks need to be treated carefully. A payment provider may retry a callback, or the same event may be received more than once.
To avoid duplicate processing, payment state is persisted in Aurora PostgreSQL with callback records and idempotency keys.
The database stores things like:
  • Razorpay order ID
  • Razorpay payment ID
  • verification timestamp
  • callback payload
  • invoice or receipt number
  • commission amount
  • owner payout amount
  • refund status
This helps the app safely ignore duplicate callbacks and maintain a clear audit trail.

Uploads and emails

Aurora PostgreSQL stores the marketplace state, but it does not store large files.
AWS S3 is used for:
  • toy listing images
  • user avatars
  • claim or moderation evidence
The database stores the S3 object keys and metadata.
AWS SES is used for:
  • verification
  • password reset
  • booking notifications
  • payment and refund updates
  • listing approval or rejection messages
Aurora remains the source of truth for workflow state, while S3 and SES handle the infrastructure jobs they are designed for.

Challenges

The hardest part was keeping the UI and backend state aligned.
A marketplace has many moving parts. A toy can be:
  • drafted
  • submitted
  • pending review
  • approved
  • booked
  • paid
  • picked up
  • returned
  • disputed
  • removed
Each state affects what the user, owner, and admin are allowed to do.
Some of the main challenges were:
  • keeping schema changes aligned with UI changes
  • handling Razorpay callbacks without false failure states
  • preventing duplicate payment processing
  • keeping setup scripts from damaging existing data
  • ensuring unapproved listings never appeared in public browse flows
  • keeping admin-only state separate from public marketplace state
  • managing S3 and SES configuration in production-style paths
The project became much more stable once the database model became clearer.

What I learned

The biggest learning was that marketplaces are coordination systems.
The UI matters, but the real complexity is in the state:
  • Who owns this item?
  • Who can book it?
  • Has it been approved?
  • Is the payment verified?
  • Has a refund started?
  • Can this user review it?
  • Should this listing be public?
  • Has this callback already been processed?
That is why the database choice matters.
For Another Turn, Aurora PostgreSQL gave the app a reliable foundation for relational data, transactions, search, moderation, and payments.
I also learned that server boundaries matter. The browser should not directly access the database. The app now follows a clean flow where client components call API routes, route handlers call server-only modules, and server modules use the database entry point.

What’s next

The next steps for Another Turn are:
  • improve search and location-aware discovery
  • add stronger owner and parent verification
  • expand email notifications through SES
  • improve invoice and receipt rendering
  • add better audit logs for support workflows
  • strengthen booking availability checks
  • improve admin dashboards for payments and moderation
  • add analytics for reuse, savings, and avoided purchases
The long-term goal is to make toy reuse feel normal, local, and trustworthy.
Another Turn started from a simple observation: many toys are still useful long after one child has outgrown them.
With Vercel, Aurora PostgreSQL, S3, SES, and Razorpay, the project became a real end-to-end marketplace where unused toys can find another child, another home, and another turn.
#H0Hackathon.
Try it out here: https://v0-toy-rental-marketplace.vercel.app
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article