Post

[NestJS] Publishing a Custom Class Validator as an NPM Package

In this post, I will share the process of creating and publishing a custom class validator as an NPM package for validating user data in various projects, particularly in Korean contexts like user registration. This stems from the necessity to streamline data validation in such projects.

You can check out the open-source package on GitHub and participate in its development! :D


Introducing My NPM Package: kr-validators

Install it in your NestJS project using the following command:

1
npm i kr-validators

Step 1: Create a Project

Open Git Bash and run the following commands:

1
2
npx @nestjs/cli new kr-validators
cd kr-validators

Step 2: Create a Library

Run the commands below to set up your library:

1
2
npm i class-validator
npx nest generate library validation

This will create a libs/validation folder, where you can write your custom decorators.


Step 3: Implementing a Decorator

Here’s how to create a decorator for validating Korean resident registration numbers:

Create a file libs/validation/src/decorators/is-resident-id-number.decorator.ts and write the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import {
  registerDecorator,
  ValidationOptions,
  ValidatorConstraint,
  ValidatorConstraintInterface,
} from 'class-validator';

@ValidatorConstraint({ async: false })
export class IsResidentIdNumberConstraint
  implements ValidatorConstraintInterface
{
  validate(value: string): boolean {
    // 1. Check if the number is 13 digits long
    if (!value || value.length !== 13) return false;

    // 2. Multiply the first 12 digits by their weights and sum them
    const weights = [2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5];
    const digits = value.split('').map(Number);
    const checkSum = weights.reduce(
      (sum, weight, index) => sum + weight * digits[index],
      0,
    );
    // 3. Calculate the check digit
    const checkDigit = (11 - (checkSum % 11)) % 10;

    // 4. Compare the calculated check digit with the 13th digit
    return checkDigit === digits[12];
  }

  defaultMessage(): string {
    return 'Invalid Resident ID number';
  }
}

export function IsResidentIDNumber(validationOptions?: ValidationOptions) {
  return function (object: Object, propertyName: string) {
    registerDecorator({
      target: object.constructor,
      propertyName: propertyName,
      options: validationOptions,
      constraints: [],
      validator: IsResidentIdNumberConstraint,
    });
  };
}
  • Korean resident registration numbers are 13 digits long. We first check the length.
  • Multiply the first 12 digits by a predefined weight array and sum the results.
  • Divide the sum by 11 and calculate the remainder, subtract it from 11, and get the remainder when divided by 10.
  • If this matches the last digit of the number, the validation is successful.

Step 4: Module and Barrel Setup

  1. Module Setup

Edit libs/validation/src/validation.module.ts as follows:

1
2
3
4
5
6
7
import { Module } from '@nestjs/common';
import { IsResidentIDNumber } from './decorators/is-resident-id-number.decorator';

@Module({
  providers: [IsResidentIDNumber],
})
export class ValidationModule {}
  1. Barrel Setup

Edit libs/validation/src/index.ts to export the necessary decorators:

1
2
export * from './decorators/is-resident-id-number.decorator';
export * from './validation.module';

Step 5: Build and Publish the Package

  1. Log in to NPM:
1
npm login
  1. Build the Library:
1
npm run build:libs
  1. Publish the Package:
1
npm publish

Conclusion

While this example only includes validation for resident registration numbers, I’ve also added validators for phone numbers, business numbers, card numbers, emails, and postal codes. Without these validators, developers often need to implement such checks directly in business logic for each project, which can be repetitive and inefficient.

This package addresses that need and makes validation reusable and efficient across projects. I plan to update it further, so feel free to contribute!

Thank you for reading, and happy blogging! 🚀

References

This post is licensed under CC BY 4.0 by the author.