Aspireify an existing app with a TypeScript AppHost

This guide shows you how to add Aspire orchestration to an existing JavaScript or TypeScript application while using a TypeScript AppHost (apphost.ts). You'll initialize Aspire with aspire init --language typescript, model your resources in apphost.ts, and run the app locally with Aspire.

[!NOTE] Definition Aspireify (verb): To transform an existing application into a distributed, observable, and orchestrated system by adding Aspire—no cape required, just a few commands!

Why add Aspire to an existing app?

As distributed applications grow, coordinating multiple services becomes a tangled web of configuration files, hard-coded URLs, and fragile startup scripts. You're juggling connection strings across environments, manually wiring service dependencies, and struggling to trace issues across your microservices. Development setup becomes a ritual of precision—start the database, then the cache, then service A before service B—and any misstep sends you back to square one.

Aspire cuts through this complexity with a unified orchestration layer that treats your entire application as a cohesive system. Define your services and their relationships once in code (the AppHost), and Aspire handles service discovery, injects configuration automatically, and provides a dashboard with logs, traces, and metrics out of the box. When you're orchestrating JavaScript and TypeScript services, you get the same consistent experience from development through deployment.

The best part? You can adopt Aspire incrementally. Start with orchestration, add observability when you're ready, integrate external services as needed. Your existing codebase stays largely unchanged, and you can reverse course if Aspire isn't the right fit.

Prerequisites

Before you begin, ensure you have the following prerequisites installed:

Common requirements

  • Aspire CLI installed — For orchestration and deployment commands.
  • An existing application to add Aspire to.

Language-specific requirements

For JavaScript/TypeScript applications:

  • Node.js 22 or later installed
  • npm, yarn, or pnpm for package management
  • An existing JavaScript/TypeScript application

Example application types:

  • React, Vue, or Svelte applications (especially Vite-based)
  • Node.js/Express APIs
  • Next.js applications
  • Angular applications
  • TypeScript backend services

[!TIP] Multi-language applications If your application uses multiple languages, ensure you have all the relevant prerequisites installed. The Aspire AppHost can orchestrate services across different languages simultaneously.

Overview of the process

Adding Aspire to an existing application follows these key steps:

  1. 1

    Initialize Aspire support — Use aspire init to add the AppHost (orchestration layer)

  2. 2

    Add your applications — Register your JavaScript and TypeScript applications in the AppHost

  3. 3

    Configure telemetry (optional) — Add OpenTelemetry instrumentation for observability

  4. 4

    Add integrations (optional) — Connect to databases, caches, and message queues

  5. 5

    Run and verify — Test your application with Aspire orchestration

Initialize Aspire support

The aspire init command is the starting point for adding Aspire to your existing application. It adds an AppHost that orchestrates your services while letting those services stay in their current languages.

  1. 1

    Navigate to your project directory:

    cd /path/to/your-workspace
    
  2. 2

    Run aspire init to initialize Aspire support:

    aspire init --language typescript
    

    [!TIP] Use the TypeScript AppHost path throughout This page consistently uses aspire init --language typescript and apphost.ts in every example. If you omit the --language flag, choose TypeScript when the CLI asks which AppHost language to create.

    The aspire init command still runs in interactive mode for the remaining setup. It will:

    • Detect your existing workspace structure
    • Create a TypeScript AppHost (apphost.ts)
    • Add the generated AppHost SDK and config files
    • Install the supporting Node.js dependencies for the AppHost

What does aspire init create?

After running aspire init --language typescript, your project will have a TypeScript AppHost:

A typical monorepo with Node.js API, React storefront, and admin dashboard.

[!NOTE] The .modules folder is generated The .modules/ folder contains generated AppHost SDK files. Let the Aspire CLI manage it rather than editing those files manually.

The apphost.ts file initially contains a minimal starter:

// Aspire TypeScript AppHost
// For more information, see: https://aspire.dev
  
  
const builder = await createBuilder();
  
// Add your resources here, for example:
// const redis = await builder.addContainer("cache", "redis:latest");
// const postgres = await builder.addPostgres("db");
  
await builder.build().run();

This is your starting point. In the next section, you'll add your applications and configure their relationships.

Add your applications to the AppHost

Once you have an AppHost, you need to register your existing applications. First, install the appropriate hosting packages for your application types, then add your resources to the AppHost.

Install hosting packages

For JavaScript/TypeScript applications, add the JavaScript hosting integration to your AppHost:

aspire add javascript

This updates the generated AppHost SDK with methods like addViteApp, addNodeApp, and addJavaScriptApp.

Model your resources in the AppHost

Now update your apphost.ts file to register your applications as resources. Resources are the building blocks of your distributed application—each service, container, or infrastructure resource becomes something Aspire can orchestrate.

For JavaScript applications, use the appropriate method based on your application type:

const builder = await createBuilder();
  
// Node.js API
const api = await builder
  .addNodeApp('api', './packages/api', 'src/server.js')
  .withNpm()
  .withHttpHealthCheck({ path: '/health' });
  
// React storefront
const web = await builder
  .addViteApp('web', './packages/web')
  .withExternalHttpEndpoints()
  .withReference(api)
  .waitFor(api);
  
// Admin dashboard
const admin = await builder
  .addViteApp('admin', './packages/admin')
  .withExternalHttpEndpoints()
  .withReference(api)
  .waitFor(api);
  
await builder.build().run();

Key methods:

  • addViteApp - For Vite-based applications (React, Vue, Svelte)
  • addNodeApp - For Node.js applications
  • addJavaScriptApp - For generic JavaScript applications with npm/yarn/pnpm
    • withNpm() / withYarn() / withPnpm() - Specify package manager
  • withRunScript - Specify which npm script to run during development

[!CAUTION] JavaScript deployment note This example shows how to run existing JavaScript applications with Aspire during development. It does not, by itself, define who serves a Vite or JavaScript frontend in production.

If you plan to publish or deploy these frontend resources, read Deploy JavaScript apps to choose the correct production hosting model.

[!NOTE] Passing arguments to scripts Pass arguments to your scripts in one of two ways:

Option 1: Use withArgs to pass arguments directly

await builder.addViteApp('frontend', './frontend').withArgs(['--no-open']);

Option 2: Define custom scripts in package.json with arguments

{
  "scripts": {
    "dev": "vite",
    "dev:no-open": "vite --no-open"
  }
}
await builder.addViteApp('frontend', './frontend').withRunScript('dev:no-open');

Option 2 keeps all script configuration in package.json, making your scripts more discoverable and easier to run outside of Aspire (e.g., npm run dev:no-open).

Tip

You can mix and match JavaScript application types. Combine Node.js APIs, Vite frontends, workers, and supporting containers in the same TypeScript AppHost to model your full development environment in one place.

Connect services

The withReference calls establish service dependencies and enable service discovery:

// Omitted for brevity...
  
const api = await builder
  .addNodeApp('api', '../node-api', 'server.js')
  .withNpm();
  
await builder.addViteApp('frontend', '../react-frontend').withReference(api); // Frontend gets API_HTTP and API_HTTPS env vars
  
// Omitted for brevity...

When you call withReference, you're declaring a dependency between resources. Aspire handles the rest—automatically injecting configuration at runtime and during deployment so your services can communicate seamlessly, whether running locally or in production.

Add telemetry configuration (optional)

TypeScript Node.js applications can also send telemetry using OpenTelemetry. The example below uses TypeScript and ESM imports so it fits naturally into a TypeScript codebase. If your app uses plain JavaScript, keep the same structure but use .js files and your existing module syntax.

  1. 1

    Install OpenTelemetry packages:

    npm install @opentelemetry/api @opentelemetry/sdk-node \
      @opentelemetry/auto-instrumentations-node \
      @opentelemetry/exporter-trace-otlp-grpc \
      @opentelemetry/exporter-metrics-otlp-grpc
    
  2. 2

    Create a telemetry configuration file:

    import { NodeSDK } from '@opentelemetry/sdk-node';
    import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
    import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc';
    import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-grpc';
    import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
    import { resourceFromAttributes } from '@opentelemetry/resources';
    import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
      
    const otlpEndpoint =
      process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? 'http://localhost:4317';
      
    const resource = resourceFromAttributes({
      [ATTR_SERVICE_NAME]: 'api',
    });
      
    const sdk = new NodeSDK({
      resource,
      traceExporter: new OTLPTraceExporter({ url: otlpEndpoint }),
      metricReader: new PeriodicExportingMetricReader({
        exporter: new OTLPMetricExporter({ url: otlpEndpoint }),
      }),
      instrumentations: [getNodeAutoInstrumentations()],
    });
      
    sdk.start();
  3. 3

    Import the telemetry configuration at the top of your application entry point:

    // This must be first!
    import './telemetry';
      
    import express from 'express';
      
    const app = express();
    // ... rest of your app

Add integrations (optional)

Aspire provides integration libraries that simplify working with common services like Redis, PostgreSQL, RabbitMQ, and more. These integrations handle configuration, health checks, and telemetry automatically.

When you add a hosting integration (like Redis) to your AppHost and reference it from a dependent resource, Aspire automatically injects the necessary configuration. This includes connection strings, URLs, environment variables, and other service-specific settings—eliminating manual connection string management across your services.

  1. 1

    Identify which services in your application could benefit from Aspire integrations. Common candidates include:

    • Databases (PostgreSQL, SQL Server, MongoDB)
    • Caching (Redis, Valkey)
    • Messaging (RabbitMQ, Azure Service Bus, Kafka)
    • Storage (Azure Blob Storage, AWS S3)
  2. 2

    Use the aspire add command to add integration packages to your AppHost:

    aspire add redis
    

    This command adds the necessary AppHost dependencies and helps you configure the integration.

  3. 3

    Update your AppHost to reference the integration and share it across all your services:

    import { createBuilder } from './.modules/aspire.js';
      
    const builder = await createBuilder();
      
    const cache = await builder.addRedis('cache');
      
    await builder
      .addNodeApp('api', '../node-api', 'server.js')
      .withNpm()
      .withReference(cache);
      
    await builder
      .addNodeApp('worker', '../node-worker', 'worker.js')
      .withNpm()
      .withReference(cache);
      
    await builder.build().run();
  4. 4

    Configure the integration in each language:

    npm install redis
    
    const redis = require('redis');
      
    // Aspire injects CACHE_HOST and CACHE_PORT
    const client = redis.createClient({
      socket: {
        host: process.env.CACHE_HOST,
        port: process.env.CACHE_PORT,
      },
    });

Run and verify

Now that you've added Aspire to your application, it's time to run it and see the orchestration in action.

  1. 1

    From your app root, run the application using the Aspire CLI:

    aspire run
    

    The Aspire CLI will:

    • Find your AppHost
    • Build your app and its dependencies
    • Launch all orchestrated services
    • Start the Aspire dashboard
  2. 2

    The dashboard URL will appear in your terminal output:

    🔍  Finding apphosts...
      
    Dashboard:  https://localhost:17068/login?t=ea559845d54cea66b837dc0ff33c3bd3
      
         Logs:  ~/.aspire/cli/logs/apphost-13024-2025-11-19-12-00-00.log
      
                Press CTRL+C to stop the apphost and exit.
    

    The CLI output also shows which AppHost Aspire found, such as apphost.ts.

  3. 3

    Open the dashboard in your browser using the provided URL. You'll see:

    • All your orchestrated resources and their status
    • Real-time logs from each service
    • Traces and metrics for observability
    • Environment variables and configuration
  4. 4

    Verify that your services are running correctly by:

    • Checking the Resources page for service health
    • Accessing your application endpoints
    • Reviewing logs and traces in the dashboard
  5. 5

    Stop the application by pressing in your terminal.

Tip

The Aspire dashboard provides valuable insights even if you haven't added integrations or telemetry yet. You'll see basic information about your services, their startup order, and their endpoints.

Compare with Docker Compose

If you're currently using Docker Compose, the example below shows how the same relationships translate into a TypeScript AppHost:

Docker Compose approach

services:
   postgres:
      image: postgres:latest
      environment:
         - POSTGRES_PASSWORD=postgres
         - POSTGRES_DB=mydb
      ports:
         - "5432:5432"
  
   api:
      build: ./api
      ports:
         - "8080:8080"
      environment:
         - DATABASE_URL=postgres://postgres:postgres@postgres:5432/mydb
      depends_on:
         - postgres
  
   web:
      build: ./web
      ports:
         - "3000:3000"
      environment:
         - API_URL=http://api:8080
      depends_on:
         - api

Aspire's approach

const builder = await createBuilder();
  
const db = (await builder.addPostgres('postgres')).addDatabase('mydb');
  
const api = await builder
  .addNodeApp('api', './api', 'server.js')
  .withNpm()
  .withReference(db);
  
await builder
  .addViteApp('web', './web')
  .withExternalHttpEndpoints()
  .withReference(api)
  .waitFor(api);
  
await builder.build().run();

The orchestration pattern stays the same: model the shared dependencies once, then connect services with withReference.

Key advantages of Aspire over Docker Compose:

  • No manual URL configuration — Services discover each other automatically.
  • Code-based resource references — Define service dependencies in code instead of duplicating them in YAML.
  • Built-in dashboard — Observability without additional tools like Prometheus/Grafana.
  • Reusable orchestration code — The same AppHost definitions work locally and can also drive deployment targets.
  • Integration libraries — Pre-built support for databases, caches, message queues with best practices.

Note

Aspire can also target Docker Compose during deployment. See Deploy your first Aspire app for more details.

Next steps

Congratulations! You've successfully added Aspire to your existing application. Here are some recommended next steps: