This week I continue writing on the series of Angular and the REST by using Angular for the front-end and Nest.js, a Node app, for the backend. In addition, the backend app will be connecting to a PostgreSQL database instance running on top of a Docker container.
The third installment of this series will cover the Nrwl/Nx project to build a monorepo containing an Angular app, Nest.js app and a set of libraries to serve those apps. The front-end app will implement CRUD operations to allow an anonymous user to manage and track movies.
In the next installment of this series, we will continue building to support authentication in the app using JWT (JSON Web Tokens) to restrict access to managing and tracking movies to authenticated users only.
In case you haven't read the previous two installments, here is a quick reminder of what we are going to build.
I start building the Angular app to track movies that I've watched and will be watching in the future. The Angular app allows me to perform all CRUD, Create Read Update and Delete, operations on the movies. From the backend side, I will build a Nest.js Web API. For the time being, it will provide a RESTful Endpoint to perform all the CRUD operations on movies and connect to PostgreSQL database.
If you are new to Nest.js I recommend reading my other series of articles. The series follows a Step-by-Step guide teaching the Nest.js framework starting with the basics to more advanced topics. Nest.js Step-by-Step.
For this article and the coming one, I assume you have a basic knowledge of Nrwl/Nx, Nest.js and Angular. Also, I will be sharing some great resources for a reference if need be.
Let's start creating the monorepo workspace using Nrwl/Nx extensions.
The source code for this monorepo workspace can be found on this GitHub repo.
Building the Monorepo with Nrwl/Nx workspace
In brief, Nrwl/Nx is defined by the tooling it provides:
- It contains schematics to help with generating code in the projects to ensure consistency.
- It contains tools to help with enforcing linting rules and code formatting.
- It allows you to visualize dependencies between apps and libraries within a repository.
- It allows commands to be executed against code changes: any uncommitted changes, comparison between two specific git commits, comparison against another branch, etc. These commands can test, lint, etc. only the affected files, saving us a lot of time by avoiding files that were not affected by our changes.
- It includes utilities to help with race conditions in Angular applications.
I suggest you read the book Enterprise Angular: Monorepo Patterns for an in depth knowledge on Nrwl/Nx.
To get started learning and using Nrwl/Nx the best resource to read is their official documentation and tutorials.
What is an Nx workspace?
An Nx workspace is a folder created using Nx. The folder consists of a single git repository, with folders for apps and libs; along with some scaffolding to help with building, linting and testing.
Create the Movie Tracker Nx Workspace
Let's follow the steps below to create the Movie Tracker workspace structure.
Step 1
Start by installing the latest releases of the Angular CLI and Nrwl/Nx Schematics. Run the following command:
yarn global add @angular/cli @nrwl/schematics
The command installs the latest releases globally of both Angular CLI and Nrwl/Schematics.
Step 2
Let's create the Movie Tracker workspace by running this command:
yarn create nx-workspace mt --directory=movie-tracker
This command downloads and installs the latest create-nx-workspace
CLI NPM package. The CLI then prompts you with a set of questions allowing you to customize your Nx workspace.
I've chosen the following:
-
Which stylesheet format would you like to use?
SASS (.scss) -
What to create in the new workspace?
empty
The empty answer instructs the create-nx-workspace
CLI to create an empty workspace. I will show you how we can control what apps and libraries to create shortly.
Right after answering these two questions, the CLI generates a new workspace for you.
The apps* and **libs folders are empty. We will fill them soon.
Notice the nx.json
file. This file is Nx specific to track all projects created in the workspace.
The npmScope is used later by Nx-workspace to add a path alias, in the root tsconfig.json
file, for each and every library created in the workspace. This makes import statements simpler and less crowded.
Step 3
Let's add the movietracker
Angular application.
Start by enabling the create-nx-workspace
CLI with the Nrwl/Angular schematics by running the following command in a terminal window:
ng add @nrwl/angular --defaults
Next, you can create your first Angular app by running this command:
ng g @nrwl/angular:application movietracker --prefix=mt
The command scaffolds a new Angular app inside the apps
folder. In case you want to nest the application inside a folder, you can simply pass a directory name by using the parameter --directory
.
The application created above has a prefix of mt
.
Step 4
Let's add a few libraries needed by the Angular app.
Run the following command to generate the movies
library under libs\movietracker
folder. Notice the name of the subfolder movietracker
resembles that of the Angular app. This way, all the libs related to the Angular app movietracker
are placed in one centralized place grouped by the app name.
ng g @nrwl/angular:lib movies --directory=movietracker
This command creates a new library with the MovietrackerMovies
module.The module will contain all the components related to managing movies.
Run the command to generate the ui
library under libs\movietracker
folder:
ng g @nrwl/angular:lib ui --directory=movietracker
The command creates a new library with the MovietrackerUi
module. This module will contain all the components that are used by movietracker
libs. The module declares UI components and exports them in return to be accessible outside its boundaries. In addition, this module imports and re-exports the Angular Material modules to be accessible in other modules.
The next command generates the core
library under libs\movietracker
folder:
ng g @nrwl/angular:lib core --directory=movietracker
This creates a new library with the MovietrackerCore
module. The core module will contain shared helper classes. In addition, it imports and re-exports the MovietrackerUi
module and other standard Angular modules that are needed in the Angular app. This way, the Angular app will import this module statically so that all UI components and shared helper classes, will be available at the Angular app root level.
Step 5
Let's create the api-services
shared library that will eventually contain all the Angular Services needed by the app to connect to the backend.
Now run the command to generate the api-services
library under libs
folder:
ng g @nrwl/workspace:lib api-services
This will create a new library without a module. Later we will add Angular Services decorated with:
@Injectable({
providedIn: 'root'
})
This way, all services added to this library will be available at the root Angular app.
Step 6
Finally, let's create the interfaces
shared library that will contain all interfaces for all modules needed by the application.
Run the command to generate the interfaces
library under libs
folder:
ng g @nrwl/workspace:lib interfaces
The command creates a new library without a module. Later, we fill this library with all models we need in the application.
Step 7
The last step. We will create the Nest.js application.
Start by enabling the create-nx-workspace
CLI with the Nrwl/Nest schematics by running the following:
ng add @nrwl/nest
Next, you can create your first Nest.js app by running this command:
ng g @nrwl/nest:app api --frontendProject=movietracker
The command creates a new Nest.js app under the folder apps
. The frontendProject
parameter is required so the CLI can configure the Angular app with a proxy.conf
so all calls to the backend are redirected to the correct port where the Nest.js app is running.
You may read more about Proxying a Backend Server on the Angular website.
Update tsconfig.json
files
I've tweaked the tsconfig.json
files located in the root of the workspace, the Angular app and the Nest.js app. The changes mainly direct the output of the applications into a single /dist
location to make it easier for deployment later on.
Replace the contents of the tsconfig.json
file located at the root of the workspace as follows:
Replace the contents of the tsconfig.app.json
file located inside the Angular app as follows:
Replace the contents of the tsconfig.json
file located inside the Nest.js app as follows:
Now we are ready to start coding our apps!
Building the Nest.js Web API app
In this section I'll be building the functionality for the Web API. In addition, we will setup a PostgreSQL database instance running on top of Docker container.
First things first. Let's build a PostgreSQL database instance running on top of Docker.
Step 1
Create a docker-compose.yaml
file at the root of the workspace with the following content:
The file configures a PostgreSQL database service running the postgres:10.7
version. In addition, it defines a volume mapped to a physical location inside the app, so we can feed in a bash script that runs the first time the container is created and started.
The bash script is located under .\db\init.db\init-users-db.sh
folder and contains the following:
The script creates a new database and a database user. It also makes the user a SUPERUSER
on the database. Remember, this is only used while in the development stage and not in production, so we are okay with this setup!
Let's add a new NPM script to the package.json
file to make it easy to run the Docker container:
"run:services": "docker-compose up && exit 0",
"stop:services": "docker-compose kill"
The first script is used to run the Docker container. The second is used to stop the container.
Open a terminal window and run the command to make sure the Docker container is up and running:
yarn run run:services
Step 2
Next, we will install and configure TypeORM to use inside our Nest.js application.
TypeORM is a JavaScript ORM that is supported by Nest.js. You can read more about TypeORM by checking their official documentation website.
The next command:
yarn add typeorm pg @nestjs/typeorm
The command installs the typeorm
library, the pg
client used internally by TypeORM to connect to a PostgreSQL database and @nestjs/typeorm
module provided by Nest.js to integrate with TypeORM.
To configure TypeORM, create an ormconfig.json
file at the root of the workspace with:
This configuration file contains the connection string named development
that will be used by TypeORM to connect to the database.
I've configured TypeORM to auto-sync everytime we run the Nest.js application to migrate the database structure if need be. When you move to production, you should definitely turn this setting off and use migrations.
Step 3
Let's start coding the Nest.js Web API app.
Add a new movietracker
module under the path apps\api\src\app\movietracker
with the following:
This module configures the TypeOrmModule
with the entities used within it. In this case it is the MovieEntity
. Also, it defines a MovieTrackerController
and MovieTrackerService
.
The TypeOrmModule.forFeature([])
method configures the TypeOrmModule
with the entities used in this module. By passing the list of entities, in our case there is only one entity, we guarantee that each entity will have its own Repository<TEntity>
instance ready to be injected and used by the services as you will see shortly.
Step 4
Add the MovieEntity
class under the path apps\api\src\app\movietracker\entity\movie.entity.ts
with the following content:
We annotate the Movie class with TypeORM decorators that will be used eventually by the TypeORM engine to generate the tables inside the database.
One important thing to note here, is the Movie Id
is annotated with PrimaryGenerationColumn('uuid')
. TypeORM will generate a new UUID
value for each Movie added to the database.
You can learn more about TypeORM decorators by visiting their Decorator Reference.
Step 5
Let's create the MovieTrackerService
under the path apps\api\src\app\movietracker\movietracker.service.ts
. This service contains methods to handle searching for movies, adding a new movie, editing an existing movie and deleting a movie.
The service injects the Repository<MovieEntity>
instance via it's constructor.
constructor(
@InjectRepository(MovieEntity)
private readonly movieTrackerRepo: Repository<MovieEntity>
) {}
Nest.js has a built-in dependency injection system that supports IoC (Inversion of Control). We use the @InjectRepository()
decorator whenever we inject a custom provider, which is the case here. The @nestjs/typeorm
integration module defines the repositories for the entities configured at the MovieTrakerModule
as we will see shortly.
Read more about custom providers at the official documentation website.
The searchMovies(term: string)
method is defined as follows:
The method uses the underlying repository to find and return all movies whose title
or genre
contains the search term passed into the method.
The addMovie(addMovieDto: AddMovieDto)
method is defined as follows:
This method receives as input an object of type AddMovieDto
. We will shortly look at DTOs (Data Transfer Object).
The method creates and returns a new movie stored in the database with the Movie Id populated.
Notice the movie returns a MovieDto
rather than a MovieEntity
. This is a basic concept when dealing with backend apps. You don't need to return a full Entity object. Sometimes you want to hide some fields and not return them to the front-end. That's why we define DTOs with exactly the fields we want to return back to the client-side app.
The updateMovie(id: string, movieDto: MovieDto)
method is defined as:
The method starts by finding a movie stored in the database, updates it and returns the newly updated movie in the form of a MovieDto
.
The deleteMovie(id: string)
method is defined as follows:
The method starts by finding a movie stored in the database, deletes the movie and returns the deleted movie in the form of a MovieDto
.
Step 6
Let's add a set of DTO (Data Transfer Objects). The front-end app will use these objects when communicating with the backend app. Whether to send data or receive data, the front-end app always uses these DTOs and never deals with Entities. Entities belong to the backend app only.
The movie.dto
object:
This object is annotated by validation decorators defined by class-validator
and class-transformer
libraries.
At this stage, you need to install both libraries into your application by running this command:
yarn add class-transformer class-validator
The annotations are self-explanatory and are used to enforce some validation rules to the data received from the front-end app.
Nest.js defines a ValidationPipe
pipe that validates objects with such annotations. Soon you will see where we configure this pipe.
You can read more about Nest.js Pipes by checking the official documentation website.
You may access the rest of the DTO on the GitHub repo of this article.
In addition, we need to create a mapper helper function, that accepts as input, a Movie entity and returns a MovieDto object. Create a new mapper file under the path apps\api\src\app\movietracker\mapper.ts
with the following content:
Step 7
Next, we need to setup the bootstrapping of the application. To prepare for that, let's create a new utils file under the path apps\api\src\shared\utils.ts
and paste in the following:
This async function returns an object containing the connection string that is ready from the ormconfig.json
file with a connection string name of default
. This will be the default connection string used by TypeORM.
Step 9
Replace the content of the app.module.ts
with the following:
I converted the AppModule
into a DynamicModule
to accept a connection string. This connection string is then used to initialize and configure the TypeOrmModule
module at the root level.
You can read more about dynamic modules at the Nest.js official document website.
Step 10
The final configuration step involves bootstrapping the application inside the main.ts
file located at apps\api\src\
.
Paste the following code into the main.ts
file:
The bootstrap()
method starts by creating a new Nest.js app by using the NestFactory.create()
method. This method accepts the root module, in this case it is the AppModule
.
Notice how we are calling the AppModule.forRoot(await getdbConnectionOptions(process.env.NODE_ENV))
method and passing to it a dynamic connection string? This helps running the application on multiple environments without having to change any single line of code.
Then it adds the ValidationPipe
as a global pipe to the application. This way, the input validation is applied to each and every action on all controllers in the app.
Finally, it starts listening on a port defined by means of an environment variable and defaults to 3000
.
Let's add some NPM scripts to run the backend API:
"watch:server": "nodemon",`
"build:server": "tsc -p ./apps/api/tsconfig.app.json",
"run:server": "node -r dotenv/config -r source-map-support/register dist/api/src/main.js",
The watch
script uses nodemon
to run the application. This way, any changes made to the backend API files will cause the node server to restart automatically without having to stop and start the app again.
The contents of the nodemon.json
configuration file located at the root of the workspace is as follows:
It watches all files inside the apps\api\src
folder and executes the following commands every time the server restarts:
"exec": "yarn build:server && yarn run:server",
Basically, the build:server
builds the Nest.js app by running the Typescript compiler against the Nest.js app folder. The run:server
simply runs the Nest.js app from the compiled version of the application and includes the dotenv/config
module. This module imports any environment variables defined inside the .env
file located at the root of the workspace.
For instance, you can define the PORT and any other relevant data inside this file.
Let's run the Docker container first and then the Web API by using the following commands in a terminal window:
yarn run run:services
yarn run watch:server
You should see a statement on your terminal window saying something like [Bootstrap] Server started running on http://localhost:3000
.
To save time, I won't be testing the Web API via Postman. Instead, I will cover the front-end part of this application and test the Web API via the app itself.
Building the Angular client-side App
In the section Create the Movie Tracker Nx Workspace we've added three main libraries under movietracker
folder:
- movietracker\ui
- movietracker\core
- movietracker\movies
Now we will fill the gaps by creating the necessary components needed to build our Movie Tracker CRUD page.
Step 1
Install the Angular Material module into the project by running this command:
ng add @angular/material
It installs the Angular Material module and all the related modules.
Next, create a new Typescript code file ui-material.ts
inside the libs\movietracker\ui\src\lib\
and paste in the following:
I've bundled all the required Angular Material modules inside a material
array.
Next, we need to include this array in the imports
and exports
arrays at the movietracker-ui.module
level. This way, any other module importing movietracker-ui.module
will be able to use the Angular Material modules.
@NgModule({
imports: [CommonModule, RouterModule, ...material],
declarations: [],
exports: [...material]
})
export class MovieTrackerUiModule {}
Step 2
In the movietracker\ui
module, add a new component app-layout.component
to render the general layout of the app.
Run the command to create the component:
ng g component components/app-layout --project=movietracker-ui
This command creates a new component under the path libs\movietracker\ui\src\lib\components\app-layout
.
Paste the following code in the app-layout.component.html
file:
This renders an Angular Material toolbar at the top of the component together with a router-outlet
to allow other components to render beneath this toolbar.
Make sure to include this CSS class inside the app-layout.component.scss
file as follows:
.fill-remaining-space {
display: flex;
justify-content: space-between;
}
We will also add a star-rating
component to use on the Edit and Create Movie forms. You can check components at the GitHub repo of this article.
Now let's import and export this component to the movietracker-ui.module
so that it is available to other modules in the application:
@NgModule({
imports: [CommonModule, RouterModule, ...material],
declarations: [AppLayoutComponent, StarRatingComponent],
exports: [AppLayoutComponent, ...material, StarRatingComponent]
})
export class MovieTrackerUiModule {}
That's it for the movietracker-ui.module
. Let continue with the rest of libs and modules.
Step 4
Create a new Typescript Interface actions.interface.ts
inside the movietracker\core
module with the following content:
export interface MtAction {
type: any;
payload?: any;
label?: string;
}
This interface is used to wrap event payloads later on.
The movietracker-core.module
imports and exports common Angular modules needed in the application in addition to importing and exporting the movietracker-ui.module
. Inside the movietracker
Angular app we just import this core module having all needed dependencies.
Step 5
Create the Movie Typescript interface inside the libs\interfaces\src\lib\movie.interface.ts
with the following content:
export interface Movie {
id?: string;
title: string;
watchedOn: Date;
genre?: string;
rating?: number;
}
Step 6
Let's create the movietracker.service.ts
service inside the libs\api-services
library.
Run the following command:
ng g service movietracker --project=api-services
The command creates the service. Now, let's paste in the following inside this service:
The source code is self-explanatory. In brief, this service implements the searchMovies(), addMovie(), deleteMovie() and updateMovie() methods.
It makes use of the HttpClient object to send HTTP Requests to the backend to communicate with the backend Web API.
Step 7
Let's create the movietracker-movie-list
component inside the movietracker\movies
library.
The next command being:
ng g component containers/movietracker-movies-list --project=movietracker-movies
The command above creates the component in the movietracker-movies
module. This is a smart component or what we call a container component. That's why I place it inside the containers
folder to distinguish it from the other dump or presentational components we will build shortly.
Learn more about smart and presentational components in Angular with this reference.
Paste in the following HTML markup inside the movietracker-movies-list.component.html
file:
The component uses three presentational components:
-
movietracker-search-bar
: This component displays a search box to allow the user to filter for movies. -
movietracker-grid
: This component displays the movies in a tabular form. For each and every row, you have two buttons to edit or delete a movie.- move-dialog-box
: This component is used as a Dialog component to edit, create and delete a movie.
You can generate the components by running the following commands:
ng g component components/movietracker-grid --project=movietracker-movies
ng g component components/movietracker-search-bar --project=movietracker-movies
ng g component components/movie-dialog-box --project=movietracker-movies
You may check the GitHub repo of this article to go through the components.
Let's add a new NPM script to the package.json
file to run the movietracker
app:
"watch:movietracker": "ng serve movietracker",
Now we will run the application and check the UI:
yarn run run:services
yarn run watch:server
yarn run watch:movietracker
The user can:
- View all movies
- Filter movies
- Add a new movie
- Edit an existing movie
- Delete an existing movie
For instance, when the user clicks on the Edit
button on a movie row, the following code executes:
The code creates and opens a new Angular Material Dialog. It then listens to the closed event to receive the updated movie and then it calls the server to update the movie in the database.
In addition, the component injects the MovieTrackerService
that it uses to communicate with the backend Web API.
The create and delete functions are similar to the above. You can check the entire code-behind the movietracker-movies-list.component
here:
You can always check the detailed explanation of the components by reading the first article of this series Angular and the REST.
Step 8
Finally, let's run the application and try its functionality.
yarn run run:services
yarn run watch:server
yarn run watch:movietracker
Conclusion
We've seen how easy it is to connect an Angular app with a Nest.js application.
In the next article, I will add support for authentication using JSON Web Token (JWT).
This post was written by Bilal Haidar, a mentor with This Dot.
You can follow him on Twitter at @bhaidar.
Need JavaScript consulting, mentoring, or training help? Check out our list of services at This Dot Labs.
Top comments (4)
Typo: "The bash script is located under .\db\init.db\init-users-db.sh folder..." should be "The bash script is located under .\db\initdb.d\init-users-db.sh folder..."
Also: it is now possible to select angular and nestjs in one go when creating the workspace rather than doing it manually as explained at the beginning of this article.
Hello ! Very good article.
I was just wondering why you put all the angular code in the libs that is for shared code between many elements in your group of apps ?
Thanks for you time !
Hello and thanks for your kind words.
Yes indeed. In case you need to reuse the same code again and again better to have the code placed inside a lib.
Thanks, Bilal
Wow thanks for that fast response time !
Any tips/ressources on how to deploy this repo in CI ? I am stile struggling with dev vs prod deployement process (use different database connection url for example)
I have been trying to use ZEIT Now but it does not seems very smooth.