aspire-ts
.NET Aspire for JavaScript/TypeScript
Orchestrate JavaScript and TypeScript applications using .NET Aspire's AppHost. Aspire treats JS/TS apps as first-class resources alongside .NET services, containers, and cloud infrastructure — providing service discovery, telemetry, health monitoring, and deployment from a single code-first model.
Important: The AppHost is always written in C#, even when orchestrating JS/TS apps. The
Aspire.Hosting.JavaScriptNuGet package provides the hosting APIs.
Prerequisites
- .NET SDK 10.0+ — Required for the Aspire AppHost
- Aspire CLI installed (
aspire --version) - Node.js 22+ with npm, yarn, or pnpm
- NuGet package:
Aspire.Hosting.JavaScriptin your AppHost project
# Install in AppHost project
dotnet add package Aspire.Hosting.JavaScript
# For file-based AppHost (apphost.cs), use directive:
#:package Aspire.Hosting.JavaScript@13.1.0
Resource Types
Three methods to add JS/TS apps to the AppHost:
| Method | Use Case | Entry Point |
|---|---|---|
AddViteApp |
Vite-based apps (React, Vue, Svelte) | npm script (dev) |
AddNodeApp |
Node.js apps with direct file entry | JavaScript file |
AddJavaScriptApp |
General JS apps with npm scripts | npm script (dev) |
AddViteApp — Vite Applications
Best for React, Vue, Svelte, and other Vite-based frontends.
var builder = DistributedApplication.CreateBuilder(args);
var api = builder.AddProject<Projects.Api>("api");
var frontend = builder.AddViteApp("frontend", "./packages/web")
.WithExternalHttpEndpoints()
.WithReference(api)
.WaitFor(api);
builder.Build().Run();
Defaults:
- Runs
devscript during development - Runs
buildscript when publishing - Auto-generates Dockerfile for production
- Auto-detects package manager from lockfiles
AddNodeApp — Node.js Applications
For Node.js servers with a direct JavaScript entry point.
var api = builder.AddNodeApp("api", "./packages/api", "src/server.js")
.WithNpm()
.WithHttpEndpoint(port: 3000, env: "PORT")
.WithHttpHealthCheck("/health");
Parameters:
name— Resource name in Aspire dashboardappDirectory— Path to directory containing the appscriptPath— Path to the JS file to run (relative to appDirectory)
AddJavaScriptApp — Generic JavaScript
For any JS app using npm scripts. Foundation for both AddViteApp and AddNodeApp.
var app = builder.AddJavaScriptApp("app", "./frontend")
.WithHttpEndpoint(port: 3000, env: "PORT");
Defaults: Uses npm, runs dev script in development, build script when publishing.
Package Managers
npm (Default)
var app = builder.AddViteApp("frontend", "./frontend");
// npm is used automatically
// Customize npm behavior:
var app2 = builder.AddViteApp("frontend", "./frontend")
.WithNpm(installCommand: "ci", installArgs: ["--legacy-peer-deps"]);
yarn
var app = builder.AddViteApp("frontend", "./frontend")
.WithYarn();
// With custom args:
var app2 = builder.AddViteApp("frontend", "./frontend")
.WithYarn(installArgs: ["--immutable"]);
pnpm
var app = builder.AddViteApp("frontend", "./frontend")
.WithPnpm();
// With custom args:
var app2 = builder.AddViteApp("frontend", "./frontend")
.WithPnpm(installArgs: ["--frozen-lockfile"]);
Auto-install: All package managers install dependencies automatically before running. In publish mode, deterministic install commands are used (npm ci, yarn install --immutable, pnpm install --frozen-lockfile) when lockfiles exist.
Service Discovery
When you call .WithReference(api), Aspire injects environment variables into the JS app:
var api = builder.AddNodeApp("api", "./api", "server.js")
.WithNpm()
.WithHttpEndpoint(port: 3000, env: "PORT");
var frontend = builder.AddViteApp("frontend", "./frontend")
.WithReference(api) // Injects API_HTTP and API_HTTPS env vars
.WaitFor(api);
Consuming in JavaScript/TypeScript
// Access injected service URLs
const apiUrl = process.env.API_HTTP || process.env.API_HTTPS;
// For databases and other resources
const redisHost = process.env.CACHE_HOST;
const redisPort = process.env.CACHE_PORT;
const pgConnectionString = process.env.POSTGRESDB_CONNECTIONSTRING;
Environment variable naming: {RESOURCE_NAME}_{PROPERTY} (uppercase, underscores).
| Resource Type | Variables Injected |
|---|---|
| HTTP service | {NAME}_HTTP, {NAME}_HTTPS |
| Redis | {NAME}_HOST, {NAME}_PORT, {NAME}_URI |
| PostgreSQL DB | {NAME}_HOST, {NAME}_PORT, {NAME}_URI, {NAME}_DATABASENAME |
| Connection string | ConnectionStrings__{name} |
Endpoints & Networking
Configure HTTP endpoint
var app = builder.AddViteApp("frontend", "./frontend")
.WithHttpEndpoint(port: 3000, env: "PORT");
port— Host port (exposed to browser/other services)env— Environment variable name set to the internal target port- Aspire inserts a reverse proxy between host port and app port
External endpoints
Mark endpoints as externally accessible (browser-facing):
var frontend = builder.AddViteApp("frontend", "./frontend")
.WithExternalHttpEndpoints();
HTTPS with developer certificate (Vite)
var app = builder.AddViteApp("frontend", "./frontend")
.WithHttpsEndpoint(env: "PORT")
.WithHttpsDeveloperCertificate();
Aspire auto-injects HTTPS config into Vite without modifying vite.config.js.
Scripts & Arguments
Custom run/build scripts
var app = builder.AddJavaScriptApp("app", "./app")
.WithRunScript("start") // Use "npm run start" instead of "dev"
.WithBuildScript("prod"); // Use "npm run prod" instead of "build"
Pass arguments
// Option 1: WithArgs
var app = builder.AddViteApp("frontend", "./frontend")
.WithArgs("--no-open");
// Option 2: Custom script in package.json
// package.json: { "scripts": { "dev:no-open": "vite --no-open" } }
var app2 = builder.AddViteApp("frontend", "./frontend")
.WithRunScript("dev:no-open");
Custom Vite config file
var app = builder.AddViteApp("frontend", "./frontend")
.WithViteConfig("./vite.production.config.js");
OpenTelemetry for Node.js
Configure telemetry so JS/TS apps report to the Aspire dashboard.
Install packages
npm install @opentelemetry/api @opentelemetry/sdk-node \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-grpc \
@opentelemetry/exporter-metrics-otlp-grpc
Create telemetry.js (must be imported first)
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc');
const { OTLPMetricExporter } = require('@opentelemetry/exporter-metrics-otlp-grpc');
const { PeriodicExportingMetricReader } = require('@opentelemetry/sdk-metrics');
const { resourceFromAttributes } = require('@opentelemetry/resources');
const { ATTR_SERVICE_NAME } = require('@opentelemetry/semantic-conventions');
const otlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4317';
const sdk = new NodeSDK({
resource: resourceFromAttributes({ [ATTR_SERVICE_NAME]: 'frontend' }),
traceExporter: new OTLPTraceExporter({ url: otlpEndpoint }),
metricReader: new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter({ url: otlpEndpoint }),
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
Import first in app entry point
require('./telemetry'); // Must be first!
const express = require('express');
// ... rest of app
Aspire auto-sets OTEL_EXPORTER_OTLP_ENDPOINT for orchestrated resources.
Integrations (Redis, PostgreSQL, etc.)
Redis
// AppHost
var cache = builder.AddRedis("cache");
var app = builder.AddNodeApp("api", "./api", "server.js")
.WithNpm()
.WithReference(cache); // Injects CACHE_HOST, CACHE_PORT, CACHE_URI
// Node.js
const redis = require('redis');
const client = redis.createClient({
socket: { host: process.env.CACHE_HOST, port: process.env.CACHE_PORT }
});
PostgreSQL
// AppHost
var postgres = builder.AddAzurePostgresFlexibleServer("postgres")
.RunAsContainer();
var db = postgres.AddDatabase("mydb");
var app = builder.AddNodeApp("api", "./api", "server.js")
.WithNpm()
.WithReference(db); // Injects MYDB_HOST, MYDB_PORT, MYDB_URI, etc.
// Node.js
const { Client } = require('pg');
const client = new Client({
host: process.env.MYDB_HOST,
port: process.env.MYDB_PORT,
database: process.env.MYDB_DATABASENAME,
});
Polyglot Patterns
Mix JS/TS apps with .NET, Python, and containers in one AppHost:
var builder = DistributedApplication.CreateBuilder(args);
// Infrastructure
var cache = builder.AddRedis("cache");
var db = builder.AddPostgres("postgres").AddDatabase("appdb");
// .NET API
var api = builder.AddProject<Projects.Api>("api")
.WithReference(db)
.WithReference(cache);
// Node.js microservice
var worker = builder.AddNodeApp("worker", "./worker", "index.js")
.WithNpm()
.WithReference(cache);
// React frontend
var frontend = builder.AddViteApp("frontend", "./frontend")
.WithExternalHttpEndpoints()
.WithReference(api)
.WaitFor(api);
builder.Build().Run();
Deployment
aspire run (Development)
aspire run
JS/TS apps run as Node.js processes (not containerized). Aspire dashboard launches with logs, traces, and metrics.
aspire deploy (Containerized)
aspire deploy
Aspire auto-generates Dockerfiles, builds container images, and deploys. JS apps become Docker containers.
aspire publish (CI/CD artifacts)
aspire publish --output-path ./aspire-output
Generates docker-compose.yaml and .env files for deployment.
Docker Compose output
Aspire generates production-ready Docker Compose for JS/TS apps:
- Auto-generated multi-stage Dockerfile
- Deterministic dependency install from lockfiles
- Build step runs production build script
- Proper environment variable injection
Common Patterns
Health checks
var api = builder.AddNodeApp("api", "./api", "server.js")
.WithNpm()
.WithHttpHealthCheck("/health");
Persistent containers
var redis = builder.AddRedis("cache")
.WithLifetime(ContainerLifetime.Persistent);
Wait for dependencies
var frontend = builder.AddViteApp("frontend", "./frontend")
.WithReference(api)
.WaitFor(api) // Wait for API to be ready
.WaitFor(cache); // Wait for Redis to be ready
Environment variables
var app = builder.AddViteApp("frontend", "./frontend")
.WithEnvironment("NODE_ENV", "development")
.WithEnvironment("VITE_API_URL", api.GetEndpoint("http"));
Quick Reference
| Task | Code |
|---|---|
| Add Vite app | builder.AddViteApp("name", "./path") |
| Add Node.js app | builder.AddNodeApp("name", "./path", "server.js") |
| Use yarn | .WithYarn() |
| Use pnpm | .WithPnpm() |
| Set port via env | .WithHttpEndpoint(port: 3000, env: "PORT") |
| Reference service | .WithReference(otherResource) |
| Wait for dependency | .WaitFor(otherResource) |
| External endpoint | .WithExternalHttpEndpoints() |
| Health check | .WithHttpHealthCheck("/health") |
| Custom script | .WithRunScript("start") |
| Pass args | .WithArgs("--no-open") |
| HTTPS dev cert | .WithHttpsDeveloperCertificate() |
| Custom Vite config | .WithViteConfig("./custom.config.js") |