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 Labs is a modern web consultancy focused on helping companies realize their digital transformation efforts. For expert architectural guidance, training, or consulting in React, Angular, Vue, Web Components, GraphQL, Node, Bazel, or Polymer, visit thisdotlabs.com.
This Dot Media is focused on creating an inclusive and educational web for all. We keep you up to date with advancements in the modern web through events, podcasts, and free content. To learn, visit thisdot.co.
Top comments (0)