angularexpressserver-side-renderingangular-ssr

Adding API route to the server of an Angular SSR project is not working as I expected


for an API, I'm trying to add a path for the Node Express server from an Angular SSR project.

After some trouble, I thought I should reduce the problem. So, I used a Postman request to talk directly to the server.

This is my /server.ts:

import { APP_BASE_HREF } from '@angular/common';
import { CommonEngine } from '@angular/ssr';
import express from 'express';
import { fileURLToPath } from 'node:url';
import { dirname, join, resolve } from 'node:path';
import bootstrap from './src/main.server';


// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
  const server = express();
  const serverDistFolder = dirname(fileURLToPath(import.meta.url));
  const browserDistFolder = resolve(serverDistFolder, '../browser');
  const indexHtml = join(serverDistFolder, 'index.server.html');

  const commonEngine = new CommonEngine();

  server.set('view engine', 'html');
  server.set('views', browserDistFolder);

  // Example Express Rest API endpoints
  // server.get('/api/**', (req, res) => { });
  // Serve static files from /browser
  server.get('**', express.static(browserDistFolder, {
    maxAge: '1y',
    index: 'index.html',
  }));

  server.get('/api/test', function(req, res){
    console.log("test first")
    res.json({test:"test"})
  })


  // All regular routes use the Angular engine
  server.get('**', (req, res, next) => {
    const { protocol, originalUrl, baseUrl, headers } = req;

    commonEngine
      .render({
        bootstrap,
        documentFilePath: indexHtml,
        url: `${protocol}://${headers.host}${originalUrl}`,
        publicPath: browserDistFolder,
        providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],
      })
      .then((html) => res.send(html))
      .catch((err) => next(err));
  });


  return server;
}


function run(): void {
  const port = process.env['PORT'] || 4000;

  // Start up the Node server
  const server = app();
  server.listen(port, () => {
    console.log(`Node Express server listening on http://localhost:${port}`);
  });
}

run();

I think the only change I have made is adding the following:

server.get('/api/test', function(req, res){
    console.log("test first")
    res.json({test:"test"})
  })

This is my error, when I call http://localost:4200/api/test via postman

ERROR RuntimeError: NG04002: Cannot match any routes. URL Segment: 'api/test'
    at Recognizer.noMatchError (angular-client/.angular/cache/18.2.10/angular-client/vite/deps_ssr/@angular_router.js:2732:12)
    at eval (angular-client/.angular/cache/18.2.10/angular-client/vite/deps_ssr/@angular_router.js:2764:20)
    at eval (angular-client/.angular/cache/18.2.10/angular-client/vite/deps_ssr/chunk-77RZBIYQ.js:5844:49)
    at OperatorSubscriber2.OperatorSubscriber2._this._error (angular-client/.angular/cache/18.2.10/angular-client/vite/deps_ssr/chunk-77RZBIYQ.js:909:13)
    at OperatorSubscriber2.Subscriber2.error (angular-client/.angular/cache/18.2.10/angular-client/vite/deps_ssr/chunk-77RZBIYQ.js:520:16)
    at OperatorSubscriber2.Subscriber2._error (angular-client/.angular/cache/18.2.10/angular-client/vite/deps_ssr/chunk-77RZBIYQ.js:543:28)
    at OperatorSubscriber2.Subscriber2.error (angular-client/.angular/cache/18.2.10/angular-client/vite/deps_ssr/chunk-77RZBIYQ.js:520:16)
    at OperatorSubscriber2.Subscriber2._error (angular-client/.angular/cache/18.2.10/angular-client/vite/deps_ssr/chunk-77RZBIYQ.js:543:28)
    at OperatorSubscriber2.Subscriber2.error (angular-client/.angular/cache/18.2.10/angular-client/vite/deps_ssr/chunk-77RZBIYQ.js:520:16)
    at OperatorSubscriber2.Subscriber2._error (angular-client/.angular/cache/18.2.10/angular-client/vite/deps_ssr/chunk-77RZBIYQ.js:543:28) {
  code: 4002
}

Solution

  • I had to add a proxy bypass to reach the server in a SSR project.

    To do so, add that to your angular.json

      "projects": {
        "project": {
          "architect": {
            "serve": {
              "builder": "@angular-devkit/build-angular:dev-server",
              "options": {
                "proxyConfig": "src/proxy.conf.json"
              },
    
    

    in you configuration src/proxy.conf.json, you can define routs to bypass the the ng server build

    {
       "/auth": {
         "target": "http://localhost:4000",
         "secure": false
       }
    }
    
    

    In your package.json you should have a script

    "serve:ssr:project": "node dist/project/server/server.mjs"
    

    Then you have to start the server via npm run serve:ssr:project next to you ng serve. You can also omit ng serve when you are only work on the server.