DEV Community

Fernando
Fernando

Posted on

Learning to build an API in NestJS (Node + Typescript) - Part 05

Intro

If you have been following this series, skip the intro!

This is the fifth part of a series about learning NestJs for people who have experience building web applications but in languages other than typescript.

The structure of these articles is not scripted 👮 nor random 😕. My goal is to lay out everything I learn with as much depth as I can understand. I try to build working code that I understand and can replicate in upcoming real projects. That's why I don't write about any foo bar examples.

This is my learning process 📚... hopefully it will serve you as well.

The articles are written at the same time as I code and read the docs for what I am trying to build. After the draft is completed, I revise it, edit it, and reorder stuff to make reading this article easier for you and me in the future.

Recap

Last time we managed to set up a postgres database and generate entities and migrations. Now we can start creating Users! That is what we will be doing next.

Controller

Let's add a Sign-Up endpoint which receives a DTO and invokes the user service's create method.
Now we find ourselves asking questions like where should this DTO be placed. First I thought of placing it in the model module, next to our entities. But then I realized that the model module is used across all the application modules. And no other module needs this DTO (or any other similar DTO), so it makes sense to put this DTO inside a DTO's folder in the Account Module.

To Create a user we just need its email and password.

export default class SignUpReqDTO {
  email: string;
  password: string;
}
Enter fullscreen mode Exit fullscreen mode

Now that we have our DTO, let's code the endpoint. It calls the create method from the user service. passing only the parameters we need to create a user.

  @Post('sign-up')
  async SignUp(@Body() signUpReqDTO: SignUpReqDTO) {
    await this.userService.create(signUpReqDTO.email, signUpReqDTO.password);
    return;
  }
Enter fullscreen mode Exit fullscreen mode

Service

We need a User Service to create our users. For it to work we need access to the user repository. That is imported at the module level.

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [AccountController],
  providers: [
    AccountService,
    JwtService,
    UserService,
    JwtStrategy,
  ],
})
export default class AccountModule {}
Enter fullscreen mode Exit fullscreen mode

Now we can initialize our service and remember to inject the user repository.

@Injectable()
export default class UserService {
  private saltOrRounds: number;
  constructor(
    @InjectRepository(User) private userRepository: Repository<User>,
  ) {}
Enter fullscreen mode Exit fullscreen mode


ts

Now we can start implementing a create method. The first problem we find is that we need to encrypt the user password to save it in the database. This is for security reasons. In this way, we make sure that if anybody gets access to the user's database, they can't find out the real user passwords.

Following the documentation, we use the 'bcrypt' package.

  import * as bcrypt from 'bcrypt';
  //...
  async create(email: string, password: string) {
    const user = new User();
    user.Email = email;
    const saltOrRounds = 10;
    user.PasswordHash = await bcrypt.hash(password, saltOrRounds);
    await this.userRepository.save(user);
  }
Enter fullscreen mode Exit fullscreen mode

Just like that, we are able to create a user. In the future, we will add roles so that we can apply some authorization over the authentication.

What is more useful at this point is to enforce some data validations like "duplicated user".

Validations

The first step to finding out if a user is already created with this unique email is to find if there is one in the database. user repository has a "findOne" method which we can apply filtering by email.

  async getByEmail(email: string): Promise<User> {
    const user = await this.userRepository.findOne({
      where: { Email: email }
    });

    return user;
  }
Enter fullscreen mode Exit fullscreen mode

With that, we add the corresponding if statement and the consequence exception. For this, I found these HTTP exceptions from 'nestjs/common'.

      const dbUser = await this.getByEmail(email);
      if (dbUser)
        throw new HttpException('Email is already taken', HttpStatus.BAD_REQUEST);
Enter fullscreen mode Exit fullscreen mode

Later on, we can apply some constraints to our database so that a user with a taken email can never be created.

Delete

Now we can start to add other crud operations easily. For example a Delete operation.
We just need to specify the user Id of the user we want to delete.

In the controller, we implement our endpoint that requires the id to be passed as a route parameter.

  @Delete(':id')
  async delete(@Param('id') id: string) {
    return await this.userService.delete(id);
  }
Enter fullscreen mode Exit fullscreen mode

The delete method in user service should look something like this

  async delete(id: string) {
    const user = await this.userRepository.findOneBy({ Id: id });
    await this.userRepository.remove(user);
  }
Enter fullscreen mode Exit fullscreen mode

If you want, we can add a validation here as well. Let's check if the user exists.

  async delete(id: string) {
    const user = await this.userRepository.findOneBy({ Id: id });
    if (!user)
      throw new HttpException('The user does not exist', HttpStatus.BAD_REQUEST);
    await this.userRepository.remove(user);
  }
Enter fullscreen mode Exit fullscreen mode

We can sophisticate this even more. For now, I pretend to keep on building the app.

Wrap up

That's it for now. Now I want to complete the authentication scheme by adding a local authentication strategy for user log-in. With that in place, we can then apply some roles authorization and have a nice platform boilerplate with a log-in and user management. This is our roadmap!
In the next article, I'll focus on that local authentication strategy.

Thanks for reading. Once again I hope this article is useful for you as it is for me. Keep coding!

Top comments (0)