🟢 Your favourite library for validating incoming data in express.js.
🟢 Your favourite library for validating incoming data in express.js.
With the creation of a single class and usage of decorators, we achieve validation of the following content types (body
parser required):
Also, this library supports both ESM and CJS versions via dynamic exports in package.json.
class-validator
class-transformer
reflect-metadata
Optional, for multipart/form-data parsing only (These are truly optional):
multer (recommended)
formidable (yes, we support an ESM version only)
yarn add validness class-validator class-transformer reflect-metadata
npm install validness class-validator class-transformer reflect-metadata
Please import reflect-metadata in an entry file of your project in order for this package to work
import 'reflect-metadata'
// your code
For typescript decorators usage turn this option on in your tsconfig.json file.
{
"compilerOptions": {
"experimentalDecorators": true
}
}
As stated above, we’re going to use decorators.
Let’s create a DTO of commonly expected data:
registration.dto.ts
import { IsEmail, IsPhoneNumber } from "class-validator";
export class RegistrationDto {
@IsPhoneNumber()
phone: ValidatedFile;
@IsEmail()
email: string;
}
registration.controller.ts
import { Router } from "express";
import { validationBodyPipe, DefaultBodyError } from "validness";
import { RegistrationDto } from './registration.dto.ts';
import { StatusCodes } from "http-status-codes";
const router = Router()
// controller
router.post('/', validationBodyPipe(RegistrationDto), async (req, res, next) => {
const validatedBody = req.body as RegistrationDto;
await registerUser(validatedBody);
res.json({ message: 'SUCCESS' })
});
// Error handling
router.use((err, req, res, next) => {
// validness throws Default error models if customErrorFactory isn't sepcified.
// Standard models contain everything you need, like status code and errored fields
if (err instanceof DefaultBodyError) {
// implement handling logic
res.status(StatusCodes.BAD_REQUEST).json({ message: 'ERROR' });
}
});
Let’s create a query DTO with a use of decorators.
As you might know, query params always contain strings, you can transform them
with class-transformer decorators.
get-users-query.dto.ts
import { IsEmail, IsNotEmpty, IsNumberString, IsPhoneNumber } from "class-validator";
export class GetUsersQueryDto {
@IsString()
@IsNotEmpty()
query: string;
@IsNumberString()
pageIndex: string;
@IsNumberString()
perPage: string;
}
users.controller.ts
import { Router } from "express";
import { GetUsersQueryDto } from './get-users-query.dto.ts';
import { validationQueryPipe, DefaultQueryError } from "validness";
import { StatusCodes } from "http-status-codes";
const router = Router()
// controller
router.get('/', validationQueryPipe(GetUsersQueryDto), async (req, res, next) => {
const validatedQuery = req.query as GetUsersQueryDto;
await findUsers(validatedQuery);
res.json({ message: 'SUCCESS' })
});
// Error handling
router.use((err, req, res, next) => {
// validness throws Default error models if customErrorFactory isn't sepcified.
// Standard models contain everything you need, like status code and errored fields
if (err instanceof DefaultQueryError) {
// implement handling logic
res.status(StatusCodes.BAD_REQUEST).json({ message: 'ERROR' });
}
});
Install an underlying driver of your choice:
yarn add multer
yarn add -D @types/multer
npm install multer
npm install -D @types/multer
In the same simple way we create a new DTO.
sign-up.dto.ts
import { IsEmail, IsNotEmpty, IsNumberString, IsPhoneNumber, IsString } from "class-validator";
import { ValidatedFile, IsFiles, IsFile } from "validness";
export class SignUpDto {
// form-data text field
@IsString()
@IsNotEmpty()
firstName: string;
// form-data text field
@IsString()
@IsNotEmpty()
lastName: string;
// a single file
@IsFile({ type: 'image' })
photo: ValidatedFile
// a multiple files
@IsFiles({ type: 'image' })
documents: ValidatedFile[]
}
auth.controller.ts
import { Router } from "express";
import { SignUpDto } from './sign-up.dto.ts';
import { validationFilePipe, DefaultFileError } from "validness";
import { StatusCodes } from "http-status-codes";
const router = Router()
// controller
router.post('/', validationFilePipe(SignUpDto), async (req, res, next) => {
const validatedBody = req.body as SignUpDto;
// We do not use any disk storage engines for multer
// So, validatedBody.photo.buffer is accessbile and stored in memory
// Consult API section for more details
await signUp(validatedBody);
res.json({ message: 'SUCCESS' })
});
// Error handling
router.use((err, req, res, next) => {
// validness throws Default error models if customErrorFactory isn't sepcified.
// Standard models contain everything you need, like status code and errored fields
if (err instanceof DefaultFileError) {
// implement handling logic
res.status(StatusCodes.BAD_REQUEST).json({ message: 'ERROR' });
}
});
IMO this library does not have a convenient API, therefore, some decisions were made while developing mine.
If you decide to change core config of this library (coreConfig property), e.g. set max size for files there and not
in
the decorator, then the error thrown by formidable will NOT be mapped to DefaultFileError, but rather passed as is.
Each validation pipe has a couple of extra arguments to customise its or underlying libraries’ behaviour.
Let’s take a look at the validationFilePipe and validationBodyPipe signatures.
// ...
export const validationFilePipe =
(DtoConstructor: ClassConstructor, config?: ValidationFileConfig): Router => {
}
// ...
export const validationBodyPipe =
(DtoConstructor: ClassConstructor, config?: ValidationBodyConfig): RequestHandler => {
}
// ...
If you want to customise config globally for all pipes, use
validness function and pass an object with options there. (Be careful with what you change in coreConfig)
import { validness } from "validness";
validness({
// And many other options..
customErrorFactory: errorFactory,
queryValidationConfig: {
enableDebugMessages: true
}
});
Defaults can be found HERE
I think overall documentation for each property is not required because they’re well commented in the
code itself.
If you feel a lack of examples - open an issue, I will add as many as you want. Thanks.
If you wish to contribute to evolving of this package, please submit your issues or even open pull requests. You’re
always welcome. 🥰
MIT © Sole Cold