Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions samples/FullStackJS/FullStackJS.AppHost.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.35806.103
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FullStackJS.AppHost", "FullStackJS.AppHost\FullStackJS.AppHost.csproj", "{D9DBAC61-2DA8-4BAD-9DCA-76BD3B679B3D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D9DBAC61-2DA8-4BAD-9DCA-76BD3B679B3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D9DBAC61-2DA8-4BAD-9DCA-76BD3B679B3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D9DBAC61-2DA8-4BAD-9DCA-76BD3B679B3D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D9DBAC61-2DA8-4BAD-9DCA-76BD3B679B3D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B6A7D11E-8BE9-4E41-9E50-6194AFBDBBE3}
EndGlobalSection
EndGlobal
21 changes: 21 additions & 0 deletions samples/FullStackJS/FullStackJS.AppHost/FullStackJS.AppHost.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<Sdk Name="Aspire.AppHost.Sdk" Version="9.1.0" />

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireHost>true</IsAspireHost>
<UserSecretsId>2222dfd5-330f-4fac-b953-5353a93e6b31</UserSecretsId>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.1.0" />
<PackageReference Include="Aspire.Hosting.Azure" Version="9.1.0" />
<PackageReference Include="Aspire.Hosting.NodeJs" Version="9.1.0" />
<PackageReference Include="CommunityToolkit.Aspire.Hosting.NodeJS.Extensions" Version="9.2.1" />
</ItemGroup>

</Project>
23 changes: 23 additions & 0 deletions samples/FullStackJS/FullStackJS.AppHost/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Microsoft.Extensions.Hosting;

var builder = DistributedApplication.CreateBuilder(args);

var api = builder.AddNpmApp("api", "../api")
.WithNpmPackageInstallation()
.WithExternalHttpEndpoints()
.PublishAsDockerFile();

_ = builder.ExecutionContext.IsPublishMode
? api.WithHttpEndpoint(env: "PORT")
: api.WithHttpsEndpoint(env: "PORT");

builder.AddNpmApp("app", "../app")
.WithNpmPackageInstallation()
.WithReference(api)
.WaitFor(api)
.WithEnvironment("BROWSER", "none")
.WithHttpEndpoint(env: "PORT")
.WithExternalHttpEndpoints()
.PublishAsDockerFile();

builder.Build().Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:17059;http://localhost:15093",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21193",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22019"
}
},
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:15093",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19235",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20147"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
9 changes: 9 additions & 0 deletions samples/FullStackJS/FullStackJS.AppHost/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Aspire.Hosting.Dcp": "Warning"
}
}
}
54 changes: 54 additions & 0 deletions samples/FullStackJS/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
languages:
- csharp
- javascript
products:
- dotnet
- dotnet-aspire
page_type: sample
name: ".NET Aspire with Node.js API and React frontend"
urlFragment: "aspire-fullstack-js"
description: "An example of how to integrate several Node.js apps into a .NET Aspire app."
---

# Integrating a Node.js API and React frontend with .NET Aspire

This sample demonstrates an approach for integrating a Node.js API and React frontend with a .NET Aspire app. The sample includes a Node.js API that returns randomly generated weather forecast data and a React app that consumes the API and displays the data in a table.

The solution consists of three key areas:

- **FullStackJS.AppHost**: The .NET Aspire app that hosts the Node.js API and React app.
- **api**: The Node.js API that returns weather forecast data.
- **app**: The React app that consumes the Node.js API and displays the data.

## Pre-requisites

- [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)
- [Docker Desktop](https://www.docker.com/products/docker-desktop/)
- [Node.js](https://nodejs.org) - at least version 20.7.0
- **Optional** [Visual Studio 2022 17.12](https://visualstudio.microsoft.com/vs/)

## Running the app

If using Visual Studio Code, right-click the `FullStackJS.AppHost.sln` file in the **Explorer** and select **Open Solution**. Open the `Program.cs` file and run the app by clicking the **Run** button in the top right corner of the editor.

If using Visual Studio, open the solution file `FullStackJS.AppHost.sln` and launch/debug the `FullStackJS.AppHost` project.

If using the .NET CLI, run `dotnet run` from the `FullStackJS.AppHost` directory.

### Experiencing the app

Once the app is running, the .NET Aspire dashboard will launch in your browser:

![.NET Aspire dashboard](./images/aspire-dashboard.png)

From the dashboard, you can navigate to the `api` resource to view the weather forecast data returned by the API:

> [!TIP]
> The dashboard will display the `http` endpoint; however, it will not include the route for the weather forecast API. Manually append the `/weatherforecast` route after opening the endpoint.

![Weather forecast API](./images/weather-forecast-api.png)

The `app` endpoint displays the React app, which consumes the weather forecast API and displays the data in a table:

![React app](./images/react-app.png)
22 changes: 22 additions & 0 deletions samples/FullStackJS/api/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
FROM node:23-alpine as build

WORKDIR /app

COPY package.json package.json
COPY package-lock.json package-lock.json

RUN npm install

COPY . .

RUN npm run build

FROM node:23-alpine

WORKDIR /app

COPY --from=build /app .

EXPOSE 3000

CMD ["npm", "start"]
36 changes: 36 additions & 0 deletions samples/FullStackJS/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import express from 'express';

const app = express();
const port = process.env["PORT"] || 3000;

const summaries = [
'Freezing', 'Bracing', 'Chilly', 'Cool', 'Mild',
'Warm', 'Balmy', 'Hot', 'Sweltering', 'Scorching'
];

app.get('/weatherforecast', (_, res) => {
const forecasts: WeatherForecasts = Array.from({ length: 5 }, (_, index) => {
const date = new Date();
date.setDate(date.getDate() + index + 1);

return {
date: date.toISOString().split('T')[0],
temperatureC: Math.floor(Math.random() * 75) - 20,
summary: summaries[Math.floor(Math.random() * summaries.length)]
};
});

res.json(forecasts);
});

app.listen(port, () => {
console.log(`Server is running at http://localhost:${port}`);
});

interface WeatherForecast {
date: string;
temperatureC: number;
summary: string;
}

type WeatherForecasts = WeatherForecast[];
Loading