Fullstack JS with Angular Universal and NestJS (Nx flavor)
Tags: angular, node, typescript
Published: 2022-10-24 16:28:29

**TL;DR**

- Scaffold a **Nx workspace with an Angular and NestJS app**

```bash
nx-workspace@latest --preset angular-nest
```

- Install [@nxarch/ng-nest](https://github.com/nxarch/nxarch/tree/master/packages/ng-nest) and use the **init** schematic to generate the necessary project setup

```bash
yarn add @nxarch/ng-nest

@nxarch/ng-nest:init --ssrApp=my-angular-project --serverApp=my-nestjs-project
```

- Use `yarn dev:server` to **start** the app

_Side note_:
Make sure to use one of these environment variables for local development.
```bash
APP_ENV=development 
#or 
REMOVE_WEBPACK_CACHE=true
```

If you're using relative paths for your server side API calls via the Angular HttpClient you might encounter an error as it doesn't prepend the base path.

There are **multiple ways** to solve this:
- Use an HttpInterceptor that prepends the base url only on the server
- _or_ update the [PlatformConfig](https://angular.io/api/platform-server/PlatformConfig) to prepend relative paths with the base path
- _or_ just replace relative paths in your HttpClient calls with absolute paths

```ts
hello$ = this.http.get('http://localhost:4200/api/hello')
```

***

**Angular and NestJS** share a lot of common concepts and design patterns. You can even go as far as sharing services if you keep them **framework agnostic**. As a developer being familiar with either of the frameworks you will probably feel **comfortable pretty quickly**.

But why would you **use Angular Universal**?
There are a lot of use cases for **server side rendered** applications such as:

- Improved SEO performance
- Improved load performance of your application
- Shortened time to first meaningful paint and perceived time to interactive. Your users will thank you.

Angular Universal itself isn’t a new framework you’d have to learn. It’ll just **enhance** your apps capabilities as it will also **run on the server**. Eventually it will send the rendered html to the client.

Here are some (non-exhaustive) things you can do on the **server within your Angular application** when Angular Universal is used.

- Accessing the request object in your Angular business logic
- Caching responses
- Issue requests from localhost to localhost (fast)
- Getting access to session values in your Angular app

In order to use Angular Universal with a simple express server you can just use

```bash
ng add @nguniversal/express-engine
```

**But** you’re here to read about how to setup Angular Universal with a NestJS server.

There is a great [library](https://github.com/nestjs/ng-universal) created by the NestJS team that helps integrating an Angular Universal app with a NestJS app. It will compile the NestJS app together with the Angular SSR app in one bundle.

👉 This article will show how to setup a **Nx workspace** with a compiled output of **three separate bundles** -> one for the server, one for the ssr app and one for the client side bundle.

This yields the advantage that we only need to recompile those bundles that are affected by code changes. Hence, it will **decrease compile time** significantly in bigger code bases.

One possible dist folder structure looks like this.
```
├── dist
│   ├── server
│   |   ├── main.js
│   ├── ssr-app
│   |   ├── main.js
│   ├── ui-app
│   |   ├── main.js
│   |   ├── index.html
...
```

We also want to use **browser-sync** during local development in order to proxy our server and enable **live refreshes** whenever the NestJS app or the Angular app is rebuild.

Let’s explore what the scaffolded projects look like after we’ve used the **init** generator

```bash
yarn add @nxarch/ng-nest

@nxarch/ng-nest:init --ssrApp=my-angular-project --serverApp=my-nestjs-project
```

***

## The scaffolded Projects

#### Now there are <u>three app module</u> files

The `AppBrowserModule`, the `AppSsrModule` and the server/`AppModule`.

```ts
// server/app.module.ts

@Module({
  imports: [
    AngularUniversalModule.forRoot({
      bootstrap: join(process.cwd(), 'dist/apps/ui/ssr/main.js'),
      viewsPath: join(process.cwd(), 'dist/apps/ui/browser'),
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule { }

```

The server AppModule will import the `AngularUniversalModule` which is responsible for setting up server-side-rendering routes and delegating requests to the ngExpressEngine.

For more options see the [nxarch/nest-nguniversal](https://github.com/nxarch/nest-nguniversal) repository. You can configure options such as using **custom endpoints** for render routes, **asynchronous cache storages** or the render path.

If you keep the default wildcard render path make sure to prefix the api paths. We do this in your **main.ts** file.

```ts
// server/main.ts

app.setGlobalPrefix("/api");

```

The `AppSsrModule` is only loaded and instantiated on the server. It uses the `AppBrowserModule` in order to render our html document. The `AppBrowserModule` is the main Angular module that’s also used on the client side.

```ts
// ui/app.ssr.module.ts

@NgModule({
  imports: [
    ServerModule,
    AppBrowserModule
  ],
  bootstrap: [AppComponent],
})
export class AppSsrModule { }

```

#### main.ssr.ts

The path of the compiled output of this file is passed via the **bootstrap** option of the `AngularUniversalModule` options. We want to avoid to use any Angular specific dependencies in our NestJS bundle. That’s why we need to re-export the **ngExpressEngine** here.

```ts
// ui/main.ssr.ts

import'@angular/platform-server/init';
import{enableProdMode}from'@angular/core';
import{environment}from'./environments/environment';

if(environment.production){
enableProdMode();
}

export{AppSsrModule}from'./app/app.ssr.module';
export{ngExpressEngine}from'@nguniversal/express-engine';
```

#### server/project.json

There are probably two additions worth mentioning.

The server _project.json_ needs some **external dependencies** to be set in order to tell Webpack **not to bundle** those packages. This will prevent warnings and errors.

```json
"targets": {
        ...
	"build": {
		"executor": "@nrwl/webpack:webpack",
		"outputs": [
		  "{options.outputPath}"
		],
		"options": {
		  "target": "node",
		  "compiler": "tsc",
		  "outputPath": "dist/apps/server",
		  "main": "apps/server/src/main.ts",
		  "tsConfig": "apps/server/tsconfig.app.json",
		  "assets": [
		    "apps/server/src/assets"
		  ],
		  "externalDependencies": [
		    "@nestjs/common",
		    "@nestjs/core",
		    "express",
		    "@nestjs/microservices",
		    "@nestjs/microservices/microservices-module",
		    "@nestjs/websockets",
		    "@nestjs/websockets/socket-module",
		    "cache-manager"
		  ],
		  "optimization": false
		}
	},
	...
}

```

Also there’s another **target** added that can be used to serve the application during local development.

```json
"targets": {
    ...
	"serve-ssr": {
    "executor": "@nxarch/ng-nest:build",
    "options": {
      "browserTarget": "ui:build:development",
      "ssrTarget": "ui:ssr:development",
      "serveTarget": "api:serve:development"
    }
  },
  ...
}
```

#### ui/project.json

In the ui _project.json_ one additional **target** for the server side rendered app has been added.

```json
"targets": {
	"ssr": {
	  "executor": "@angular-devkit/build-angular:server",
	  "options": {
	    "outputPath": "dist/apps/ui/ssr",
	    "main": "apps/ui/src/main.ssr.ts",
	    "tsConfig": "apps/ui/tsconfig.ssr.json",
	    "inlineStyleLanguage": "scss",
	    "outputHashing": "none",
	    "optimization": false
	  },
	  "configurations": {...},
	  "defaultConfiguration": "production"
	},
	...
}
```

#### package.json

Last but not least notice there’s one more script in our scripts object which we can use to start the dev server.

```bash
"dev:server": "nx serve-ssr api"
```

***

**[@nxarch/ng-nest](https://github.com/nxarch/nxarch/tree/master/packages/ng-nest)** tries to make integrating and setting up an Angular Universal app and a NestJS app **quick, simple and more productive**.

If you enjoy using it don't forget to star ⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️.