Building a full-stack application usually starts out easy. You set up your routes, connect your database, and everything feels great. But once you start dealing with real money, third-party webhooks, and background jobs, things get complicated fast.
I recently finished building Eventful, a ticketing platform where users can host events, buy tickets, and get scanned in at the door via QR codes.
I used Next.js 16 and Tailwind 4 for the frontend, and NestJS, Prisma, Postgres, and Redis for the backend.
Here are the specific, real-world problems I ran into while building it and exactly how I fixed them.
1. Never trust the frontend for payments
When a user buys a paid ticket, they are redirected to Paystack (the payment gateway). When they finish, Paystack redirects them back to my frontend.
Initially, I thought about confirming the ticket when the frontend loads the success page. Don't do this. Users can easily spoof URLs.
The Fix: I implemented a two-phase commit pattern.
The backend creates a transaction record marked
PENDINGand returns a checkout link.The user pays.
Paystack fires a server-to-server Webhook to my backend.
4.The backend verifies the HMAC signature, updates the status to SUCCESS, and generates the ticket.
Takeaway: The frontend is just a UI. The backend webhook is the only source of truth for money.
2. Webhook signatures require the raw body
To verify that the webhook actually came from Paystack, you have to hash the payload using your secret key and compare it to their signature header.
My hashes kept failing. Why? Because NestJS parses incoming JSON requests automatically. JSON.stringify(req.body) strips out whitespace and changes the exact byte order. Paystack signs the raw bytes.
The Fix: I had to configure NestFactory.create() to enable rawBody: true. I then created a custom TypeScript interface to access req.rawBody (a Buffer) to run the crypto hash perfectly every time.
- The
@nestjs/cache-managerv3 silent failure I wanted to cache my event listings in Upstash Redis. I installed the dependencies, set up the store, and... nothing happened. No errors, no crashes, but Redis remained empty. It turns out@nestjs/cache-managerv3 introduced a massive breaking change. They moved away from a singularstoreto astoresarray using Keyv adapters. Passing the oldcache-manager-redis-yetconfiguration is silently ignored by the compiler, and the app just falls back to local memory caching without telling you.
The Fix : I ripped out the old adapter, migrated to @keyv/redis, passed it inside a stores: [] array, and made sure to use the rediss://connection string because Upstash strictly requires TLS. Suddenly, the cache started working perfectly.
4. Graceful degradation is better than downtime
I implemented rate-limiting using Redis. But what happens if the Redis server blips?
By default, if ioredis fails to connect after 20 retries, it throws a MaxRetriesPerRequestError. Because my rate-limiter was sitting in front of every route, a Redis timeout meant the entire application threw 500 Internal Server Errors.
The Fix: I wrote a custom wrapper around the ThrottlerStorage interface. Now, if the rate-limiter catches a Redis connection error, it logs a warning but allows the request to pass through. I would rather deal with a few seconds of un-rate-limited traffic than take down the entire API for all users.
Conclusion
Tutorials rarely teach you about TLS connection strings, webhook payload buffers, or failing gracefully. Building Eventful forced me to stop thinking like a framework user and start thinking like a systems engineer.
(Note: If your team is hiring, I am actively looking for Full-Stack and Backend roles. Feel free to reach out to me on LinkedIn or check out the code for this project on my GitHub
🔗 Link to Live App
🔗 Link to Backend - Repo
🔗 Link to Frontend - Repo



Top comments (0)