Here is a breakdown of the technical choices I made while building ResumeEngine, and why I didn't do things the "standard" way.
The Choice: I set up an NPM Workspace instead of just having a client folder and a server folder that don't talk to each other. The Reason:
- Validating forms in React is annoying. Validating API requests in Express is annoying. Keeping them perfectly synced is a nightmare.
- By putting all my Zod schemas in a shared package, I only write the rules once.
- The React forms use these rules to show error messages before you even click submit. If you bypass the frontend, the Express backend uses the exact same rules to reject the bad data. No duplicated code.
The Choice: I use SSE to stream backend pipeline progress updates and manage long-running tailoring requests instead of WebSockets. The Reason:
- During tailoring, communication is strictly one-way (Server -> Client). WebSockets are built for full-duplex two-way chat apps and add unnecessary connection overhead.
- SSE runs over standard HTTP (
text/event-stream). It allows the backend to stream live status events (e.g., "Validating Job Description...", "Analyzing profile...") so users see progress steps in real time while the AI generates the complete JSON payload. - Crucially, SSE lets the backend handle connection timeouts cleanly and automatically refund usage credits if a user disconnects mid-pipeline.
The Choice: The server generates PDFs using one instance of Chromium that stays open all the time. The Reason:
- Turning HTML into a PDF with Puppeteer is incredibly heavy on RAM. If 10 people try to download a PDF at the exact same time and the server tries to launch 10 fresh headless browsers, a small AWS EC2 instance will just run out of memory and crash.
- I fixed this by launching the browser once when the server starts. When someone needs a PDF, it just opens a new tab, prints the PDF, and closes the tab. It keeps the memory footprint way down.
The Choice: I use Groq's API (specifically Llama 3.3 70B) for the AI stuff. The Reason:
- Resume tailoring means generating a lot of text. Traditional APIs like OpenAI have noticeable lag when they try to spit out massive JSON payloads.
- Groq runs on specialized hardware (LPUs) that makes token generation ridiculously fast. It makes the UI feel instantly responsive instead of feeling sluggish.
The Choice: I wrapped the Groq API calls in a Circuit Breaker pattern. The Reason:
- Sometimes external APIs go down or get incredibly slow.
- If Groq stops responding, I don't want my server to hang forever waiting for an answer. The circuit breaker notices the API is failing and "trips", instantly returning an error for the next few minutes. This keeps my server from getting bogged down with dead requests.
The Choice: The frontend turns the binary PDF data into a Blob URL to show it on screen. The Reason:
- I didn't want to save temporary PDF files on my server or mess around with an AWS S3 bucket.
- The backend just builds the PDF in memory, sends the raw bytes to the frontend, and the browser handles displaying it. It's cleaner, faster, and better for privacy.