This project demonstrates a pattern for running a “backend on the client,” using in-memory Fakes to simulate real server behavior and a simple business rules engine, all wired up with TanStack Router for navigation.
-
lib/rule-engine A sample rules engine that allows you to evaluate, apply, or chain business rules.
-
A fakes demo with TanStack Router A working example of how to toggle between fake (in-memory) and real (API-based) repositories using TanStack Router. The fake repositories simulate backend calls, making it easy to develop and test frontend features without relying on an actual backend.
Fakes are in-memory or mock implementations of repositories or services that typically would communicate with a real backend (e.g., an HTTP API or a database). In this context:
- FakeHelloRepository simulates fetching or posting data for “Hello” messages.
- LoanApplicationRepositoryFake simulates reading and writing loan application data.
Fakes can be used for:
- Rapid Prototyping: Build out your UI without waiting on a real backend.
- Testing: Test flows in a controlled environment without network dependencies.
- Offline Development: Easily develop features while traveling or if your real backend is temporarily unavailable.
Inside your code (e.g., main.tsx
), you’ll see logic that chooses between fake vs. real repositories based on
an environment variable:
// main.tsx
const isFake = import.meta.env.VITE_FAKE === "true";
const helloRepository = isFake
? new HelloRepositoryFake()
: new HelloRepositoryImpl();
-
Check
VITE_FAKE
: By default, we rely onimport.meta.env.VITE_FAKE
(set in your.env
file) to decide if the app should use fake or real repositories. -
Create the Repositories:
- Fake: In-memory classes that store data in local variables or arrays.
- Impl: Classes that communicate with actual APIs or databases.
-
Pass Repositories into the Router Context: We then pass the chosen repositories to TanStack Router’s context so any route (or component) in the tree can access them.
If you want your TanStack Router project to use in-memory fakes for local development or testing:
-
Create Your Repositories Define both fake and real implementations. For example:
// domain/HelloRepository.ts export interface HelloRepository { getGreeting(): Promise<string>; // ... } export class FakeHelloRepository implements HelloRepository { async getGreeting() { return "Hello from Fake!"; } } export class HelloRepositoryImpl implements HelloRepository { async getGreeting() { // e.g., fetch from an actual API const response = await fetch("/api/hello"); return response.text(); } }
-
Use an Environment Variable Create or update your
.env
file:# .env VITE_FAKE=true
If
VITE_FAKE
is"true"
, the app will use the fake implementations. Otherwise, it will default to real implementations. -
Load Repositories in
main.tsx
In yourmain.tsx
, detect the environment variable and select the appropriate repository:// main.tsx import { RouterProvider, createRouter } from "@tanstack/react-router"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { HelloRepositoryFake, HelloRepositoryImpl, } from "./domain/HelloRepository"; // 1. Check the environment variable const isFake = import.meta.env.VITE_FAKE === "true"; // 2. Pick fake or real const helloRepository = isFake ? new HelloRepositoryFake() : new HelloRepositoryImpl(); // 3. Create a QueryClient const queryClient = new QueryClient(); // 4. Create and configure your TanStack Router const router = createRouter({ routeTree, defaultPreload: "intent", context: { // Make the chosen repos available to any route or component helloRepository, queryClient, }, }); // 5. Render your app const rootElement = document.getElementById("app")!; const root = ReactDOM.createRoot(rootElement); root.render( <QueryClientProvider client={queryClient}> <RouterProvider router={router} /> </QueryClientProvider>, );
-
Configuring TanStack Router with Context
To get TypeScript support for your context, you need to use createRootRouteWithContext(). For example:
import { ... createRootRouteWithContext, } from "@tanstack/react-router"; import { TanStackRouterDevtools } from "@tanstack/router-devtools"; import { LoanApplicationRepository } from "../domain/LoanApplicationRepository"; import { HelloRepository } from "../domain/HelloRepository"; ... interface RouterContext { ... helloRepository: HelloRepository; ... } export const Route = createRootRouteWithContext<RouterContext>()({ component: RootComponent, }); ... ); }
Why
createRootRouteWithContext<RouterContext>
?It tells TanStack Router the shape of your router context (the custom data you want all routes to have access to).
It ensures TypeScript picks up the correct types, so you get proper autocompletion and type-checking in your route components when using something like
useRouter().context
. -
Use the Context in Your Routes or Components Within a route component, you can grab the repository from the router context:
import { useRouter } from "@tanstack/react-router"; function HelloPage() { const { helloRepository } = useRouter().context; const [greeting, setGreeting] = React.useState(""); React.useEffect(() => { helloRepository.getGreeting().then(setGreeting); }, [helloRepository]); return <div>{greeting}</div>; } export default HelloPage;