Build .NET Aspire apps with Node.js

In this article, you learn how to build .NET Aspire apps that use Node.js and Node Package Manager (npm). The sample app in this article demonstrates Angular, React, and Vue client experiences. Node.js support is available through the AddNodeApp API, while npm apps are available with AddNpmApp. The difference between these two APIs is that the former is used to host Node.js apps, while the latter is used to host apps that execute from a package.json file's scripts section—and the corresponding npm run <script-name> command.

Tip

The sample source code for this article is available on GitHub, and there are details available on the Code Samples: .NET Aspire with Angular, React and Vue page.

Prerequisites

To work with .NET Aspire, you need the following installed locally:

For more information, see .NET Aspire setup and tooling.

Additionally, you need to install Node.js on your machine. The sample app in this article was built with Node.js version 20.7.0 and npm version 9.7.2. To verify your Node.js and npm versions, run the following commands:

node --version
npm --version

Clone sample source code

To clone the sample source code from GitHub, run the following command:

git clone https://github.com/dotnet/aspire-samples.git

After cloning the repository, navigate to the samples/AspireWithJavaScript folder:

cd samples/AspireWithJavaScript

From this directory, there are six child directories described in the following list:

  • AspireJavaScript.Angular: An Angular app that consumes the weather forecast API and displays the data in a table.
  • AspireJavaScript.AppHost: A .NET Aspire app that orchestrates the other apps in this sample. For more information, see .NET Aspire orchestration overview.
  • AspireJavaScript.MinimalApi: An HTTP API that returns randomly generated weather forecast data.
  • AspireJavaScript.React: A React app that consumes the weather forecast API and displays the data in a table.
  • AspireJavaScript.ServiceDefaults: The default shared project for .NET Aspire apps. For more information, see .NET Aspire service defaults.
  • AspireJavaScript.Vue: A Vue app that consumes the weather forecast API and displays the data in a table.

Install client dependencies

The sample app demonstrates how to use JavaScript client apps that are built on top of Node.js. Each client app was initially based on a template created by the npm CLI. The following table lists the template commands used to create each client app, along with the default port:

App type Create template command Default port
Angular npm create @angular@latest 4200
React npm create reactapp@latest 3000
Vue npm create vue@latest 5173

Tip

You don't need to run any of these commands, since the sample app already includes the clients. Instead, this is a point of reference from which the clients were created. For more information, see npm-init.

To run the app, you first need to install the dependencies for each client. To do so, navigate to each client folder and run npm install (or the install alias npm i) commands.

Install Angular dependencies

npm i ./AspireJavaScript.Angular/

For more information on the Angular app, see Angular client.

Install React dependencies

npm i ./AspireJavaScript.React/

For more information on the React app, see React client.

Install Vue dependencies

npm i ./AspireJavaScript.Vue/

For more information on the Vue app, see Vue client.

Run the sample app

To run the sample app, call the dotnet run command given the orchestrator app host AspireJavaScript.AppHost.csproj as the --project switch:

dotnet run --project ./AspireJavaScript.AppHost/AspireJavaScript.AppHost.csproj

The .NET Aspire dashboard launches in your default browser, and each client app endpoint displays under the Endpoints column of the Resources page. The following image depicts the dashboard for this sample app:

.NET Aspire dashboard with multiple JavaScript client apps.

The weatherapi service endpoint resolves to a Swagger UI page that documents the HTTP API. This service is consumed by each client app to display the weather forecast data. You can view each client app by navigating to the corresponding endpoint in the .NET Aspire dashboard. Their screenshots and the modifications made from the template starting point are detailed in the following sections.

In the same terminal session that you used to run the app, press Ctrl + C to stop the app.

Explore the app host

To help understand how each client app resource is orchestrated, look to the app host project. The app host code declares the client app resources using the AddNpmApp API.

var builder = DistributedApplication.CreateBuilder(args);

var weatherApi = builder.AddProject<Projects.AspireJavaScript_MinimalApi>("weatherapi")
    .WithExternalHttpEndpoints();

builder.AddNpmApp("angular", "../AspireJavaScript.Angular")
    .WithReference(weatherApi)
    .WithHttpEndpoint(env: "PORT")
    .WithExternalHttpEndpoints()
    .PublishAsDockerFile();

builder.AddNpmApp("react", "../AspireJavaScript.React")
    .WithReference(weatherApi)
    .WithEnvironment("BROWSER", "none") // Disable opening browser on npm start
    .WithHttpEndpoint(env: "PORT")
    .WithExternalHttpEndpoints()
    .PublishAsDockerFile();

builder.AddNpmApp("vue", "../AspireJavaScript.Vue")
    .WithReference(weatherApi)
    .WithHttpEndpoint(env: "PORT")
    .WithExternalHttpEndpoints()
    .PublishAsDockerFile();

builder.Build().Run();

The preceding code:

  • Creates a DistributedApplicationBuilder.
  • Adds the "weatherapi" service as a project to the app host.
  • With a reference to the "weatherapi" service, adds the "angular", "react", and "vue" client apps as npm apps.
    • Each client app is configured to run on a different container port, and uses the PORT environment variable to determine the port.
    • All client apps also rely on a Dockerfile to build their container image and are configured to express themselves in the publishing manifest as a container from the PublishAsDockerfile.

For more information on inner-loop networking, see .NET Aspire inner-loop networking overview. For more information on deploying apps, see .NET Aspire manifest format for deployment tool builders.

Explore the Angular client

There are several key modifications from the original Angular template. The first is the addition of a proxy.conf.js file. This file is used to proxy requests from the Angular client to the "weatherapi" service.

module.exports = {
  "/api": {
    target:
      process.env["services__weatherapi__https__0"] ||
      process.env["services__weatherapi__http__0"],
    secure: process.env["NODE_ENV"] !== "development",
    pathRewrite: {
      "^/api": "",
    },
  },
};

The preceding configuration proxies HTTP requests that start with /api to target the URL within the services__weatherapi__http__0 environment variable. This environment variable is set by the .NET Aspire app host and is used to resolve the "weatherapi" service endpoint.

The second update is the to the package.json file. This file is used to configure the Angular client to run on a different port than the default port. This is achieved by using the PORT environment variable, and the run-script-os npm package to set the port.

{
  "name": "angular-weather",
  "version": "0.0.0",
  "engines": {
    "node": ">=20.7"
  },
  "scripts": {
    "ng": "ng",
    "start": "run-script-os",
    "start:win32": "ng serve --port %PORT%",
    "start:default": "ng serve --port $PORT",
    "build": "ng build",
    "watch": "ng build --watch --configuration development",
    "test": "ng test"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^17.3.5",
    "@angular/common": "^17.3.5",
    "@angular/compiler": "^17.3.5",
    "@angular/core": "^17.3.5",
    "@angular/forms": "^17.3.5",
    "@angular/platform-browser": "^17.3.5",
    "@angular/platform-browser-dynamic": "^17.3.5",
    "@angular/router": "^17.3.5",
    "rxjs": "~7.8.0",
    "tslib": "^2.3.0",
    "zone.js": "~0.14.4"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "^17.3.5",
    "@angular/cli": "^17.3.5",
    "@angular/compiler-cli": "^17.3.5",
    "@types/jasmine": "~5.1.0",
    "jasmine-core": "~5.1.2",
    "karma": "~6.4.3",
    "karma-chrome-launcher": "~3.2.0",
    "karma-coverage": "~2.2.0",
    "karma-jasmine": "~5.1.0",
    "karma-jasmine-html-reporter": "~2.1.0",
    "typescript": "~5.4.5",
    "run-script-os": "^1.1.6"
  }
}

The scripts section of the package.json file is used to define the start script. This script is used by the npm start command to start the Angular client app. The start script is configured to use the run-script-os package to set the port, which delegates to the ng serve command passing the appropriate --port switch based on the OS-appropriate syntax.

In order to make HTTP calls to the "weatherapi" service, the Angular client app needs to be configured to provide the Angular HttpClient for dependency injection. This is achieved by using the provideHttpClient helper function while configuring the application in the app.config.ts file.

import { ApplicationConfig } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';
import { provideRouter } from '@angular/router';

import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideHttpClient()
  ]
};

Finally, the Angular client app needs to call the /api/WeatherForecast endpoint to retrieve the weather forecast data. There are several HTML, CSS, and TypeScript updates, all of which are made to the following files:

import { Component, Injectable } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterOutlet } from '@angular/router';
import { HttpClient } from '@angular/common/http';
import { WeatherForecasts } from '../types/weatherForecast';

@Injectable()
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, RouterOutlet],
  templateUrl: './app.component.html',
  styleUrl: './app.component.css'
})
export class AppComponent {
  title = 'weather';
  forecasts: WeatherForecasts = [];

  constructor(private http: HttpClient) {
    http.get<WeatherForecasts>('api/weatherforecast').subscribe({
      next: result => this.forecasts = result,
      error: console.error
    });
  }
}

Angular app running

To visualize the Angular client app, navigate to the "angular" endpoint in the .NET Aspire dashboard. The following image depicts the Angular client app:

Angular client app with fake forecast weather data displayed as a table.

Explore the React client

There are several key modifications from the original React template. The first is the addition of environment variables in the app host. This file is used to set two React-specific environment variables:

  • BROWSER=none: This environment variable is used to prevent the React client app from launching a browser window.
  • REACT_APP_WEATHERAPI_URL: This environment variable is used to set the URL for the "weatherapi" service.

The preceding configuration sets the REACT_APP_WEATHERAPI_URL environment variable from the services__weatherapi__http__0 environment variable. This environment variable is set by the .NET Aspire app host, and is used to resolve the "weatherapi" service endpoint.

Important

For custom environment variables to be available in the React client app, they must be prefixed with REACT_APP_. For more information, see Adding custom environment variables.

In addition to the aforementioned environment variables, the React app automatically picks up the PORT environment variable and uses it to determine the port on which to run. With the environment variables configured, the React client app needs to call the /api/WeatherForecast endpoint to retrieve the weather forecast data. The next update is to the index.js file. The file passes the REACT_APP_WEATHERAPI_URL environment variable to the App component as the weatherApi property.

import React from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./components/App";

const container = document.getElementById("root");
const root = createRoot(container);
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

The final updates are to the following files:

React app running

To visualize the React client app, navigate to the "react" endpoint in the .NET Aspire dashboard. The following image depicts the React client app:

React client app with fake forecast weather data displayed as a table.

Explore the Vue client

There are several key modifications from the original Vue template. The first is the addition of an environment variables in the app host. This file configures a VITE_WEATHERAPI_URL environment variable from the services__weatherapi__http__0 environment variable. This environment variable is set by the .NET Aspire app host and is used to resolve the "weatherapi" service endpoint.

Important

For custom environment variables to be available in the Vue client app running on Vite, they must be prefixed with VITE_. For more information, see Environment Variables and Modes.

Environment type definitions are available in the env.d.ts file. This file is referenced in the tsconfig.app.json file, and is used to provide type information for the process.env object.

To set the server port, the Vue client app uses the PORT environment variable. This is achieved by updating the vite.config.js file:

import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
  server: {
    host: true,
    port: parseInt(process.env.PORT ?? "5173"),
    proxy: {
      '/api': {
        target: process.env.services__weatherapi__https__0 || process.env.services__weatherapi__http__0,
        changeOrigin: true,
        rewrite: path => path.replace(/^\/api/, ''),
        secure: false
      }
    }
  }
})

The final update from the template is made to the TheWelcome.vue file. This file calls the /api/WeatherForecast endpoint to retrieve the weather forecast data, and displays the data in a table. It includes CSS, HTML, and TypeScript updates.

Vue app running

To visualize the Vue client app, navigate to the "vue" endpoint in the .NET Aspire dashboard. The following image depicts the Vue client app:

Vue client app with fake forecast weather data displayed as a table.

Summary

While there are several considerations that are beyond the scope of this article, you learned how to build .NET Aspire apps that use Node.js and Node Package Manager (npm). You also learned how to use the AddNpmApp APIs to host Node.js apps and apps that execute from a package.json file, respectively. Finally, you learned how to use the npm CLI to create Angular, React, and Vue client apps, and how to configure them to run on different ports.