Skip to content

Nx Workspace with Angular and Nest

This article was written over 18 months ago and may contain information that is out of date. Some content may be relevant but please refer to the relevant official documentation or available resources for the latest information.

Nx Workspace with Angular and Nest

In a previous article, we covered creating an Angular project with Nx monorepo tooling. This gives us a great base, but usually, our application will need a server-side project to feed our frontend application with all of the necessary data. Why not leverage the monorepo approach for this use-case then?

In this article, I would like to show you how to bring Nest server-side application that will serve our frontend application all the necessary data and behaviors. We will build on top of the existing Nx-based Angular application, which you can find in this GitHub repository. If you want to follow the code in this article, I recommend cloning this repository and checking out new branch with the nxAngularNest_entryPoint tag.

git clone git@github.com:ktrz/nx-photos.git
git clone https://github.com/ktrz/nx-photos.git (In case of using HTTPS)

cd nx-photos
git checkout -b nxAngularNestWorkingBranch nxAngularNest_entryPoint

The application in the aforementioned repository contains a simple application that displays a list of photos that can be either liked or disliked. If you run the code initially, you'll notice that the app requires a backend server from which to pull the necessary data. We will build this simple backend application using the Nest framework, and all of that within a single monorepo project, so that it is easier to manage both applications.

Nest Overview

Nest is a backend framework for building scalable Node applications. It is a great tool for Angular devs to get into server-side development as it is based on concepts that are very similar to Angular ones:

  • TypeScript support
  • Dependency Injection mechanism that is very similar to the Angular mechanism
  • Puts emphasis on testability
  • Configuration is similar (mostly based on decorators)
  • Best practices and conventions are similar - knowledge is transferable

All of this makes for a great candidate to use Nest as a server application framework for our application. Let's add a Nest application to our existing project.

Add Nest app

To start off, we need to install all of the dependencies which will allow Nx to assist us with building a Nest application. All of this is packed into a single Nx plugin @nrwl/nest.

yarn add -D @nrwl/nest

# or
# > npm install -D @nrwl/nest

With the tooling in place, we can generate the Nest application with one command.

# > nx g @nrwl/nest:application <nest-app-name>

Please keep in mind that, since we're keeping applications using 2 separate Nx plugins, we need to specify the full path to the schematics for generating applications/libraries. In this case, it is @nrwl/nest:application

A nice feature when creating a Nest application is the ability to set up a proxy to our newly created application so that our FE application can easily access it. We can use the --frontendProject additional param to do so. Let's use it to create our actual Nest application:

nx g @nrwl/nest:application api/photos --frontendProject fe-photos

This command will generate a project skeleton for us. The application is bootstrapped similarly to an Angular app. We define an AppModule, which will be a root of the app, and all the other necessary modules will be imported within this module.

// apps/api/photos/src/main.ts

import { Logger } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';

import { AppModule } from './app/app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const globalPrefix = 'api';
  app.setGlobalPrefix(globalPrefix);
  const port = process.env.PORT || 3333;
  await app.listen(port, () => {
    Logger.log('Listening at http://localhost:' + port + '/' + globalPrefix);
  });
}

bootstrap();
// apps/api/photos/src/app/app.module.ts

import { Module } from '@nestjs/common';

import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

For a more in-depth explanation of the Nest framework, please visit the official docs.

Building the API

For our photos application we require 3 following endpoints to be handled: GET /api/photos - which returns the list of all photos PUT /api/photos/:photoId/like - allows us to like a photo PUT /api/photos/:photoId/dislike - allows us to dislike a photo

To handle requests in Nest, we use a class called Controller which can handle requests to a specific sub-path (in this case it will be the photos sub-path). To keep our application clean, let's create a separate module that will contain our controller and all the necessary logic.

nx g @nrwl/nest:module app/photos --project=api-photos nx g @nrwl/nest:controller app/photos --project=api-photos --export

Since the controller shouldn’t contain business logic, we will also create a service to handle the logic for storing and manipulating our photo collection.

nx g @nrwl/nest:service app/photos --project=api-photos

Our newly created service will be added to our PhotosModule providers.

// apps/api/photos/src/app/photos/photos.module.ts

@Module({
  controllers: [PhotosController],
  providers: [PhotosService]
})
export class PhotosModule {}

Just like in Angular, we also need to include our PhotosModule in the AppModule's imports to notify Nest of our module's existence.

// apps/api/photos/src/app/app.module.ts

@Module({
  imports: [PhotosModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Now, we are ready to build the API we need. We can start with the first endpoint for getting all the photos: GET /api/photos

Let's start by creating all the necessary logic within the PhotosService class. We need to store our collection of photos and be able to return them in a form of an Array. To store it, I prefer to use an id-based map for quick access.

// apps/api/photos/src/app/photos/photos.service.ts

const stateToArray = (state: PhotoState): Photo[] =>
  Object.keys(state).map((key) => state[key]);

@Injectable()
export class PhotosService {
  state: PhotoState = {
    ['11ecb817-d6fc-49a9-8b53-229fc064da97']: {
      id: '11ecb817-d6fc-49a9-8b53-229fc064da97',
      title: 'Nest',
      url: 'https://docs.nestjs.com/assets/logo-small.svg',
      likes: 0,
      dislikes: 0,
    },
    /* more initial data entries */
  };

  getPhotos(): Photo[] {
    return stateToArray(this.state);
  }
}

To simplify transformation from a map to an array, I added a utility function stateToArray. It can definitely be extracted to a separate file/directory as an application grows, but for now, let's leave it here inline.

Now, our controller can leverage this getPhotos function to return a list of all photos via an API. To create an endpoint in Nest, we use decorators corresponding to an HTTP method that we want to expose. In our case, it will be a GET method so we can use a @Get() decorator:

// apps/api/photos/src/app/photos/photos.controller.ts

// this prefix will be used for all routes in this controller
@Controller('photos')
export class PhotosController {

  constructor(private photoService: PhotosService) {}

  // we're handling GET request to root path from this controller
  @Get()
  findAll(): any[] {
    return this.photoService.getPhotos();
  }
}

Now, we can run both our frontend and backend server to see the list of photos requested via our new API.

nx serve fe-photos
nx serve api-photos

We still need to implement the liking and disliking feature in the Nest app. To do this, let's follow the same approach as we did earlier. First, let's add the liking functionality to PhotosService:

// apps/api/photos/src/app/photos/photos.service.ts

@Injectable()
export class PhotosService {

  /* rest of the service */

  likePhoto(id: string): Photo {
    const photo = this.state[id];
    this.state = {
      ...this.state,
      [id]: {
        ...photo,
        likes: photo.likes + 1,
      },
    };
    return this.state[id];
  }
}

and similarly, we can implement the dislike functionality

// apps/api/photos/src/app/photos/photos.service.ts

@Injectable()
export class PhotosService {

  /* rest of the service */

  dislikePhoto(id: string): Photo {
    const photo = this.state[id];
    this.state = {
      ...this.state,
      [id]: {
        ...photo,
        dislikes: photo.dislikes + 1,
      },
    };
    return this.state[id];
  }
}

With both methods in place, all that is left to do is implement to endpoints in the PhotosController and use methods provided by a PhotosService:


@Controller('photos')
export class PhotosController {

  /* rest of the controller */

  @Put(':photoId/like')
  likePhoto(@Param() params: {photoId: string}): Photo {
    return this.photoService.likePhoto(params.photoId);
  }

  @Put(':photoId/dislike')
  dislikePhoto(@Param() params: {photoId: string}): Photo {
    return this.photoService.dislikePhoto(params.photoId);
  }
}

The path params are defined analogously to how we define params in Angular routing with the : prefix, and to access those params we can use @Param() decorator for a method's parameter. Now, after our server reloads, we can see that the applications are working as expected with both the liking and disliking functionalities working.

Common interfaces

In this final section, I would like to show you how we can benefit from the monorepo approach by extracting the common interface between the frontend and backend to a separate library. Let's start by creating a library, again using the Nx command tools.

nx g @nrwl/workspace:library photo/api

This will generate a new library under libs/photo/api/ folder. Let's create a new file libs/photo/api/src/lib/photo.model.ts and put the ApiPhoto interface in it so it can be shared by both frontend and backend applications.

// libs/photo/api/src/lib/photo.model.ts

export interface ApiPhoto {
  id: string;
  title: string;
  url: string;
  likes: number;
  dislikes: number;
}

We need to export this interface in the index.ts file of the library as well:

// libs/photo/api/src/index.ts

export * from './lib/photo.model';

There is no way we can use the same interface for an API request in both of our applications. This way, we make sure that the layer of communication between our applications in always up to date. Whenever we change the structure of the data in our server application, we will have to apply the appropriate changes to the frontend application as well as the TypeScript compiler. This forces data to be consistent and braking changes to be more manageable.

Conclusion

As you can see, maintaining the project in a monorepo makes it easier to maintain. Nest framework is a great choice for a team of developers that are acquainted with Angular as it builds on top of similar principles. All of that can be easily managed by the Nx toolset.

You can find the code for this article's end result on my GitHub repo.

Checkout the nxAngularNest_ready tag to get the up-to-date and ready-to-run solution. To start the app you need to serve both Angular and Nest projects:

nx serve fe-photos
nx serve api-photos

In case you have any questions you can always tweet or DM me @ktrz. I'm always happy to help!

This Dot is a consultancy dedicated to guiding companies through their modernization and digital transformation journeys. Specializing in replatforming, modernizing, and launching new initiatives, we stand out by taking true ownership of your engineering projects.

We love helping teams with projects that have missed their deadlines or helping keep your strategic digital initiatives on course. Check out our case studies and our clients that trust us with their engineering.

Let's innovate together!

We're ready to be your trusted technical partners in your digital innovation journey.

Whether it's modernization or custom software solutions, our team of experts can guide you through best practices and how to build scalable, performant software that lasts.

Prefer email? hi@thisdot.co