npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

data-transfer-object

v0.4.0

Published

Data Transfer Object class built on `class-validator`.

Downloads

608

Readme

data-transfer-object

Data Transfer Object class built on TypeStacks's class-validator. Allows you to build a class that validates input from outside sources and ensures its shape is correct. Full TypeScript support with strict, strong typing.

Table of contents

Installation

For Node.js and bundlers (Webpack, Rollup, etc)

npm i data-transfer-object
# or
yarn add data-transfer-object

For browsers (script tag)

Browser builds aren't available yet, but they are planned for the future.

Release notes

See information about breaking changes and release notes here.

Usage

Create your own classes by importing and extending DataTransferObject with your own properties, and using the appropriate validator decorators. This package re-exports all decorators and functions from TypeStack's class-validator.

import { DataTransferObject, IsString } from 'data-transfer-object';

class MyDto extends DataTransferObject {
  // Insert decorated properties here
  @IsString()
  myString: string;
}

TypeScript note: when using TypeScript with "strict": true, you must use non-null assertions (!:) when declaring class properties. Also, experimentalDecorators and emitDecoratorMetadata must be set to true in your tsconfig.json.

Once your class is defined, you can construct a new instance of it, pass your input into it, and run .validate() on it. If your input is valid, it will return a plain object with the validated data. Otherwise, by default, a ValidationException will be thrown.


try {
  const validated = new MyDto(input).validate();
  console.log(validated);
  // validated === input
} catch (err) {
  if (err instanceof ValidationException) {
    console.error(err.validationErrors);
    // e.g. if `input` was `{ myString: 1234 }`, this would output:
    { "myString": ["property myString should be a string"] }
  }
}

Throwing a custom error on validation failure

You can use setValidationErrorHandler() to register a callback to run whenever a .validate() call fails on any object across your project. For instance:

import { setValidationErrorHandler, ValidationErrorMap } from 'data-transfer-object';

class CustomError extends Error {
  statusCode = 400;
  errors: ValidationErrorMap;

  constructor(errors: ValidationErrorMap) {
    super('Failed to validate');
    this.errors = errors;
  }
}

setValidationErrorHandler(errors => {
  throw new CustomError(errors);
});

try {
  new MyExampleDto({ invalidData: 42 }).validate();
} catch (err) {
  if (err instanceof CustomError) {
    console.error(`Request failed with error code ${err.statusCode}`);
    console.error(err.errors);
  }
}

Accessing raw error data in custom error handler

Under the hood, data-transfer-object formats the raw array of ValidationError returned by class-validator into an object mapping keys to properties and values to validation messages (aka a ValidationErrorMap).

If you wish to get direct access to the array of ValidationError in your error handler, you may pass in { rawErrors: true } as a second parameter to .setValidationErrorHandler():

import { setValidationErrorHandler } from 'data-transfer-object';

setValidationErrorHandler(
  errors => {
    // `errors` here is now of type `ValidationError[]`.
    console.error(errors);
    // example output:
    [
      {
        target: { myString: 12 },
        value: 12,
        property: 'myString',
        children: [],
        constraints: { isString: 'myString must be a string' },
      },
    ];
  },
  { rawErrors: true },
);

Get validation errors without throwing an error

You may use .getValidationErrors() to get an array of ValidationError without having to thrown an actual error. To get the plain data later, use .toJSON().

const dto = new MyDto({ someData: 'data' });
const errors = dto.getValidationErrors();
if (errors.length) {
  console.error(errors);
  return;
}
const data = dto.toJSON();

Validating it will also silently drop all unknown properties from the input:

const input = new MyDto({ myString: 'a', myOtherString: 'b' });
const errors = input.validate();

console.log(errors); // []
console.log(input); // { myString: 'a' }

Take a look at class-validator's documentation to get information on all available validators and validation options.

Run async validators

.validate() and .getValidationErrors() will only run synchronous validators, which covers most use cases in a web application. If you would like to run asynchronous validators as well, use .validateAsync() and .getValidationErrorsAsync() respectively. These counterparts will always return a promise, even if there are no async validators on the validated object.

Full API Documentation

The most up-to-date documentation for all exported items from this package is automatically generated from code and available at https://danielegarciav.github.io/data-transfer-object/.

You may wish to uncheck the "Externals" checkbox option (top-right on desktop, cog icon on mobile) in order to hide documentation from class-validator and focus only on what this package exports.

Comprehensive example

In the following example, we have an Express application that lets us sign up users.

  • We define an UserSignupInput data transfer object class.
  • We define our own custom errors ApiError and ApiValidationError, which allow us to define a status code, among other things.
  • We define an Express error handler to catch any errors and format the result that will be sent back to our client.
  • We wire everything up as an Express app, and make it so validation errors throw an ApiValidationError, which will be then appropriately handled by our Express error handler.
  • We can then write our signup controller.
// input-classes.ts

import { DataTransferObject, IsString, Length, MinLength } from 'data-transfer-object';

export class UserSignupInput extends DataTransferObject {
  @IsString()
  @Length(2, 36, {
    message: 'username must be 2-36 characters long',
  })
  username!: string;

  @IsString()
  @MinLength(8, {
    message: 'password must be at least 8 characters long',
  })
  password!: string;
}
// custom-errors.ts

import { ValidationErrorMap } from 'data-transfer-object';

export class ApiError extends Error {
  type: string;
  statusCode: number;

  constructor(type: ApiErrorType, statusCode?: number, message?: string) {
    super(message);
    this.name = 'ApiError';
    this.type = type;
    this.message = message ?? 'Unspecified error';
    this.statusCode = statusCode ?? 500;
  }
}

export class ApiValidationError extends ApiError {
  validationMessages: ValidationErrorMap;
  constructor(errors: ValidationErrorMap) {
    super('InvalidRequest', 400, 'Request is invalid');
    this.validationMessages = errors;
  }
}
// error-handler.ts

import { Request, Response, NextFunction } from 'express';
import { ApiError } from './custom-errors';

export function expressErrorHandler(
  error: Error | ApiError,
  req: Request,
  res: Response,
  next: NextFunction,
) {
  const statusCode = error instanceof ApiError ? error.statusCode : 500;
  const jsonResponse = { error: { ...error, statusCode } };

  // Log non-ApiErrors to console
  if (!(error instanceof ApiError)) console.error(error);

  res.status(statusCode).json(jsonResponse);
}
// express-server.ts

import Express from 'express';
import { setValidationErrorHandler } from 'data-transfer-object';
import { expressErrorHandler } from './error-handler.ts';
import { ApiValidationError } from './custom-errors.ts';
import { signup } from './signup-controller.ts';

setValidationErrorHandler(errors => {
  throw new ApiValidationError(errors);
});

export const app = Express();
app.use(Express.json());
app.use('/users/signup', signup);
app.use(expressErrorHandler);
// signup-controller.ts

import { Request, Response, NextFunction } from 'express';
import { User } from './models';
import { UserSignupInput } from './input-classes';

export async function signup(req: Request, res: Response, next: NextFunction) {
  try {
    const { username, password } = new UserSignupInput(req.body).validate();
    await User.register(username, password);
    return res.status(200).json({ success: true });
  } catch (err) {
    return next(err);
  }
}

We may also use something like express-async-errors in order to avoid having to wrap our async code in try/catch statements and manually calling next(). This is something that will be automatically addressed by Express 5 once it is released.

Development and contributions

Check package.json to find scripts related to installing dependencies, building, testing, linting and generating documentation. I am open to new issues and pull requests!

License

MIT