Clone, run, and explore this sample
Visit counter with Express backend, Redis caching, and React TypeScript frontend using YARP.
The entry point that composes every resource and dependency in this sample's distributed application.
import { createBuilder } from "./.aspire/modules/aspire.mjs";
const builder = await createBuilder();const executionContext = await builder.executionContext();
const dc = await builder.addDockerComposeEnvironment("dc");
const redis = await builder.addRedis("redis") .withRedisInsight() .withComputeEnvironment(dc);
const api = await builder.addNodeApp("api", "./api", "index.js") .withHttpEndpoint({ env: "PORT" }) .withHttpHealthCheck({ path: "/health" }) .withComputeEnvironment(dc) .waitFor(redis) .withReference(redis);
const frontend = await builder.addViteApp("frontend", "./frontend") .withReference(api);
await builder.addYarp("app") .withConfiguration(async (yarp) => { const apiCluster = await yarp.addClusterFromResource(api); await (await yarp.addRoute("api/{**catch-all}", apiCluster)) .withTransformPathRemovePrefix("/api");
if (await executionContext.isRunMode()) { const frontendCluster = await yarp.addClusterFromResource(frontend); await yarp.addRoute("{**catch-all}", frontendCluster); } }) .withExternalHttpEndpoints() .withBrowserLogs() .publishWithStaticFiles(frontend) .withComputeEnvironment(dc) .withExplicitStart();
await builder.build().run();Visit counter with Express backend, Redis caching, and React TypeScript frontend using YARP.
Architecture
Section titled ArchitectureRun Mode:
flowchart LR
Browser --> YARP
YARP -->|/api/*| Express[Express API]
YARP --> Vite[Vite Dev Server<br/>HMR enabled]
Express --> RedisPublish Mode:
flowchart LR
Browser --> YARP[YARP serving<br/>Vite build output<br/>'npm run build']
YARP -->|/api/*| Express[Express API]
Express --> RedisWhat this demonstrates
Section titled What this demonstrates- addNodeApp: Express backend with Redis integration
- addViteApp: React + TypeScript frontend with Vite
- addYarp: Single endpoint for frontend and API with path transforms
- addRedis: In-memory data store with automatic connection injection
- publishWithStaticFiles: Frontend embedded in YARP for publish mode
- Dual-Mode Operation: Vite HMR in run mode, Vite build output in publish mode
Running
Section titled Runningaspire runSecurity notes
Section titled Security notesThis sample keeps the Express endpoints public and unauthenticated so the visit counter is easy to run and inspect locally. It does not add CSRF protection, rate limiting, authentication, or authorization, so treat it as a demo pattern rather than production-ready API security.
The API accepts visits only for the sample's built-in pages and uses bounded Redis operations for stats and reset. Production services still need deliberate cache and key management to avoid unbounded key cardinality, accidental deletion of unrelated keys, or expensive enumeration against shared Redis instances.
For production applications, add real authN/authZ, request rate limiting, CSRF protection where browser credentials are used, bounded request bodies, and Redis security controls appropriate for your deployment. See the Node.js security best practices, Express body parser limits documentation, Redis security documentation, and OWASP API Security Top 10.
Commands
Section titled Commandsaspire run # Run locallyaspire deploy # Deploy to Docker Composeaspire do docker-compose-down-dc # Teardown deploymentKey aspire patterns
Section titled Key aspire patternsYARP Routing - Single endpoint with path-based routing:
await builder.addYarp("app") .withConfiguration(async (yarp) => { const apiCluster = await yarp.addClusterFromResource(api); await (await yarp.addRoute("api/{**catch-all}", apiCluster)) .withTransformPathRemovePrefix("/api");
if (await executionContext.isRunMode.get()) { const frontendCluster = await yarp.addClusterFromResource(frontend); await yarp.addRoute("{**catch-all}", frontendCluster); } }) .publishWithStaticFiles(frontend);Redis Connection - Automatic connection string injection via REDIS_URI environment variable
WaitFor - Ensures Redis starts before API
Sample screenshots
Select the image to zoom in.