Skip to content

Latest commit

 

History

History
290 lines (214 loc) · 14 KB

File metadata and controls

290 lines (214 loc) · 14 KB
title Angular CLI and Express
description Mount the devtools hub in the Express server of an Angular SSR app.
Mount the devtools hub in your server.ts, load the overlay in main.ts, and open the panel from a button on your page.

Angular CLI and Express

In an *Angular app with server-side rendering, the devtools run inside your Express server. You add a middleware on the server and load the overlay in the browser.

This setup mounts the devtools in the Express server.ts that Angular SSR generates. For an Analog app, follow Vite and Analog instead. For a server on Hono, h3 or Fastify, follow Hono, h3 and Fastify.

Setup at a glance

Add @santoshyadavdev/ng-devtools and devframe. See Installation. Add initNgDevtoolsHub() to server.ts, before your other routes. Import the overlay in main.ts, in development only. Start the app in development mode and click the amber button in the corner of the page.

Mount the hub

Add the middleware

// src/server.ts
import express from 'express';
import {initNgDevtoolsHub} from '@santoshyadavdev/ng-devtools/hub';

const app = express();
const devtools = initNgDevtoolsHub({ws: false});
app.use(devtools.nodeMiddleware);

The full-page viewer is at http://localhost:4000/__devframes/. The hub is built on @devframes/hub, so other devframe tools can join the same dock.

Middleware order

Mount the middleware before express.static and the Angular SSR handler, so the devtools routes answer first.

// src/server.ts
const app = express();
const devtools = initNgDevtoolsHub({ws: false});
app.use(devtools.nodeMiddleware); // devtools first

app.use(express.static(browserDistFolder, {index: false}));
app.use((req, res, next) => {
  angularApp
    .handle(req)
    .then((response) => (response ? writeResponseToNodeResponse(response, res) : next()))
    .catch(next);
});

The middleware only handles requests under its base path (/__devframes/ by default). Everything else goes to the next handler.

Pick a transport

The browser talks to the hub over server-sent events or a WebSocket. Pick one with the ws option:

// src/server.ts
const devtools = initNgDevtoolsHub({ws: false});
// src/server.ts
const devtools = initNgDevtoolsHub({ws: {sidecar: true}});

With ws: false there is no WebSocket, and the browser connects over SSE on the same port. It is the simplest choice: every request goes through your Express server, including under ng serve.

With ws: {sidecar: true}, the WebSocket runs on its own port, picked automatically.

Rebuilds under ng serve

ng serve runs server.ts again after a rebuild that changes the server output. The process keeps one hub per base, so each new initNgDevtoolsHub() call closes the hub from the run before, along with its side-car port. A generated MCP token stays the same until the process exits.

Hub options

initNgDevtoolsHub() accepts the options of initHub() from @devframes/hub, apart from devframes and ui. These are the ones you are most likely to set:

Option Default What it does
base '/__devframes/' Where the hub is mounted. The devtools panel lives at <base>ng-devtools/.
ws false uses server-sent events only. { sidecar: true } runs the WebSocket on its own port.
auth on false turns off the one-time code.
allowedOrigins loopback origins and the Chrome extension Extra origins allowed to open the WebSocket. A list replaces the Chrome extension default. false turns the origin check off.
mcp a bearer token Mounts the MCP endpoint at <base>__mcp and asks for a bearer token. With auth: false the default is 'auto': it mounts once agent tools exist and asks for no token. See Send a token.

The hub also takes the devtools options, such as inspectors, agent, actions, redaction and limits. See Configuration.

Access control

The hub protects its connection with a one-time code by default. The server prints the code, and a browser can read data only after it exchanges that code. On a machine only you use, pass auth: false to turn the gate off.

The origin check is on by default too. Only loopback origins and the Chrome extension can open the WebSocket. If you pass your own allowedOrigins list, it keeps loopback origins but drops the extension. Add chrome-extension://<id> to the list, with the ID from chrome://extensions:

// src/server.ts
import {initNgDevtoolsHub} from '@santoshyadavdev/ng-devtools/hub';

const devtools = initNgDevtoolsHub({
  allowedOrigins: ['https://tunnel.example', 'chrome-extension://<id>'],
});

Access and redaction covers both checks.

The demo app in this repository mounts the hub like this:

// src/server.ts
const auth = process.env['NG_DEVTOOLS_AUTH'] === 'true';
const devtools = initNgDevtoolsHub({
  ws: {sidecar: true},
  auth,
});
app.use(devtools.nodeMiddleware);

It turns the one-time code off unless NG_DEVTOOLS_AUTH is true. Don't copy that setting. Keep the one-time code on for your own apps. The demo keeps the default origin check.

initNgDevtoolsHub() has no production switch of its own. If your server.ts also runs in production, decide there whether to mount it.

Load the overlay

Import it in development

The overlay collects live data from the page. Import it after bootstrap, in development only:

// src/main.ts
import {bootstrapApplication} from '@angular/platform-browser';
import {App} from './app/app';
import {appConfig} from './app/app.config';

bootstrapApplication(App, appConfig)
  .then(() => {
    if (typeof ngDevMode === 'undefined' || ngDevMode) {
      return import('@santoshyadavdev/ng-devtools/overlay');
    }
    return undefined;
  })
  .catch((err) => console.error(err));

ngDevMode is false in production builds, so the import never runs there and the overlay stays out of your production bundle.

The dock entries

A floating button appears on your page. It opens the devtools with one dock entry per tool:

Dock entry Shows
Angular Dashboard, components, routes, signals, injectors, forms, pipes, and SSR & HTTP
NgRx Store patterns from source, and live state and actions
Analog File routes, server calls, render modes and lint (a notice in non-Analog apps)
NativeScript A Coming Soon placeholder
Capacitor A Coming Soon placeholder

Popup and hub covers the panel, its dock modes and deep links.

Run the app

With the dev server

When you run ng serve, the Angular dev server runs server.ts too, so the hub answers on port 4200 as well.

ng serve
# open http://localhost:4200 and click the amber button
ng build --configuration development
node dist/<your-app>/server/server.mjs
# open http://localhost:4000 and click the amber button

With the built SSR server

To test the real Express process, build with the development configuration and start server.mjs. Replace <your-app> with your project name.

ng build uses the production configuration by default. ngDevMode is false there, so the overlay is never imported and the button never appears. The live tabs need --configuration development. In this repository, the same applies to pnpm build.

Fill the SSR & HTTP tab

Add the providers

To fill the SSR & HTTP tab, add the interceptor and hydration hooks to your app config:

// src/app/app.config.ts
import {provideHttpClient, withFetch} from '@angular/common/http';
import {ApplicationConfig} from '@angular/core';
import {provideClientHydration} from '@angular/platform-browser';
import {provideNgDevtoolsHttp, withNgDevtools} from '@santoshyadavdev/ng-devtools/http';

export const appConfig: ApplicationConfig = {
  providers: [
    provideClientHydration(),
    provideHttpClient(withFetch(), withNgDevtools()),
    provideNgDevtoolsHttp(),
  ],
};

withNgDevtools() records requests and applies fault rules. provideNgDevtoolsHttp() captures hydration warnings before the overlay loads. In production builds the interceptor passes requests through untouched.

Put withNgDevtools first

Register withNgDevtools() before your own interceptors, for example provideHttpClient(withNgDevtools(), withInterceptors([authInterceptor])). It then records requests as the app makes them, and fault rules apply before anything else.

Run SSR in the same process

SSR and the devtools middleware must run in the same Express process. Otherwise the server-side calls never reach the tab.

The SSR & HTTP guide covers interceptor order and fault injection in detail.

Mount only the panel

To mount only the devtools panel without the dock, use initDevframe() from devframe/initiate:

// src/server.ts
import {initDevframe} from 'devframe/initiate';
import ngDevtools from '@santoshyadavdev/ng-devtools/devframe';

const devtools = initDevframe(ngDevtools, {base: '/__ng-devtools/'});
app.use(devtools.nodeMiddleware);

The overlay looks for /__ng-devtools/ too. Without the hub, every tab sits in one tab bar.

Troubleshooting

The app is probably a production build. Run ng serve, or build with --configuration development. Then check that main.ts imports the overlay. The page could not reach the hub. Check that the hub middleware is mounted before express.static and the SSR handler, and that base matches the path the overlay uses. See No devtools server found. Check that the server is running and that the hub middleware is mounted before express.static and the SSR handler. Then reload the page. With auth on, a browser reads data only after it exchanges the one-time code the server printed. On a machine only you use, pass auth: false. Add withNgDevtools() and provideNgDevtoolsHttp(), and run SSR in the same Express process as the hub. Prerendered routes make no requests at runtime.

Where to next

What the overlay sends, and how to point it at a custom mount path. Dock modes, the hub rail, deep links and connection status. Interceptor order, fault rules and hydration warnings. Who can reach the hub, and what is redacted.