Exploring NestJS with an Angular gallery system
Posted at: 11-10-2023

A little while ago I was looking for two essential solutions: an uncomplicated way to run a website and a suitable way of hosting it. Opting for Angular as the frontend technology, I aimed to maintain a lightweight setup for the backend. This led me to explore the world of NodeJS.

This is where I discovered NestJS. I must say: it's awesome! If you're familiar with Angular, NestJS is the ideal choice for learning and rapid development on this part. I used both technologies to create a "straightforward" gallery system.

We'll cover a few topics without delving too deep into them. Feel free to skip sections and see if something is relevant for you.

Table of Content

Only interested in an example? Find it here at github: Gallery System with NestJS and Angular.

Setup of NestJS 🔗

Relatively easy. As most frameworks out there you can simply run the required commands to get it up and running.

npm i -g @nestjs/cli
nest new name-of-project

This will give us a default structure:

src
|- app.controller.spec.ts
|- app.controller.ts
|- app.module.ts
|- app.service.ts
|- main.ts

As you might have noticed, it has a similar main.ts and app.module.ts file just like Angular. The behaviour is similar as well. The main.ts is responsible for bootstrapping while the app.module.ts will register modules, controllers and providers. And yes, you can create modules for each domain that you have.

Now, let's quickly run our NodeJS backend and see if it works. Use the following command to run it locally:

npm run start:dev

After running successfully, You should be able to visit http://localhost:3000/ or be able to use a GET request in order to see a "hello world!" message. From this moment on, we can start building!

Introducing our own controller 🔗

Under src we can add our own controllers. Let's add a gallery-manager folder and add the following typescript files: gallery-manager.controller.ts, gallery-manager.service.ts. Your structure should look like this:

src
|- gallery-manager
|-- gallery-manager.controller.ts
|-- gallery-manager.service.ts

Inside your app.module.ts you can register both of them:

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

Inside the gallery-manager.controller.ts you may now inject the service (just as how you would do it in Angular). We will also define our controller with the @Controller decorator while adding the route in it, to tell NestJS we want to use it this way. This means that it will become available at http://localhost:3000/api/gallery-manager.

@Controller("api/gallery-manager")
export class GalleryManagerController {
  constructor(private galleryManagerService: GalleryManagerService) {}
}

Furthermore, let's add a quick test case. Inside the GalleryManagerService we'll add a method to fetch some "imaginary" images. Note that I'm using Promise.all to give you an async example.

export class GalleryManagerService {
  async getFilesWithFolder(): Promise<string[]> {
    return await Promise.all([
      "data:image/png;base64,img",
      "data:image/png;base64,img2",
    ]);
  }
}

We can finally bring it all together and as a result this is what your controller should look like:

import { Controller, Get } from "@nestjs/common";
import { GalleryManagerService } from "./gallery-manager.service";

@Controller("api/gallery-manager")
export class AppController {
  constructor(private galleryManagerService: GalleryManagerService) {}

  @Get("categories/images")
  async getFilesWithFolder(): Promise<string[]> {
    return await this.galleryManagerService.getFilesWithFolder();
  }
}

Run your application and do a GET at http://localhost:3000/api/gallery-manager/categories/images. You should be able to see following result:

[
   "data:image/png;base64,img",
   "data:image/png;base64,img2"
]

And that's how easy it is to set up a controller. Great! Let's move to the next part and add an authentication to our system.

Using Json Web Token (JWT) authentication with NestJS 🔗

Security is an important aspect. Lucky for us, NestJS also provides a neat solution for us.

Setting up the authentication part 🔗

Install the following packages:

npm i @nestjs/jwt
npm i @nestjs/passport

Once installed, create a new folder named "auth". Create the following files for this folder: auth.module.ts, auth.controller.ts, auth.service.ts, jwt.strategy.ts and jwt-auth.guard.ts. Your structure should look as followed:

src
|- auth
|-- auth.controller.ts
|-- auth.module.ts
|-- auth.service.ts
|-- jwt-auth.guard.ts
|-- jwt.strategy.ts

Inside the jwt.strategy.ts file add the following content:

import { ExtractJwt, Strategy } from "passport-jwt";
import { PassportStrategy } from "@nestjs/passport";
import { Injectable } from "@nestjs/common";

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: "JUSTANOTHERSECRET",
    });
  }

  async validate(payload: any) {
    return { username: payload.username, password: payload.password };
  }
}

We will be using the passport strategy. This means the usage of a username and password mechanism to verify the user. In this current setup, we will allow the user to login once and validate calls being made to the parts where a guard is put in place. The session of the user will expire after 4 hours, meaning that they will need to log in again.

Inside the jwt-auth.guard.ts add the following code:

import { Injectable } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";

@Injectable()
export class JwtAuthGuard extends AuthGuard("jwt") {}

And last but not least, add this to your auth.service.ts:

import { Token } from "./models/token";
import { User } from "./models/user";
import { Injectable } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";

@Injectable()
export class AuthService {
  constructor(private jwtService: JwtService) {}

  async login(user: User): Promise<Token> {
    const payload = { username: user.username, password: user.password };
    return {
      access_token: await this.jwtService.signAsync(payload),
    };
  }
}

Great, the basics are done. All we need to do now is to register them within our module and start using them inside of our controllers!

Your auth.module.ts should look like this:

import { JwtAuthGuard } from "./jwt-auth.guard";
import { JwtModule } from "@nestjs/jwt";
import { AuthService } from "./auth.service";
import { Module } from "@nestjs/common";
import { PassportModule } from "@nestjs/passport";
import { ConfigModule } from "@nestjs/config";
import { AuthenticationController } from "./auth.controller";
import { JwtStrategy } from "./jwt.strategy";

@Module({
  imports: [
    PassportModule,
    ConfigModule,
    JwtModule.register({
      secret: "JUSTANOTHERSECRET",
      signOptions: {
        expiresIn: "4h",
      },
    }),
  ],
  providers: [JwtStrategy, AuthService, JwtAuthGuard],
  controllers: [AuthenticationController],
})
export class AuthModule {}

Don't forget to register the AuthModule inside your app.module.ts. It belongs under the imports section. Now that truly everything is set, we can start filling in the authentication controller. We will do this by example right now, without any database, so hardcoded.

Before we proceed, please create a new folder under auth and name it models. Add two new files named token.ts and user.ts. Add the following code in these classes:

export interface Token {
  access_token: string;
}
export interface User {
  username: string;
  password: string;
}

Your structure for this part should look like this:

src
|- auth
|-- models
|--- token.ts
|--- user.ts

Then to continue, open the auth.service.ts and add the following code:

import { Token } from "./models/token";
import { User } from "./models/user";
import { Injectable } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";

@Injectable()
export class AuthService {
  constructor(private jwtService: JwtService) {}

  async login(user: User): Promise<Token> {
    const payload = { username: user.username, password: user.password };
    return {
      access_token: await this.jwtService.signAsync(payload),
    };
  }
}

As you can see, we will use the provided JWT solution out of the box. Easy like that. We're almost there. Open your controller and add the final code:

import { User } from "./models/user";
import {
  Body,
  Controller,
  Get,
  Post,
  UnauthorizedException,
  UseGuards,
} from "@nestjs/common";
import { AuthService } from "./auth.service";
import { ConfigService } from "@nestjs/config";
import { Token } from "./models/token";
import { AuthGuard } from "@nestjs/passport";

@Controller("api/authentication")
export class AuthenticationController {
  constructor(
    private authService: AuthService,
    private readonly configService: ConfigService,
  ) {}

  @UseGuards(AuthGuard("jwt"))
  @Get("isAuthenticated")
  isAuthenticated(): boolean {
    return true;
  }

  @Post("token")
  async token(@Body() user: User): Promise<Token> {
    if ("admin" === user.username && "password" === user.password) {
      return await this.authService.login(user);
    }
    throw new UnauthorizedException();
  }
}

Here we go. All parts are connected and we can now make a POST call to http://localhost:3000/api/authentication, adding the user inside the body.

{
  "username": "admin",
  "password": "password"
}

This will return a unique access token (JSON Web Token) that can be used to further authenticate the user. As you might remember, we set the expiration for the token to 4 hours. Meaning that after these hours, the user will get unauthorized when using this token. Also, make sure to never share it!

Guarding your controller with JWT authentication 🔗

It only requires a few more lines of code to activate a guard on your controller. The controller will check if the given token is still valid and will either allow or disallow the user to do their requested action.

Update your gallery-manager.controller.ts with the following code:

import { Controller, Get, UseGuards } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
import { GalleryManagerService } from "./gallery-manager.service";

@Controller("api/gallery-manager")
export class AppController {
  constructor(private galleryManagerService: GalleryManagerService) {}

  @Get("categories/images")
  @UseGuards(AuthGuard("jwt"))
  async getFilesWithFolder(): Promise<string[]> {
    return await this.galleryManagerService.getFilesWithFolder();
  }
}

Can you feel see it? That's how easy it is to use! Of course, there is more functionality to the ways of permissions, but let's keep it basic for now. Let's have a quick test. Make a GET request towards http://localhost:3000/api/gallery-manager/categories/images. You should get an unauthorized response:

{
  "statusCode": 401,
  "message": "Unauthorized"
}

Great! It's secure! Let's fetch at token by doing a POST towards http://localhost:3005/api/authentication/token using the credentials from before:

{
  "username": "admin",
  "password": "password"
}

You should receive a specific access token back:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFiYyIsInBhc3N3b3JkIjoiYWJjIiwiaWF0IjoxNjk2OTY1MzY5LCJleHAiOjE2OTY5Nzk3Njl9.YcqlGDdcV03wQrqp2CTT2MsIpPc7Ynp-nRQdcViOEqA"
}

Do another GET call towards http://localhost:3000/api/gallery-manager/categories/images but this time with the token value. You may do this by adding a new header where the key is Authorization and the value Bearer <your token value here>.

For example:

Key: Authorization
Value: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFiYyIsInBhc3N3b3JkIjoiYWJjIiwiaWF0IjoxNjk2OTY1MzY5LCJleHAiOjE2OTY5Nzk3Njl9.YcqlGDdcV03wQrqp2CTT2MsIpPc7Ynp-nRQdcViOEqA

Boom! That should have worked (if not, abandon ship!). You successfully made an authorized request to your custom controller.

Host your clientside application together with NestJS 🔗

Another great feature. You know, .Net can host your angular application. But so can NestJS! And again, it's very easy and straightforward to set up. Let's have a look.

Assuming that you have an Angular project (or something else) that you want to host with NestJS, you may call the command to build. For Angular this would be ng build. After that, you should be able to find the required content in the dist folder. Take note of this.

Execute the following command for NestJS:

npm i @nestjs/serve-static

Apply the following configuration to the app.module.ts file (assuming you followed the above steps, if not take note of the ServeStaticModule):

import { GalleryManagerService } from "./gallery-manager/gallery-manager.service";
import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";
import { ServeStaticModule } from "@nestjs/serve-static";
import { join } from "path";
import { GalleryManagerController } from "./gallery-manager/gallery-manager.controller";
import { AuthModule } from "./auth/auth.module";

@Module({
  imports: [
    ServeStaticModule.forRoot({
      rootPath: join(__dirname, "..", "frontend"),
    }),
    AuthModule,
  ],
  controllers: [AppController, GalleryManagerController],
  providers: [GalleryManagerService],
})
export class AppModule {}

It will look in your root NestJS directory for a folder named frontend. In this folder, add the generated content from your build command (Angular). Execute the following command for NestJS:

npm build:prod

This will identically create a dist folder at your NestJS root level, creating your production version. All that's left is running your production version of NestJS with your favorite solution. This will also host your Angular application. If you have no favorite solution then I recommend using pm2. It runs everywhere and contains straightforward commands.

Note: if you are having troubles going to an initial route page, you can adjust the app.controller.ts to redirect you straight to what you desire:

import { Controller, Get, Redirect } from "@nestjs/common";

@Controller()
export class AppController {
  constructor() {}

  @Get()
  @Redirect()
  redirect(): any {
    return { url: "/admin" };
  }
}

...But there is even more! 🔗

There is definitely more to NestJS than what the eye meets and I barely scratched the surface. That's why I created an example gallery project that exists out of NestJS and Angular. This project is built upon the basics used here. You can find it here, at github.

I hope that you enjoyed this very first blogpost and discovered something new, regardless what it is!