{"id":316,"date":"2026-03-26T10:00:00","date_gmt":"2026-03-26T17:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/aspire\/?p=316"},"modified":"2026-03-26T09:25:41","modified_gmt":"2026-03-26T16:25:41","slug":"aspire-typescript-apphost","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/aspire\/aspire-typescript-apphost\/","title":{"rendered":"TypeScript AppHost in Aspire 13.2: same app model, different syntax"},"content":{"rendered":"<p>Aspire 13.2 brings <a href=\"https:\/\/aspire.dev\/get-started\/first-app-typescript-apphost\/\">TypeScript AppHost<\/a> support to the same application model that existing Aspire users already know. You still define resources, references, startup dependencies, endpoints, and deployment intent in code. You still get the same dashboard, service discovery behavior, health checks, and deployment artifacts. The difference is the authoring surface: you can now write the AppHost in TypeScript and run it with Node.js.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/aspire\/wp-content\/uploads\/sites\/90\/2026\/03\/apphost-ts.jpg\" alt=\"TypeScript AppHost\" \/><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/aspire\/wp-content\/uploads\/sites\/90\/2026\/03\/dashboard.png\" alt=\"Aspire dashboard running a TypeScript AppHost\" \/><\/p>\n<p>This makes Aspire a natural fit for TypeScript developers. Teams that already live in JavaScript and TypeScript can adopt Aspire without moving AppHost authoring into C#.<\/p>\n<h2>Prerequisites<\/h2>\n<ul>\n<li><strong>Node.js 20 or later<\/strong> \u2014 runtime for the TypeScript AppHost<\/li>\n<li><strong>An OCI-compatible container runtime<\/strong> \u2014 Docker Desktop or Podman<\/li>\n<li><strong><a href=\"https:\/\/get.aspire.dev\">Aspire CLI<\/a><\/strong> \u2014 installed via the commands below<\/li>\n<\/ul>\n<p>The .NET 10 SDK is <strong>only<\/strong> required when the AppHost is written in C# or when the application graph includes .NET projects<\/p>\n<h2>Get started in a few commands<\/h2>\n<p>Install the Aspire CLI, then verify the installation:<\/p>\n<h3>macOS \/ Linux<\/h3>\n<pre><code class=\"language-bash\">curl -sSL https:\/\/aspire.dev\/install.sh | bash\naspire --version<\/code><\/pre>\n<h3>Windows PowerShell<\/h3>\n<pre><code class=\"language-powershell\">irm https:\/\/aspire.dev\/install.ps1 | iex\naspire --version<\/code><\/pre>\n<p>The CLI installs as a standalone <code>aspire<\/code> executable. Running <code>aspire --version<\/code> returns the installed build, for example <code>13.2.0+{commitSHA}<\/code>.<\/p>\n<p>The template set includes two TypeScript examples: <code>aspire-ts-empty<\/code> for an empty TypeScript AppHost, and <code>aspire-ts-starter<\/code> for an Express\/React starter.<\/p>\n<p>Create a new empty TypeScript AppHost:<\/p>\n<pre><code class=\"language-bash\">aspire new aspire-ts-empty --name my-app --output .\/my-app --non-interactive\ncd my-app\naspire run<\/code><\/pre>\n<p>Create the TypeScript starter app:<\/p>\n<pre><code class=\"language-bash\">aspire new aspire-ts-starter --name aspire-app --output .\/aspire-app --non-interactive\ncd aspire-app\naspire run<\/code><\/pre>\n<p>You can also simply use <code>aspire new<\/code> and let yourself be guided by the interactive experience.<\/p>\n<p>To add Aspire to an existing codebase with a TypeScript AppHost:<\/p>\n<pre><code class=\"language-bash\">cd \/path\/to\/your-workspace\naspire init --language typescript\naspire run<\/code><\/pre>\n<p>This creates <code>apphost.ts<\/code>, <code>.modules\/<\/code>, <code>aspire.config.json<\/code>, <code>package.json<\/code>, and <code>tsconfig.json<\/code>, while leaving th rest of the workspace in its current languages and structure.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/aspire\/wp-content\/uploads\/sites\/90\/2026\/03\/file-tree.jpg\" alt=\"Generated project tree with\" \/><\/p>\n<h2>The model stays the same<\/h2>\n<p>A TypeScript AppHost uses the same core AppHost concepts as C#. The builder creates the application model, resource methods add services and infrastructure, reference methods connect resources, and build\/run starts the application. The mapping is direct:<\/p>\n<ul>\n<li><code>DistributedApplication.CreateBuilder(args)<\/code> becomes <code>await createBuilder()<\/code><\/li>\n<li><code>AddRedis(\"cache\")<\/code> becomes <code>await builder.addRedis(\"cache\")<\/code><\/li>\n<li><code>.WithReference(db)<\/code> becomes <code>.withReference(db)<\/code><\/li>\n<li><code>.WaitFor(api)<\/code> becomes <code>.waitFor(api)<\/code><\/li>\n<li><code>Build().Run()<\/code> becomes <code>await builder.build().run()<\/code><\/li>\n<\/ul>\n<p>That continuity is the most important design choice in TypeScript AppHost support. The goal is not to introduce a second orchestration platform with similar ideas. The goal is to let you use the same Aspire model through a TypeScript-native API surface.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/aspire\/wp-content\/uploads\/sites\/90\/2026\/03\/same-model-different-syntax.svg\" alt=\"Same model, different syntax\" \/><\/p>\n<h2>A minimal side-by-side sample<\/h2>\n<p>The smallest example already shows how familiar the model feels.<\/p>\n<p><strong>C#<\/strong><\/p>\n<pre><code class=\"language-csharp\">var builder = DistributedApplication.CreateBuilder(args);\n\nvar cache = builder.AddRedis(\"cache\");\n\nbuilder.Build().Run();<\/code><\/pre>\n<p><strong>TypeScript<\/strong><\/p>\n<pre><code class=\"language-ts\">import { createBuilder } from '.\/.modules\/aspire.js';\n\nconst builder = await createBuilder();\n\nconst cache = await builder.addRedis(\"cache\");\n\nawait builder.build().run();<\/code><\/pre>\n<p>A TypeScript AppHost can also reference .NET projects with <code>addProject<\/code> alongside Node.js resources. The orchestration engine resolves dependencies across runtimes \u2014 a TypeScript-authored AppHost can manage services built with any language, containers, JavaScript or any other workloads in the same graph.<\/p>\n<h2>What the TypeScript starter looks like<\/h2>\n<p>The TypeScript starter uses JavaScript-native resource types such as <code>addNodeApp<\/code> and <code>addViteApp<\/code>. The generated project structure is centered on <code>apphost.ts<\/code>, <code>aspire.config.json<\/code>, and the <code>.modules\/<\/code> SDK directory.<\/p>\n<pre><code class=\"language-ts\">import { createBuilder } from '.\/.modules\/aspire.js';\n\nconst builder = await createBuilder();\n\nconst cache = await builder.addRedis(\"cache\");\n\nconst api = await builder\n    .addNodeApp(\"api\", \".\/api\", \"src\/index.ts\")\n    .withHttpEndpoint({ env: \"PORT\" })\n    .withReference(cache);\n\nawait builder\n    .addViteApp(\"frontend\", \".\/frontend\")\n    .withReference(api)\n    .waitFor(api);\n\nawait builder.build().run();<\/code><\/pre>\n<h2>The practical win for TypeScript teams<\/h2>\n<p>A TypeScript AppHost lets teams stay in the TypeScript toolchain for AppHost authoring. Install Node.js 20 or later, use your existing package manager, edit <code>apphost.ts<\/code>, and run the application with the Aspire CLI. Aspire remains the control plane for the application graph. TypeScript simply becomes another first-class way to declare that graph.<\/p>\n<h2>Under the hood: guest and host<\/h2>\n<p>The TypeScript AppHost uses a guest\/host architecture. <code>apphost.ts<\/code> runs in Node.js as the guest process. The Aspire orchestration engine runs as the host process. The generated SDK bridges the two. When your TypeScript code calls methods such as <code>addRedis()<\/code> or <code>withReference()<\/code>, the SDK translates those calls into JSON-RPC requests and sends them to the host over a local transport: Unix sockets on macOS and Linux, named pipes on Windows.<\/p>\n<p>The important point is not the transport itself. The important point is that the host-side engine remains the source of truth for orchestration. That is why the resulting dashboard, service discovery behavior, health checks, and deployment artifacts stay aligned across languages.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/aspire\/wp-content\/uploads\/sites\/90\/2026\/03\/guest-host-architecture.svg\" alt=\"Guest\/host architecture\" \/><\/p>\n<h2>ATS makes the TypeScript surface possible<\/h2>\n<p>The Aspire Type System, or ATS, is the contract between the host and guest languages. It maps exported .NET concepts into guest-language shapes that fit naturally in TypeScript. Resource instances are passed by handle, DTOs serialize as JSON data, and the guest SDK exposes typed fluent methods over those handles.<\/p>\n<p>That is why TypeScript AppHost support scales with Aspire\u2019s integration ecosystem. The generated SDK is produced from the same host-side implementation and integration metadata instead of relying on a separate hand-written TypeScript package for each integration. The <code>.modules\/<\/code> folder is regenerated when you add or update integrations, and the resulting API surface stays aligned with the AppHost model you already use in C#.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/aspire\/wp-content\/uploads\/sites\/90\/2026\/03\/sdk-generation-pipeline.svg\" alt=\"SDK generation pipeline\" \/><\/p>\n<h2>Day-to-day workflow<\/h2>\n<p>The daily workflow stays simple:<\/p>\n<pre><code class=\"language-bash\">aspire add redis\naspire restore\naspire run<\/code><\/pre>\n<p>Running <code>aspire add<\/code> updates the <code>packages<\/code> section in <code>aspire.config.json<\/code> and regenerates the TypeScript SDK. <code>aspire restore<\/code> regenerates SDK content manually, and <code>aspire run<\/code> or <code>aspire start<\/code> also refreshes generated content when the package list changes. The scaffolded <code>package.json<\/code> includes a <code>start<\/code> script mapped to <code>aspire run<\/code>, so the TypeScript AppHost workflow fits naturally into an existing Node.js development loop.<\/p>\n<p>A typical <code>aspire.config.json<\/code> looks like this:<\/p>\n<pre><code class=\"language-json\">{\n  \"appHost\": {\n    \"path\": \"apphost.ts\",\n    \"language\": \"typescript\/nodejs\"\n  },\n  \"sdk\": {\n    \"version\": \"13.2.0\"\n  },\n  \"packages\": {\n    \"Aspire.Hosting.PostgreSQL\": \"13.2.0\",\n    \"Aspire.Hosting.Redis\": \"13.2.0\",\n  }\n}<\/code><\/pre>\n<p>The <code>packages<\/code> object lists the hosting integrations your AppHost uses. Running <code>aspire add postgres<\/code> appends <code>Aspire.Hosting.PostgreSQL<\/code> to the list and regenerates the SDK. The full catalog of available integrations is listed in the <a href=\"https:\/\/aspire.dev\/integrations\/gallery\/?\">Aspire Integrations Gallery<\/a>.<\/p>\n<h2>Deployment<\/h2>\n<p>TypeScript AppHosts produce the same deployment artifacts as C# AppHosts. Running <code>aspire publish<\/code> generates a deployment manifest that can target Azure Container Apps, Kubernetes, or other supported environments:<\/p>\n<pre><code class=\"language-bash\">aspire publish<\/code><\/pre>\n<p>The generated manifest captures the full application graph \u2014 resources, references, endpoints, and configuration \u2014 regardless of the language used to author the AppHost.<\/p>\n<h2>Debugging with the Aspire VS Code extension<\/h2>\n<p>The <a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=microsoft-aspire.aspire-vscode\">Aspire extension for VS Code<\/a> brings first-class debugging support to TypeScript AppHosts. You can set breakpoints in your <code>apphost.ts<\/code>, step through the builder pipeline, and inspect the application model as it is constructed \u2014 all inside VS Code.<\/p>\n<p>The same extension also attaches seamlessly to the Node.js services in your application graph. When you launch your AppHost through the extension, it automatically wires up the debugger for any <code>addNodeApp<\/code> resource, so you can step from AppHost orchestration code straight into your Express or Fastify handlers without manually configuring launch profiles. The result is a single F5 experience that covers both the AppHost and the services it manages.<\/p>\n<h2>Why this matters<\/h2>\n<p>TypeScript AppHost proves that the AppHost model is bigger than any single language surface. Teams that already build in JavaScript and TypeScript can adopt Aspire without changing the way they think about their stack. The same application model stays in place no matter the language.<\/p>\n<p>The short version is: <strong>same app model, different syntax<\/strong>.<\/p>\n<h2>Learn more<\/h2>\n<ul>\n<li><a href=\"https:\/\/aspire.dev\">Aspire documentation<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/microsoft\/aspire\">Aspire GitHub repository<\/a><\/li>\n<li><a href=\"https:\/\/aspire.dev\/integrations\/overview\/\">Aspire integrations overview<\/a><\/li>\n<li><a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=microsoft-aspire.aspire-vscode\">Aspire extension for VS Code<\/a><\/li>\n<li><a href=\"https:\/\/aspire.dev\/get-started\/add-aspire-existing-app-typescript-apphost\/\">Aspireify an existing app with a TypeScript AppHost<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Aspire 13.2 enables TypeScript developers to define AppHosts directly in TypeScript, with the same streamlined Aspire application model.<\/p>\n","protected":false},"author":681,"featured_media":321,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1,17],"tags":[26,9,12,32],"class_list":["post-316","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-aspire-category","category-deep-dives","tag-apphost","tag-aspire","tag-javascript","tag-typescript"],"acf":[],"blog_post_summary":"<p>Aspire 13.2 enables TypeScript developers to define AppHosts directly in TypeScript, with the same streamlined Aspire application model.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/aspire\/wp-json\/wp\/v2\/posts\/316","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/aspire\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/aspire\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/aspire\/wp-json\/wp\/v2\/users\/681"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/aspire\/wp-json\/wp\/v2\/comments?post=316"}],"version-history":[{"count":2,"href":"https:\/\/devblogs.microsoft.com\/aspire\/wp-json\/wp\/v2\/posts\/316\/revisions"}],"predecessor-version":[{"id":324,"href":"https:\/\/devblogs.microsoft.com\/aspire\/wp-json\/wp\/v2\/posts\/316\/revisions\/324"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/aspire\/wp-json\/wp\/v2\/media\/321"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/aspire\/wp-json\/wp\/v2\/media?parent=316"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/aspire\/wp-json\/wp\/v2\/categories?post=316"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/aspire\/wp-json\/wp\/v2\/tags?post=316"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}