Feedback Pal Nest JS API Backend

Nest JS is a progressive Node.js framework for building efficient, reliable and scalable server-side applications.

The Feedback Pal backend has been implemented using Nest JS. Nest JS is the next evolution of Express.js, the popular Node.js framework. Like Angular, one writes Nest JS applications using Typescript and effectively Nest JS feels like Angular for the backend. The combination of Angular and Nest JS allow developers to build full-stack applications using Typescript and a modular architecture.

In this page we will look at the following details for the API backend:

  • The high-level architecture
  • How to protect API calls with Auth0
  • How to setup a Microservices-friendly configuration
  • Persistence with TypeORM and managed Postgresql
  • Pagination

The source code is available here.

High-Level Architecture

API Backend High-Level Architecture

Each functionality within the API layer is contained within a Module. Each module typically contains at least one controller, some DTOs, one or more services.

Of particular interest is the Permission Decorator and the Permission Guard classes. These encapsulate the behaviour to validate the permissions contained in the JWT token passed by the frontend.

How to protect API calls with Auth0

In this section, we’ll be looking at how to protect REST API resources with Auth0. As explained here, the Auth0 HTTP Interceptor in the Angular frontend sets the Authorization header with the JWT token for every request to the API layer. Let’s now see how to configure the API backend to extract the JWT token header from each request and protect each API resource based on the permissions contained in the token.

Configuring the API backend for Auth0

Because we will be deploying this application both locally and on Heroku, we need to have a way to set environment variables in a way that is agnostic from the target deployment platform.

Create a .env file in the root of your project. Please make sure that you add this file to .gitignore and you do NOT check this file in to your source code repository.

This is how the file looks like (I’ve omitted sensitive information, you’ll need to set the values that work for your environment.

.env file

This is the list of environment variables required to run the API backend:

  • DB_USERNAME / DB_PASSWORD: These are the credentials for your Postgresql database
  • DB_NAME: This is the database name within Postresql. I’ve called mine feedbackpal. You can name your database what you like.
  • DB_HOST: This is the URL to your database. I use a DigitalOcean managed Postresql instance, which costs around $20 / month. DigitalOcean offers some free credit ($100 or so) if you want to try it. There are of course other options, like Heroku, or you might want to use a Docker image and run the instance yourself. We will look at these options in the page discussing the DevOps setup for Feedback Pal.
  • PORT: This is not strictly required and definitely it must not be set for production deployments to Heroku. I use it when starting up the NestJS server as default option.
  • AUTH0_AUDIENCE: This is the API identifier, as explainer here.
  • AUTH0_DOMAIN: This is the Auth0 domain for your tenant. Please note that the value doesn’t have a http or https protocol.
  • AUTH0_ISSUER_URL: This is the value of AUTH0_DOMAIN with the https protocol
  • AUTH0_ADMIN_API_CLIENT_ID: Feedback Pal offers the ability to share private events with selected users. Users are stored in the Auth0 database, as part of the signup process. We use the Auth0 Admin API to retrieve subscribed users so that the frontend can present users with a list of subscribed users to share events with. When sharing how to setup Auth0, I described the need to setup an Authorization extension. As part of this setup, Auth0 automatically generated a new application called auth0-authz. The value for this environment variable is contained within the Client ID of that application.
  • AUTH0_ADMIN_API_CLIENT_SECRET: Similar to the CLIENT ID, the value of this property is found in the Client Secret setting for the auth0-authz application. It’s very important that you keep this value secret. Do not commit it to SCM, do not share it with anyone.
  • JWT_NS_PREFIX: As explain in the Auth0 setup page, I’ve created a custom Rule in Auth0 to add the authenticated user’s email to the JWT Token. The prefix I chose for all custom metadata to be attached to the token is the value in this property. So if I’ve asked Auth0 to set the email value to the ’email’ property, the full key to the metadata array is: https://feedbackpal.techwings.io/email

Installing Auth0 on the API backend

To install all that’s required for the API backend to work with Auth0, run the following command (the Git Repo already has these dependencies if you want to follow along with the code):

npm i @nestjs/jwt @nestjs/passport jwks-rsa passport passport-jwt

Setting up the JWT Strategy

The first thing we’ll do is to create a JWT Strategy class. The code is shown below:

jwt.strategy.ts

I’ve placed this class under the authz module. Let’s see what’s going on here.

  • The class extends the PassportStrategy class from the @nestjs/passport module.
  • The class is injected with the ConfigService which is responsible for accessing the environment variables. By default, ConfigService reads the content of the .env file if one is present, or from the environment is it’s not.
  • In the constructor, we invoke the superclass constructor with configuration options, setting some properties, like cache, rateLimit, etc. Auth0 offers an endpoint called well-known/jwks.json as a suffix to your AUTH0_ISSUER_URL environment variable. By delegating the retrieval of environment variables to the ConfigService, we can achieve Cloud Native and Container friendly code that will work both locally (with the .env file) or on a target production platform (with the environment variables set as part of the platform configuration).
  • Next, we instruct Passport to extract the JWT Token from the Authorization header, which as a remainder has the following format: Authorization Bearer <jwt_token>. We also set Passport with the values for audience, issuer and the RS256 algorithm.
  • The validate method is very simple but required as part of the contract. It simply does nothing by returning the same payload that was passed as argument. The payload type is JwtPayload, an interface we’ve created for this specific purpose and shown below
jet-payload.interface.ts

Of course the JWT Token doesn’t contain only the username value, but for this purpose, username was all that I needed and this interface is used for other purposes across the application.

Configuring the authz module with Passport and Auth0

Next, we need to configure the authz module with Passport and the code we’ve just created.

authz.module.ts

Here we initialise the PassportModule with the defaultStrategy set to ‘jwt’. We also indicate as provider the JwtStrategy we have just created and we export the PassportModule for other modules to reuse.

Setting up the Permissions Decorator and Guard

Now that we have the Auth0 boilerplate setup in place, we need to create a Permission Decorator and a Permissions Guard to extract the permissions from the JWT Token and to validate these permissions before each API call can be allowed to go through. Both these classes are in the shared module.

The Permission Decorator

The Permission Decorator is really simple.

permissions.decorator.ts

The only thing that happens in this decorator is that it sets the Metadata with the list of user’s permissions.

The Permissions Guard

The Permissions Guard is a bit more involved.

permissions.guard.ts

The Permissions Guard implements the CanActivate interface. It firsts extracts the list of permissions (entitlements) required to execute a particular API call. It then extracts the list of user permissions from the user’s context. These have been set by the Permissions decorator, which in turn it’s passed these values from the JWT Token that Passport processes.

Finally, the lists of user’s permissions is checked against the required list of permissions and if the user has the required permissions, the Guard returns true, otherwise it returns false.

Protecting API resources with Auth0 and Passport

Let’s now look at a typical controller method. In this case I’m showing the method which returns all feedback events but the same setup can be (and in Feedback Pal is) applied to all API exposed resources.

feedback-events.controller.ts

There are three important configuration settings to make API protection work:

  • The @UseGuards directive, which instructs NestJS to use the Passport AuthGuard with the jwt configuration and to use the Permissions Guard we have just created. This guard will use the Passport jwt strategy. Remember the Permissions directive? The list of strings passed as argument is provided here by the @Permissions directive. In this case we have only one, “read:feedbackEvents”. This means that if the JWT Token doesn’t contain this permission, the request will be unauthorised and rejected.
  • Passport automatically sets the user object in the Request object. We can inject the Request object into this method argument list by using the @Req() directive
  • Finally, we extract the user object from the Request object for later use.

The user object

The user object contain within the Request object looks like the following:

content of user object

The great thing about this is that we didn’t have to write any code to either create the user object or set it with correct values. The combination of Passport and Auth0 does this for us. There are three important pieces of information we will access from this token at various stages in the application:

  • The user’s email. If you remember what we discussed in this page, I’ve created a custom Auth0 rule to attach the user’s email to the JWT Token. This is stored in the user object, under the https://feedbackpal.techwings.io/email property.
  • The user unique identifier in Auth0. In the JWT Token, the authenticating entity is normally known as subject or sub for short. I use this value as user id in the database, typically to store who created events and feedback.
  • Permissions. These are the permissions that the JWT Token carried from the Angular frontend and its values have been setup based on the Auth0 configuration.

These are the permissions that the Permissions Guard will inspect against the permissions required to execute that API method.

That’s it! With all these settings in place, you can now protect each API call at a granular level. For example, the controller method to create a Feedback event looks like the following:

POST method to create feedback events – feedback-events.controller.ts

As you can see, you now have all the elements required to protect your API calls at a granular level. All you have to do is to decide which permissions are required to execute a particular API function.

How to setup a Cloud-native configuration

As we know from the 12 Factors, externalising configuration leads to container-friendly applications. In this section, we will look at how I configured the ConfigService in the API backend. If you want to know more about the 12 Factors, you can watch my YouTube video below.

12 Factors explained

We setup the ConfigModule (which offers the ConfigService) in the AppModule. The setup is really easy:

app.module.ts

Here we’re simply setting the ConfigModule to be global and we export it, so that other modules can use it. By default, the ConfigModule looks for an .env file at the root of your project, although the name and location can be customised.

That’s it! Now each module importing the ConfigModule can offer to its components the ConfigService which can be injected in the component’s constructor.

Persistence with TypeORM and managed Postgresql

In this section, we will look at how to setup a connection to a managed Digital Ocean Postresql instance and how to interact with our database through TypeORM, an ORM (Object Relational Mapping) framework, completely written in Typescript.

Provisioning a managed Postresql instance from Digital Ocean

Which database and which offering you use in your application is really up to you. For Feedback Pal, I’m using a managed Postresql instance offered by Digital Ocean as a service.

Setting up a Postresql instance on Digital Ocean is extremely easy. Just head out to Digital Ocean, signup for an account and create a database. You will need the following information from your Postresql setup:

  • DB username
  • DB password
  • DB name (e.g. feedbackpal, etc)
  • DB Host. This is the connection string to connect to your database remotely
  • DB Port. This is the port where the database is listening on for incoming connections

I also recommend downloading and installing the PgAdmin Postresql client. You can use this client to test the connection, using the information above. Once the client connects successfully to your instance, you’re ready to setup TypeORM.

Setting up TypeORM for database interactivity

Now we will be setting up TypeORM to manage connections to the underlying database. TypeORM supports many different databases, including Postresql.

Install the TypeORM dependencies

The first thing to do is to install the TypeORM dependencies, by running the following command:

npm i @nestjs/typeorm pg typeorm

Configuring the TypeORM Module

Next, we need to configure the TypeORM module in the AppModule. The setup is a little tricky and it’s shown below:

app.module.ts

There are different ways of setting up TypeORM. I chose the forRootAsync method for improved speed. When using the async setup, we need to inject the ConfigService inline through the useFactory configuration option. Here, we’re instructing TypeORM that the database type is Postresql, we then setup the connection information from the properties contained in the .env file and we’re telling TypeORM that it should consider as database entities all Typescript and Javascript files containing .entity. in the name.

We also set the synchronisation property to false, so that TypeORM doesn’t try to recreate the database each time it connects. This setting can be set to true for development purposes, e.g. when you’re in the design phase. However, once your database structure is finalised, this setting should have the value false for production.

The final bit of configuration is the ssl setting. This is very important and it should be setup exactly as shown above or the connection to the managed Postresql instance won’t work.

Entities and Repositories

To connect the API backend to the database I use TypeORM, an Object Relational Mapping framework entirely written in TypeScript and which supports most databases. ORMs work by creating a mapping between an application object (DTO, class) and the underlying database. This happens thanks to two constructs:

  • Entities. These are classes annotated in a particular way that TypeORM understands and maps to the underlying database table. There’s a 1-2-1 relationship between Entities and tables in a relational database.
  • Repositories. These are classes that extend a TypeORM base repository and provide boilerplate functions to perform CRUD operations on the database.There’s also a 1-2-1 relationship between repositories and entities.

Setting up TypeORM repositories

TypeORM Repositories

As shown above, creating an entity repository is quite straightforward: create a class which extends the TypeORM Repository class and annotate it with TypeORM @EntityRepository directive. As you can see, the Repository class is generic and it must be initialised with the value of an Entity. In the above example, we say that the Repository type is FeedbackEvent, which in turn is an Entity for the FeedbackEvent database table.

An Entity Repository offers standard CRUD operations. In addition, TypeORM offers the ability to create a QueryBuilder for more custom queries.

Below it’s an example of such query:

Example of a query builder

One creates the query builder object on the table physical name (feedback_event). This object can then be customised based on runtime values. In this case I mix pagination with business logic to retrieve the feedback events I need.

Setting up Entities

feedback-event.entoty.ts

From the picture above we can see few interesting things:

  • To create a TypeORM entity, create a class which extends the BaseEntity class.
  • Annotate the class with the @Entity() directive
  • The @Unique() directive stores a list of column names whose value must be unique within the table
  • The @Index() directive allows the creation of column database indexes, for faster searches.
  • For array of strings in Postgresql (I use this to store the list of user ids a feedback event owner wants to share an event with), you should specify ‘varchar’ as the value for the @Column() directive passing the options as shown.

With this configuration in place plus the way we initialised the TypeORM module (as explained earlier), TypeORM automatically recognises each Entity and maps it to the database.

Observables and Promises

Since database operations are expensive in terms of computation, typically a well-written framework (and TypeORM is one of them) will expose an asynchronous API, i.e. all database operations run asynchronously and either return a Promise or an Observable. As developers, we must ensure to write our code with async/await so that NestJS can do other things while our database operations complete.

This is an example of the controller invoking the service method which in turn returns the list of feedback events.

feedback-events.controller.ts

As the picture above shows, the controller method is marked with async so that we can invoke the service method with await. If we didn’t do this, the API layer would return a Promise, not the resolved values. The await construct allows us to invoke an asynchronous method (the one that ultimately results in a call from TypeORM to the database) and receive resolved values, rather than just the Promise.

Pagination

Pagination requires a common contract between the frontend and the backend. I implemented this contract via a DTO (PaginationDto) which is shown below:

export class PaginationDto {
  page: number;
  limit: number;
}

This is the parent class on the Angular frontend for all paginated results. Below it’s an example of a DTO which extends PaginationDto to add further filter criteria.

import { PaginationDto } from '../../../shared/pagination/pagination-dto';

export class GetFeedbackEventsFilterDto extends PaginationDto {
  validFrom: Date;
  validTo: Date;
  eventName: string;
  active: boolean;
}

Requests to the backend will be performed by passing the JSON representation of GetFeedbackEventsFilterDto and by virtue of inheritance the API backend will have access to the pagination attributes.

Pagination on the frontend

On the Angular frontend, to achieve pagination I’ve used the ngx-pagination module and I’ve initialised it in the AppModule by simply declaring the NgxPaginationModule there.

app.module.ts – Declaration of the NgxPaginationModule

Below it’s an example of how I used the PaginationDto to get all feedback events from the API backend:

//-----> Private stuff
  private fetchFeedbackEvents(): void {
    const http$ = this.feedbackEventsService.getFeedbackEvents(
      this.getPaginationDto()
    );
    this.http$ = http$
      .pipe(
        take(1),
        catchError((err) => {
          console.error('An error occurred while retrieving events');
          this.errorOccurred = true;
          return throwError(err);
        })
      )
      .subscribe((paginatedResults: PaginatedResultsDto<FeedbackEvent>) => {
        this.feedbackEvents = paginatedResults.data;
        this.count = paginatedResults.totalCount;
      });
  }

  private getPaginationDto(): GetFeedbackEventsFilterDto {
    const paginationDto = new GetFeedbackEventsFilterDto();
    paginationDto.page = this.page;
    paginationDto.limit = this.tableSize;

    return paginationDto;
  }

The fetchFeedbackEvents() function delegates the task of retrieving the events to the FeedbackEventService.

The Pagination code in the frontend component

For the pagination to work with the NgxPagionationModule a component must declare the following properties:

get.feedback.events.filter.dto
  • page: This is the current page the user is in. It starts at 1
  • count: This is the total number of records in the database for the Entity we enquiry
  • tableSize (optional): I want to give users the ability to choose how many records they want to see in each page.

Additionally, the NgxPaginationModule will fire events when users click on the backwards/forwards arrows to scroll through the results.

feedback-events-home.component.ts

Here we are using the paginate pipe which comes with the NgxPaginationModule and we pass an object containing the property values explained above.

Then, you need to use the pagination-controls component which also comes with the NgxPaginationModule.

feedback-events-home.component.html

The pageChange event fires every time users click on a pagination control (e.g to view the next or previous pages) and the page number is assigned to the control’s event; finally once the event is fired we retrieve the next batch of data.

The pagination on the API backend with TypeORM

Pagination on the API backend depends on which technology you use to connect to the database. As explained earlier in this page, I use TypeORM as ORM framework to connect to the Postgresql database.

feedback-event-repository.ts

Above is an example of how I implemented pagination in the API backend. The method accepts a filter DTO with some pagination information as well as other data, e.g. the event dates, the event name, etc.

After setting some defaults in case the client doesn’t send the page and limit properties, I use TypeORM createQueryBuilder which allows to set the limit and offset properties. The limit is the number of records we want, the offset represents the current page. The database handles everything else.

It’s that simple!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.