Skip to main content

Architecture Dilemma: Building a production ready application 

18 Jul 2025

I have an exciting project to work on, where I get to use the products from Cloudflare extensively - Cloudflare R2, Workers, D1, Durable Objects, Access (maybe?) and more. While the project is still in its early stages, I want to share some of the architectural decisions and dilemmas I faced while building the application.

Like most of the applications, this application needs to have the following:

  • A secure web application that can be accessed by users
  • A secure web application that can be accessed by the admin(s)
  • A secure backend that can be accessed by the user app and admin app

Now obviously, there are only certain functions that the user app should access, and only certain functions that the admin app should access. The backend should be able to differentiate between the two and allow access accordingly. For the initial MVP, I decided to put authentication and authorization on hold, and focus on building the core functionality of the application.

The dilemma I faced was how to structure the application to ensure that it is secure, maintainable, and scalable. Here are some of the key considerations:

  1. Shared resources: Structuring the application in a way that they can access the same backend resources (like D1 database, R2 bucket) without compromising security. This means ensuring that the user app and admin app can only access the resources they are allowed to.

  2. Shared design: Creating a consistent design language and user experience across both the user app and admin app, while still allowing for the necessary differences in functionality and access.

  3. Monorepo vs. Polyrepo: Deciding whether to use a monorepo structure for both the user app and admin app, or to separate them into different repositories. A monorepo can simplify shared code management and deployment, but may complicate the build process and increase the size of the repository. While, a polyrepo can provide clearer separation of concerns and potentially better performance, it may lead to duplication of code and resources.

From these considerations, I decided to go with a monorepo structure for the following reasons:

  • Shared code: Both the user app and admin app will share a significant amount of code, including the backend API, shared components, and utilities. A monorepo allows for easier management of shared code and reduces duplication.
  • Simplified deployment: With a monorepo, I can deploy both the user app and admin app together, ensuring that they are always in sync and reducing the complexity of managing multiple deployments.
  • Consistent tooling: Using a monorepo allows me to use the same build tools, linters, and testing frameworks across both applications, ensuring a consistent development experience and reducing the overhead of managing different configurations.
  • Easier refactoring: If I need to make changes that affect both the user app and admin app, a monorepo makes it easier to refactor code without having to coordinate changes across multiple repositories.
  • Better context for AI: This is a working theory, but I believe that having a monorepo will provide better context for AI tools that can assist in code generation, refactoring, and testing. AI tools can leverage the shared codebase to provide more accurate suggestions and improvements.

However, I also need to be mindful of the potential downsides of a monorepo:

  • Build complexity: As the application grows, the build process may become more complex, requiring careful management of dependencies and build configurations.
  • Repository size: A monorepo can lead to a larger repository size, which may impact performance and make it more difficult to manage version control.
  • Access control: I need to ensure that the access control mechanisms are robust enough to prevent unauthorized access to sensitive resources, especially since both the user app and admin app will share the same backend resources.
  • Tooling limitations: Some tools may not work well with monorepos (support for shadcn in monorepos is in canary), requiring additional configuration or workarounds to ensure compatibility.

In conclusion, the architecture dilemma of building a production-ready application involves balancing security, maintainability, and scalability. By choosing a monorepo structure, I can leverage shared resources and code while ensuring a consistent development experience. However, I must remain vigilant about the potential challenges that come with this approach and implement robust access control mechanisms to protect sensitive resources.

Now that I was confident with going the monorepo route, the other major dilemmas I faced was structuring the monorepo. There are three potential options:

Option 1: Separate user and admin fullstack apps in the same monorepo

This option involves creating two separate fullstack applications within the same monorepo, one for the user app and one for the admin app. Each application would have its own frontend and backend code, but they would share common resources like the D1 database and R2 bucket. For the shared resources, I would create a separate package that both applications can import - access to D1 and R2, shared components, utilities, etc. This structure allows for clear separation of concerns between the user app and admin app, while still enabling shared code and resources. It also simplifies deployment, as both applications can be deployed together.

However, this option may require careful management of shared resources to ensure that both applications can access them securely. One of the other reasons I did not consider this option is the complexity of setting up the resources for local development. While I can setup the D1 database and R2 bucket for both applications, it would require additional configuration and management to ensure that both applications can access them correctly. This could lead to potential issues during development and testing, especially if the applications are not properly isolated. This might be something I revisit in the future, but for now, I wanted to keep things simple.

Option 2: Single fullstack app with user and admin routes

The second option involves creating a single fullstack application that serves both the user app and admin app, with separate routes for each. The application would have a shared frontend and backend codebase, with different routes for the user app and admin app. The backend would handle authentication and authorization to ensure that the user app and admin app can only access the resources they are allowed to.

This option simplifies the overall architecture by reducing the number of applications and deployments, while still allowing for shared code and resources. It also makes it easier to manage shared components and utilities, as they can be used across both the user app and admin app without duplication.

However, this option may lead to a more complex codebase, as the frontend and backend code will need to handle both user and admin functionality. It also requires careful management of routes and components to ensure that the user app and admin app are properly isolated and secure. Moreover, from the initial discussion with the team, it was clear that they prefer to have separate applications for the user app and admin app, as it would allow for clearer separation of concerns and potentially better performance and security.

Option 3: Separate user and admin frontend apps and a shared backend App

The third option involves creating separate frontend applications for the user app and admin app, while sharing a common backend application. This I believe, is a common architecture pattern. The frontend applications would handle the user interface and client-side logic, while the backend application would provide the API and shared resources like the D1 database and R2 bucket. The frontend applications would communicate with the backend application to access the shared resources and perform actions. This structure allows for clear separation of concerns between the user app and admin app, while still enabling shared code and resources.

I chose this option for the following reasons:

  • Separation of concerns: The user app and admin app can be developed and maintained independently, allowing for clearer separation of concerns and potentially better performance.
  • Shared backend: The backend application can handle authentication and authorization, ensuring that the user app and admin app can only access the resources they are allowed to. This also simplifies the management of shared resources like the D1 database and R2 bucket, as they can be accessed through a single backend application.
  • Scalability: As the application grows, it may be easier to scale the frontend and backend applications independently, allowing for better performance and resource management.

With this option, I can also leverage Cloudflare’s products effectively:

  • Cloudflare Workers: I can use Workers to handle the API requests and serve the frontend applications, ensuring low latency and high availability.
  • Cloudflare D1: I can use D1 as the primary database for both the user app and admin app, ensuring that data is consistent and accessible across both applications.
  • Cloudflare R2: I can use R2 for storing and serving static assets, such as images and files, ensuring that they are accessible to both the user app and admin app.
  • Cloudflare Durable Objects: I can use Durable Objects for managing stateful interactions, such as user sessions and real-time updates.
  • Cloudflare Access: I can use Access to secure the admin app, ensuring that only authorized users can access it.
  • Workers Analytics: I can use Workers Analytics to monitor the usage of the user app, and allowing the admin app to access the analytics data for monitoring and reporting purposes.

Conclusion

In conclusion, the architecture dilemma of building a production-ready application involves careful consideration of security, maintainability, and scalability. After evaluating the options, I decided to go with a monorepo structure with separate frontend applications for the user app and admin app, sharing a common backend application. This approach allows for clear separation of concerns, and shared resources.

I am excited to continue working on this project and will share more updates as I progress. If you have any thoughts or suggestions on the architecture, feel free to reach out!

Last updated on 18 Jul 2025