Validation Library with Typescript

caner demircicaner demirci
14 min read

https://github.com/canerdemirci/cnr-validation

Validation in programming is a crucial process that ensures the accuracy and reliability of data input and output. It involves checking the validity of data against predefined rules and constraints. For instance, validating a user's email address might include ensuring it adheres to a specific format, such as "[email address removed]". By implementing validation techniques, developers can prevent errors, improve user experience, and maintain data integrity within their applications.

I have written a validation library in TypeScript for Node.js. I have also written tests for the library. There are currently two rule classes in the library: one for strings and one for numbers. These classes do type checking in their constructor and have various property functions that validate the class’s value. These functions return their class so that functions can be chained. If the value is not valid, they return an error. Errors are customizable. There is also a class called Validation. This class takes a validation model and, when you validate the model, it returns validation results.

I know there are professional packages for validation, but I wanted to create my own for TypeScript practice. I found opportunities to use TypeScript types, interfaces, and abstract classes. The package is open for development. I would like to add more validation methods, such as phone, IP, and URL, but I don't have time right now. Research is needed for these validations because each one has different formats, and the standards vary from country to country.

Example Using of the Package

import { useEffect, useState } from 'react'
import { NumberRule, StringRule, Validation, ValidationModel, ValidationResult } from 'cnr-validation'
import './App.css'
import { RiErrorWarningFill } from 'react-icons/ri'

function App() {
  const validationModel: ValidationModel = {
    name: () => new StringRule(name).required().min(5).max(10).endsWith('er'),
    age: () => new NumberRule(age).required().min(18).max(120),
    email: () => new StringRule(email).required().email(),
    bio: () => new StringRule(bio).max(100)
  }

  const [name, setName] = useState<string>('')
  const [age, setAge] = useState<number>(18)
  const [email, setEmail] = useState<string>('')
  const [bio, setBio] = useState<string>('')
  const [validationResult, setValidationResult] = useState<ValidationResult>(validate())

  useEffect(() => {
    setValidationResult(validate())
  }, [name, age, email, bio])

  function validate(): ValidationResult {
    return new Validation(validationModel).validate()
  }

  function handleFormSubmit(e: any) {
    e.preventDefault()

    for (let r in validationResult) {
      if (!validationResult[r].success) {
        alert('Form is not valid\n' + JSON.stringify({
          name: validationResult.name?.error?.message,
          age: validationResult.age?.error?.message,
          email: validationResult.email?.error?.message,
          bio: validationResult.bio?.error?.message,
        }))
        return
      }
    }

    alert('Form is valid\n' + JSON.stringify({
      name: name,
      age: age,
      email: email,
      bio: bio,
    }))
  }

  return (
    <div className="main-container">
      <header>
        <h2>VALIDATION TEST FORM</h2>
      </header>
      <form className="example-form" onSubmit={handleFormSubmit}>
        <div className="form-group">
          <div>
            <label htmlFor="name">Name</label>
            <div
              className="input-error-message"
              style={{ display: validationResult.name?.error?.message ? 'flex' : 'none' }}
            >
              <span>{validationResult.name?.error?.message}</span>
              <RiErrorWarningFill />
            </div>
          </div>
          <input type="text" name="name" id="name" value={name}
            onChange={(e: any) => setName(e.target.value)} />
        </div>
        <div className="form-group">
          <div>
            <label htmlFor="age">Age</label>
            <div
              className="input-error-message"
              style={{ display: validationResult.age?.error?.message ? 'flex' : 'none' }}
            >
              <span>{validationResult.age?.error?.message}</span>
              <RiErrorWarningFill />
            </div>
          </div>
          <input type="number" name="age" id="age" value={age}
            onChange={(e: any) => setAge(parseInt(e.target.value))} />
        </div>
        <div className="form-group">
          <div>
            <label htmlFor="email">Email</label>
            <div
              className="input-error-message"
              style={{ display: validationResult.email?.error?.message ? 'flex' : 'none' }}
            >
              <span>{validationResult.email?.error?.message}</span>
              <RiErrorWarningFill />
            </div>
          </div>
          <input type="text" name="email" id="email" value={email}
            onChange={(e: any) => setEmail(e.target.value)} />
        </div>
        <div className="form-group">
          <div>
            <label htmlFor="bio">Bio</label>
            <div
              className="input-error-message"
              style={{ display: validationResult.bio?.error?.message ? 'flex' : 'none' }}
            >
              <span>{validationResult.bio?.error?.message}</span>
              <RiErrorWarningFill />
            </div>
          </div>
          <textarea name="bio" id="bio" value={bio} rows={4}
            onChange={(e: any) => setBio(e.target.value)} />
        </div>
        <button type="submit">Submit</button>
      </form>
    </div>
  )
}

export default App

Tests of the Package

String type checking tests - [✔] - 6 test

TypeValueDescriptionExpectedResultPassedError CodeError NameError Description
number32String type testfalsefalse[✔]200InvalidTypeErrorValue must be a string
undefinedundefinedString type testfalsefalse[✔]200InvalidTypeErrorValue must be a string
nullnullString type testfalsefalse[✔]200InvalidTypeErrorValue must be a string
booleanfalseString type testfalsefalse[✔]200InvalidTypeErrorValue must be a string
NaNNaNString type testfalsefalse[✔]200InvalidTypeErrorValue must be a string
arraya,b,c,1,2,3String type test with an arrayfalsefalse[✔]200InvalidTypeErrorValue must be a string

String basic tests - [✔] - 25 test

TypeValueDescriptionExpectedResultPassedError CodeError NameError Description
stringString required testfalsefalse[✔]201RequiredErrorValue is required
stringabcString required testtruetrue[✔]---
stringabcString min length test - min: 4falsefalse[✔]300MinimumLengthErrorValue's character length can't be less than 4
stringabcdString min length test - min: 4truetrue[✔]---
stringString min length test - min: 1falsefalse[✔]300MinimumLengthErrorValue's character length can't be less than 1
stringabcdeString max length test - max: 4falsefalse[✔]301MaximumLengthErrorValue's character length can't be more than 4
stringabcdString max length test - max: 4truetrue[✔]---
stringabcString max length test - max: 4truetrue[✔]---
stringabcdString length test - length: 5falsefalse[✔]302LengthErrorValue's character length must be exactly 5 long
stringabcdefString length test - length: 5falsefalse[✔]302LengthErrorValue's character length must be exactly 5 long
stringabcdeString length test - length: 5truetrue[✔]---
stringabc_HELLOString starts with test [exactly] - with: abc_truetrue[✔]---
stringxyztHELLOString starts with test [exactly] - with: abc_falsefalse[✔]304StartsWithErrorValue must start with exactly abc_
stringABC_HELLOString starts with test [exactly] - with: abc_falsefalse[✔]304StartsWithErrorValue must start with exactly abc_
stringABC_HELLOString starts with test [not exactly] - with: abc_truetrue[✔]---
stringAbc_HELLOString starts with test [not exactly] - with: abc_truetrue[✔]---
stringöÖçÇiİüÜ#+?=-HELLOString starts with test [exactly] - with: öÖçÇiİüÜ#+?=-truetrue[✔]---
stringöÖçÇiİüÜ#+?-HELLOString starts with test [not exactly] - with: ööççiiüü#+?=-truetrue[✔]---
stringHELLO_guysString ends with test [exactly] - with: _guystruetrue[✔]---
stringHELLOgirlsString ends with test [exactly] - with: _guysfalsefalse[✔]305EndsWithErrorValue must end with exactly _guys
stringHELLO_GUYSString ends with test [exactly] - with: _guysfalsefalse[✔]305EndsWithErrorValue must end with exactly _guys
stringHELLO_GUYSString ends with test [not exactly] - with: _guystruetrue[✔]---
stringHELLO_GuysString ends with test [not exactly] - with: _guystruetrue[✔]---
stringHELLOöÖçÇiİüÜ#+?=-String ends with test [exactly] - with: öÖçÇiİüÜ#+?=-truetrue[✔]---
stringHELLOöÖçÇiİüÜ#+?-String ends with test [not exactly] - with: ööççiiüü#+?=-truetrue[✔]---

String email tests - [✔] - 10 test

TypeValueDescriptionExpectedResultPassedError CodeError NameError Description
stringEmail validation testfalsefalse[✔]303InvalidEmailErrorValue must contain the @ character
stringaEmail validation testfalsefalse[✔]303InvalidEmailErrorValue must contain the @ character
stringabc.comEmail validation testfalsefalse[✔]303InvalidEmailErrorValue must contain the @ character
stringabc@Email validation testfalsefalse[✔]303InvalidEmailErrorValue must contain a dot at domain
stringabc@.Email validation testfalsefalse[✔]303InvalidEmailErrorValue must contain domain (like abc.com)
stringabc@.comEmail validation testfalsefalse[✔]303InvalidEmailErrorValue must contain domain (like abc.com)
stringabc@xyzEmail validation testfalsefalse[✔]303InvalidEmailErrorValue must contain a dot at domain
stringabc@xyz.Email validation testfalsefalse[✔]303InvalidEmailErrorValue must contain domain (like abc.com)
stringab c@xy z. co mEmail validation testfalsefalse[✔]303InvalidEmailErrorValue mustn't contain any whitespace characters.
stringabc@xyz.comEmail validation testtruetrue[✔]---

String custom tests - [✔] - 4 test

TypeValueDescriptionExpectedResultPassedError CodeError NameError Description
stringabcCustom validation test - Must be a numeric characterfalsefalse[✔]-ErrorValue must include a number
stringab9cCustom validation test - Must be a numeric charactertruetrue[✔]---
stringabc@xyz.comRegex validation test - Email regextruetrue[✔]---
stringabc.comRegex validation test - Email regexfalsefalse[✔]-ErrorEmail format is not valid

String rule chaining tests - [✔] - 7 test

TypeValueDescriptionExpectedResultPassedError CodeError NameError Description
stringa_bc@xyz.netChaining-1 required-min-max-email-custom (Must include _)-regex (Must end with net) testtruetrue[✔]---
stringa_bc@xyz.comChaining-1 required-min-max-email-custom (Must include _)-regex (Must end with net) testfalsefalse[✔]-ErrorMust end with net
stringabc@xyz.comChaining-1 required-min-max-email-custom (Must include _)-regex (Must end with net) testfalsefalse[✔]-ErrorMust include _
stringabc@xyz.comXXXXXXXXXOChaining-1 required-min-max-email-custom (Must include _)-regex (Must end with net) testfalsefalse[✔]301MaximumLengthErrorValue's character length can't be more than 20
stringa@x.cChaining-1 required-min-max-email-custom (Must include _)-regex (Must end with net) testfalsefalse[✔]300MinimumLengthErrorValue's character length can't be less than 11
stringChaining-1 required-min-max-email-custom (Must include _)-regex (Must end with net) testfalsefalse[✔]201RequiredErrorValue is required
string32Chaining-1 required-min-max-email-custom (Must include _)-regex (Must end with net) testfalsefalse[✔]200InvalidTypeErrorValue must be a string

Number type checking tests - [✔] - 9 test

TypeValueDescriptionExpectedResultPassedError CodeError NameError Description
number-32Number type test - negativetruetrue[✔]---
number32Number type test - positivetruetrue[✔]---
number32.55Number type test floattruetrue[✔]---
string32Number type test - stringfalsefalse[✔]200InvalidTypeErrorValue must be a number
undefinedundefinedNumber type testfalsefalse[✔]200InvalidTypeErrorValue must be a number
nullnullNumber type testfalsefalse[✔]200InvalidTypeErrorValue must be a number
booleanfalseNumber type testfalsefalse[✔]200InvalidTypeErrorValue must be a number
NaNNaNNumber type testfalsefalse[✔]200InvalidTypeErrorValue must be a number
arraya,b,c,1,2,3Number type test with an arrayfalsefalse[✔]200InvalidTypeErrorValue must be a number

Number tests - [✔] - 13 test

TypeValueDescriptionExpectedResultPassedError CodeError NameError Description
undefinedundefinedNumber required testfalsefalse[✔]200InvalidTypeErrorValue must be a number
number32Number required testtruetrue[✔]---
number32Number min value testfalsefalse[✔]400MinNumberErrorValue can't be less than 40
number40Number min value testtruetrue[✔]---
number40Number min value testfalsefalse[✔]400MinNumberErrorValue can't be less than 40.75
number40.75Number min value testtruetrue[✔]---
number45Number max value testfalsefalse[✔]401MaxNumberErrorValue can't be more than 40
number40Number max value testtruetrue[✔]---
number41Number max value testfalsefalse[✔]401MaxNumberErrorValue can't be more than 40.75
number40.75Number max value testtruetrue[✔]---
number8.75Number equal value testfalsefalse[✔]402EqualNumberErrorValue must be equal to 8
number8Number equal value testtruetrue[✔]---
number8Number equal value test - 40/5=8?truetrue[✔]---

Custom number tests - [✔] - 1 test

TypeValueDescriptionExpectedResultPassedError CodeError NameError Description
number30Number custom test 100/4=25?falsefalse[✔]-ErrorValue must be 1/4 of 100

Number rule chaining tests - [✔] - 5 test

TypeValueDescriptionExpectedResultPassedError CodeError NameError Description
number50Number chain rules: required, min(25), max(50), equal(50), custom(100/2)truetrue[✔]---
number40Number chain rules: required, min(25), max(50), equal(50), custom(100/2)falsefalse[✔]402EqualNumberErrorValue must be equal to 50
number20Number chain rules: required, min(25), max(50), equal(50), custom(100/2)falsefalse[✔]400MinNumberErrorValue can't be less than 25
number70Number chain rules: required, min(25), max(50), equal(50), custom(100/2)falsefalse[✔]401MaxNumberErrorValue can't be more than 50
numberundefinedNumber chain rules: required, min(25), max(50), equal(50), custom(100/2)falsefalse[✔]200InvalidTypeErrorValue must be a number
0
Subscribe to my newsletter

Read articles from caner demirci directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

caner demirci
caner demirci

Programming is fun. It is nice to spend time with it and not realize how time passes, learning new things... I hope one day I will make awesome things with it.