Introduction
I recently released a personal project: a full-stack restaurant reservation app built using React Router Framework Mode and TypeScript. In this article, I will explain the architecture I used to build the application, including how I implemented batch processing and scheduled email delivery to support real-world operational needs.
🔗 Live Demo: https://my-booking.tech/
📂 Article: https://dev.to/yuichi_nabeshima_canada/bringing-mvc-back-a-full-stack-architecture-with-react-router-frame-work-mode-3c8b
Architecture
For this project, the web server is a full-stack application using React Router. The architecture follows a traditional MVC-like structure, where the front-end and back-end servers are not separated but integrated into one. I implemented a custom batch process for handling data updates and other operations, deploying a dedicated batch server separate from the web server.
Why separate the batch server?
- To prevent batch processing from consuming too much memory and impacting the web server's performance.
- To create an environment similar to local development for testing batch processes in production.
I deployed the app on fly.io, which offers free-tier instances that fit within the limits for web, batch, and DB servers (three instances). However, please note that keeping the batch server always running may exceed the $5 free credit.
The batch processing logic itself is implemented within the same full-stack application, and executed via command-line scripts like npm run batch
.
Here's how I defined the batch script in my package.json
// package.json
"scripts": {
"batch": "tsx ./app/.server/batch/runBatch.ts",
For executing batch jobs in production, I access the batch server via fly ssh console
and run the job with npm run batch
. You can find the source code on GitHub at the link below.
【GitHub】https://github.com/YuichiNabeshima/my-booking
The web server is optimized for performance by using a production build before deployment. In contrast, the batch server is deployed without a build step, since it only needs to run command-line scripts and doesn’t serve client assets.
Implementing Scheduled Tasks
With batch jobs in place, I also needed a way to schedule automatic execution at specific times or intervals. Following the recommended method from fly.io, I installed supercronic in the Dockerfile of the batch server and added the execution logic.
The key advantage here is that by running scheduled tasks at the server level, we avoid accidental execution of unnecessary tasks on development or local servers (such as sending test emails) during testing.
Batch Server for Email Delivery
For example, I designed the email delivery system to not send emails instantly. Instead, email requests are added to a Mail Queue table. A batch job runs every minute to process and send emails from the queue. The batch job processes each record, removes it from the queue, and logs it in the Mail Log table.
Advantages of this Mail Queue Approach:
- Handles bulk email deliveries more effectively by distributing the load
- Makes it easier to manage email logs
- Scalable for larger applications
However, this method does have a downside: the system is not real-time, as the batch job runs only once per minute. For larger-scale systems, a job queue system like BullMQ could be a better choice, as it allows delayed job execution, retries, and parallel processing, making the architecture more scalable.
Conclusion
In this article, I’ve covered the basic architecture of the app I built and discussed the email delivery system powered by batch processing. I hope this gives you some insights into handling background jobs and building full-stack applications with React Router and TypeScript.
I will be posting a more detailed breakdown of the batch implementation in a future article.
If you’re interested in learning more about full-stack architecture, React Router, or background job processing, feel free to reach out!
Thank you for reading! 😊
Top comments (0)