Angular 19 对 SSR 的工作方式进行了重大改进。特别是,服务器部分现在在启动 vite dev 服务器时实际使用。这意味着我可以使用express作为我的反向代理,这样我就不必将我的代理定义为用于开发的json配置,并再次在代码中用于生产。太棒了!
这让我思考;我知道关注点分离,前端应该只做前端,后端应该只做后端,对吗?但是使用 SSR,我的前端已经由后端提供服务。为什么我应该为前端设置一个后端,为后端设置另一个单独的后端?嗯,我认为我不需要。对 @angular/ssr 包所做的更改使我能够拥有一个完整的expressjs 服务器作为我的 BFF 开发和产品服务器,并具有以下所有功能。因此,我不仅可以通过混合补水为我的前端提供服务并提供反向代理,还可以在这里定义我的实际后端 api。一切都来自于一个简单的
ng serve
。
但是在express中定义后端端点及其逻辑有点烦人。我想利用像 NestJS 这样的后端框架。这就是我停下来的地方,因为我无法让我的 NestJS 控制器在此设置中做出响应。
这有效:
function widgetRoutes(server: express.Express) {
const widgetData = [
{ id: 1, name: 'Weather', componentName: 'weather' },
{ id: 2, name: 'Taxes', componentName: 'widget2' },
{ id: 3, name: 'Something else', componentName: 'widget3' },
];
server.get('/api/widgets', (req, res) => {
res.json(widgetData);
});
/**
* Fetch a single widget by ID.
*/
server.get('/api/widgets/:id', (req, res) => {
if (req.params.id) {
const widget = widgetData.find((w) => w.id === +req.params.id);
if (!widget) {
res.status(404).json({ error: 'Widget not found' });
} else {
res.json([widget]);
}
}
console.log('[APP]', req.method, req.url, res.statusCode);
});
}
export function bootstrap(): express.Express {
const server = express();
const serverDistFolder = dirname(fileURLToPath(import.meta.url));
const browserDistFolder = resolve(serverDistFolder, '../browser');
// Here, we now use the `AngularNodeAppEngine` instead of the `CommonEngine`
const angularNodeAppEngine = new AngularNodeAppEngine();
// Setup api routes
widgetRoutes(server);
// Setup reverse proxy routes
Object.entries(proxyRoutes).forEach(([path, config]) =>
server.get(path, createProxyMiddleware(config)),
);
// Serve static files from the browser distribution folder
server.get(
'**',
express.static(browserDistFolder, {
maxAge: '1y',
index: 'index.html',
}),
);
server.get('**', (req, res, next) => {
// Yes, this is executed in devMode via the Vite DevServer
console.log('[APP]', req.method, req.url, res.statusCode);
angularNodeAppEngine
.handle(req, { server: 'express' })
.then((response) =>
response ? writeResponseToNodeResponse(response, res) : next(),
)
.catch(next);
});
return server;
}
const server = bootstrap();
if (isMainModule(import.meta.url)) {
const port = process.env['PORT'] || 4000;
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:\${port}`);
});
}
console.warn('Node Express server started');
// This exposes the RequestHandler
export const reqHandler = createNodeRequestHandler(server);
这不是:
@Controller('api/widgets')
export class WidgetController {
widgetData = [
{ id: 1, name: 'Weather', componentName: 'weather' },
{ id: 2, name: 'Taxes', componentName: 'widget2' },
{ id: 3, name: 'Something else', componentName: 'widget3' },
];
@Get()
findAll() {
return this.widgetData;
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.widgetData.find((w) => w.id === +id);
}
}
@Module({
// Setup api routes
controllers: [WidgetController],
})
export class AppModule {}
export async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
// Get the express instance from the NestJS app
const server = app.getHttpAdapter().getInstance();
const serverDistFolder = dirname(fileURLToPath(import.meta.url));
const browserDistFolder = resolve(serverDistFolder, '../browser');
// Here, we now use the `AngularNodeAppEngine` instead of the `CommonEngine`
const angularNodeAppEngine = new AngularNodeAppEngine();
// Setup reverse proxy routes
Object.entries(proxyRoutes).forEach(([path, config]) =>
server.get(path, createProxyMiddleware(config)),
);
// Serve static files from the browser distribution folder
server.get(
'**',
express.static(browserDistFolder, {
maxAge: '1y',
index: 'index.html',
}),
);
server.get('**', (req, res, next) => {
// Yes, this is executed in devMode via the Vite DevServer
console.log('[APP]', req.method, req.url, res.statusCode);
angularNodeAppEngine
.handle(req, { server: 'express' })
.then((response) =>
response ? writeResponseToNodeResponse(response, res) : next(),
)
.catch(next);
});
return app;
}
const server = await bootstrap();
if (isMainModule(import.meta.url)) {
const port = process.env['PORT'] || 4000;
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:\${port}`);
});
}
// This exposes the RequestHandler
export const reqHandler = createNodeRequestHandler(
server.getHttpAdapter().getInstance(),
);
我做错了什么?设置看起来是相同的,但 NestJS 控制器没有响应。 或者,如果我的想法很糟糕,为什么我不应该冒险走这条路呢?
开始工作了:
@Controller('api/widgets')
export class WidgetController {
widgetData = [
{ id: 1, name: 'Weather', componentName: 'weather' },
{ id: 2, name: 'Taxes', componentName: 'widget2' },
{ id: 3, name: 'Something else', componentName: 'widget3' },
];
@Get()
findAll() {
return this.widgetData;
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.widgetData.find((w) => w.id === +id);
}
}
@Module({
// Setup api routes
controllers: [WidgetController],
})
export class ApiModule {}
export async function bootstrap() {
// Create the NestJS application
const app = await NestFactory.create<NestExpressApplication>(ApiModule);
// Get the Express instance
const server = app.getHttpAdapter().getInstance();
// Setup reverse proxy routes
Object.entries(proxyRoutes).forEach(([path, config]) =>
server.get(path, createProxyMiddleware(config)),
);
// Serve static files from the browser distribution folder
const serverDistFolder = dirname(fileURLToPath(import.meta.url));
const browserDistFolder = resolve(serverDistFolder, '../browser');
server.get(
'**',
express.static(browserDistFolder, {
maxAge: '1y',
index: 'index.html',
}),
);
// SSR middleware: Render out the angular application server-side
const angularNodeAppEngine = new AngularNodeAppEngine();
server.get('**', (req, res, next) => {
angularNodeAppEngine
.handle(req, { server: 'express' })
.then((response) => {
// If the Angular app returned a response, write it to the Express response
if (response) {
const n = writeResponseToNodeResponse(response, res);
console.log('[SSR]', req.method, req.url, response.status);
return n;
}
// If not, this is not an Angular route, so continue to the next middleware
return next();
})
.catch(next);
});
// Initialize the NestJS application and return the server
app.init(); // <-- This is what makes it work
return server;
}
const server = await bootstrap();
if (isMainModule(import.meta.url)) {
const port = process.env['PORT'] || 4000;
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:\${port}`);
});
}
// This exposes the RequestHandler
export const reqHandler = createNodeRequestHandler(server);
我显然没有初始化 Nestjs 引擎。
app.init()
成功了。同样重要的是,这是在所有快速配置之后进行的。如果我在创建 Nestjs 服务器后将其放在右侧,则所有反向代理和 ssr 内容都会被忽略。