Abstract service

Now let's decouple this functionality into its own service.

It would be interesting to isolate this logic from the UsersService into a more appropriate place. This elegant solution was extracted from an official NestJS course lesson.

First of all, let's create a resource to deal with everything pertaining to authentication/authorization and security in general. Choose no to CRUD entry points.

nest g res auth

Now, let's create a service for hashing.

nest g s auth/hashing

We now have a service for this purpose. However, it won't be here that we'll implement the logic. This will be an abstract class that will only containt the methods' signatures. We do this to achieve lesser code coupling.

@Injectable()
export abstract class HashingService {
  abstract hash(data: string | Buffer): Promise<string>;
  abstract compare(data: string | Buffer, encrypted: string): Promise<boolean>;
}

The second method is about comparing a sent password with the hash, for example in a login attempt. But it will be further explored in a future moment.

We prefix the class and its methods with the abstract keyword to indicate that they should be implemented and thus cannot be directly used.

We'll now create the service that contains the actual logic, in this case related to bcrypt.

nest g s auth/hashing/bcrypt --flat

The --flat flag avoids creating a folder to hold the generated files.

This service should implement the abstract one. With this, we separate declaration from definition.

@Injectable()
export class BcryptService implements HashingService {
  async hash(data: string | Buffer) {
    const salt = await genSalt();
    return hash(data, salt);
  }

  compare(data: string | Buffer, encrypted: string) {
    return compare(data, encrypted);
  }
}

After generating both services, the AuthModule will have added them in its providers in the standard fashion. However, this is not correct, as the abstract service should not be directly used. You can even notice an error. What should be done instead is to use the interface of the abstract class but with a reference to the concrete class. This allows for lesser coupling.

{
  provide: HashingService,
  useClass: BcryptService,
},

Lastly, we should add the HashingService to the exports, and also add the AuthModule to the imports of the UsersModule, so that its context can use this service. After that, remove that private method from the UsersService and inject the HashingService here, to use its method instead.

In the next section, we'll learn an approach that allows to put the hashing logic involving the password itself, elsewhere.

Commit - Hashing service as abstract class

Last updated