diff --git a/internal/documentation/docs/pages/Server.md b/internal/documentation/docs/pages/Server.md
index 01a0b5eeff1..f4454fd5732 100644
--- a/internal/documentation/docs/pages/Server.md
+++ b/internal/documentation/docs/pages/Server.md
@@ -185,6 +185,42 @@ Once started with `ui5 serve`, the server automatically monitors changes to the
Changes to configuration files or custom tasks require a server restart to take effect.
:::
+## Integrating into an Existing Node.js Server
+
+Beyond starting its own HTTP server via [`serve`](../api/module-@ui5_server.html#.serve), `@ui5/server` exposes a [`serveMiddleware`](../api/module-@ui5_server.html#.serveMiddleware) API for integrating UI5 Server functionality into an **existing** Express or Connect application. This is the supported entry point for tools that embed a UI5 Server — for example test runners or framework plugins — instead of running `ui5 serve` as a standalone process.
+
+[`serveMiddleware`](../api/module-@ui5_server.html#.serveMiddleware)`(graph[, options][, error])` takes a [project graph](./Project.md) and resolves with:
+
+- `middleware` — a single Connect/Express-compatible request handler; mount it on your own app with `app.use(middleware)`.
+- `close()` — an async teardown function that releases the `BuildServer`'s source watcher and build-cache handle. Call it when shutting down.
+
+Unlike `serve`, `serveMiddleware` does **not** bind a port, attach the live reload WebSocket server, or install the terminal HTML error handler — those remain the responsibility of the HTTP server you own. Error handling and the listener are yours to provide.
+
+```js
+import express from "express";
+import {graphFromPackageDependencies} from "@ui5/project/graph";
+import {serveMiddleware} from "@ui5/server";
+
+const graph = await graphFromPackageDependencies({cwd: process.cwd()});
+const {middleware, close} = await serveMiddleware(graph);
+
+const app = express();
+app.use(middleware);
+const listener = app.listen(8080);
+
+// On teardown, stop the listener and release the BuildServer's watcher and cache handle:
+listener.close();
+await close();
+```
+
+Connect works the same way — `connect().use(middleware)`.
+
+`options` accepts the serving-related subset of [`serve`](../api/module-@ui5_server.html#.serve)'s options. See the [API Reference](../api/module-@ui5_server.html#.serveMiddleware) for the full signature.
+
+::: warning Serve a graph only once
+A project graph can be served only once. Do not call both `serveMiddleware` and `serve` for the same graph.
+:::
+
## SSL Certificates
When starting the UI5 Server in HTTPS- or HTTP/2 mode, for example by using UI5 CLI parameter `--h2`, you will be prompted for the automatic generation of a local SSL certificate if necessary.
diff --git a/package-lock.json b/package-lock.json
index 81010d05fba..dfeb702f455 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7264,6 +7264,91 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/connect": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz",
+ "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "finalhandler": "1.1.2",
+ "parseurl": "~1.3.3",
+ "utils-merge": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ }
+ },
+ "node_modules/connect/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/connect/node_modules/encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/connect/node_modules/finalhandler": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
+ "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "on-finished": "~2.3.0",
+ "parseurl": "~1.3.3",
+ "statuses": "~1.5.0",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/connect/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/connect/node_modules/on-finished": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+ "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/connect/node_modules/statuses": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+ "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/consola": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz",
@@ -19076,6 +19161,7 @@
"@istanbuljs/esm-loader-hook": "^0.3.0",
"@ui5/project": "^5.0.0-alpha.7",
"ava": "^8.0.1",
+ "connect": "^3.7.0",
"cross-env": "^10.1.0",
"eslint": "^10.7.0",
"esmock": "^2.7.6",
diff --git a/packages/project/lib/graph/ProjectGraph.js b/packages/project/lib/graph/ProjectGraph.js
index 200a3fa9c0a..3a6cf7f5327 100644
--- a/packages/project/lib/graph/ProjectGraph.js
+++ b/packages/project/lib/graph/ProjectGraph.js
@@ -759,6 +759,27 @@ class ProjectGraph {
});
}
+ /**
+ * Creates a {@link @ui5/project/build/BuildServer} for this graph and returns it.
+ * Called by @ui5/server to back the request-serving middleware stack.
+ *
+ * Kept private because the API surface is not yet stable enough for general use,
+ * but must remain compatible with the server package's internal usage.
+ *
+ * @private
+ * @param {object} parameters Parameters
+ * @param {boolean} [parameters.initialBuildRootProject=false]
+ * @param {string[]} [parameters.initialBuildIncludedDependencies=[]]
+ * @param {string[]} [parameters.initialBuildExcludedDependencies=[]]
+ * @param {boolean} [parameters.selfContained=false]
+ * @param {boolean} [parameters.jsdoc=false]
+ * @param {boolean} [parameters.createBuildManifest=false]
+ * @param {string[]} [parameters.includedTasks=[]]
+ * @param {string[]} [parameters.excludedTasks=[]]
+ * @param {module:@ui5/project/build/cache/Cache} [parameters.cache=Default]
+ * @param {string} [parameters.ui5DataDir]
+ * @returns {Promise<@ui5/project/build/BuildServer>}
+ */
async serve({
initialBuildRootProject = false,
initialBuildIncludedDependencies = [], initialBuildExcludedDependencies = [],
diff --git a/packages/server/lib/serveMiddleware.js b/packages/server/lib/serveMiddleware.js
index a87e2cb9415..abdc7e0e57c 100644
--- a/packages/server/lib/serveMiddleware.js
+++ b/packages/server/lib/serveMiddleware.js
@@ -52,8 +52,8 @@ import {buildRouter} from "./serve/stack.js";
* outside of request handling.
* @returns {Promise