Skip to content

TypeScript Node.js sample

Aaron Hanusa edited this page Mar 11, 2019 · 7 revisions

This sample requires nodejs and TypeScript.

Start by creating a directory to house your code.

From a command line within your new directory, initialize a new project via:

  • npm init

Next, install and initialize TypeScript via:

  • npm i typescript --save-dev
  • tsc --init --target es6

Finally, install peasy-js via:

  • npm: npm install peasy-js.

Now let's write some code. Create a file (person.ts) with the following content:

// person.ts

export interface IPerson {
  id: number;
  name: string;
  city: string;
}

Next create a data proxy by creating a file (personDataProxy.ts) with the following content:

// personDataProxy.ts

declare const Promise: any;
import { IDataProxy } from 'peasy-js';
import { IPerson } from './person';

export class PersonDataProxy implements IDataProxy<IPerson, number> {

  private _data: IPerson[] = [];

  public getById(id: number): Promise<IPerson> {
    const person = this._findBy(id);
    return Promise.resolve(person);
  }

  public getAll(): Promise<IPerson[]> {
    return Promise.resolve(this._data);
  }

  public insert(data: IPerson): Promise<IPerson> {
    data.id = this._data.length + 1;
    const newPerson = Object.assign({} as IPerson, data);
    this._data.push(newPerson);
    return Promise.resolve(data);
  }

  public update(data: IPerson): Promise<IPerson> {
    const person = this._findBy(data.id);
    Object.assign(person, data);
    return Promise.resolve(data);
  }

  public destroy(id: number): Promise<void> {
    const person = this._findBy(id);
    const index = this._data.indexOf(person);
    this._data.splice(index, 1);
    return Promise.resolve();
  }

  private _findBy(id: number): IPerson {
    const person = this._data.filter(p => {
      return p.id === id;
    })[0];
    return person;
  }
}

Then, create a business service file (personService.ts), which exposes CRUD commands responsible for subjecting data proxy invocations to business rules before execution:

// personService.ts

import { BusinessService, IDataProxy } from "peasy-js";
import { IPerson } from "./person";

export class PersonService extends BusinessService<IPerson, number> {
  constructor(dataProxy: IDataProxy<IPerson, number>) {
    super(dataProxy);
  }
}

Now let's consume our PersonService by creating a file (example.ts) with the following contents:

// example.ts

import { PersonService } from "./personService";
import { PersonDataProxy } from "./personDataProxy";
import { IPerson } from "./person";

const personService = new PersonService(new PersonDataProxy());
const command = personService.insertCommand({name: "James Morrison"} as IPerson);

command.execute().then(result => {
  if (result.success) {
    console.log(result.value); // prints the inserted object with the assigned id
  }
});

Testing it out from command line: tsc && node example.js

Let's create a business rule file (personNameRule.ts) whose execution must be successful before the call to inject dataproxy.insert() function is invoked.

// personNameRule.ts

declare const Promise: any;
import { Rule } from "peasy-js";

export class PersonNameRule extends Rule {

  constructor(private name: string) {
    super();
    this.association = "name"; // optional
  }

  protected _onValidate(): Promise<void> {
    if (this.name === "Jimi Hendrix") {
      this._invalidate("Name cannot be Jimi Hendrix");
    }
    return Promise.resolve();
  }
}

And wire it up in our PersonService to ensure that it gets fired before inserts:

// personService.ts

declare const Promise: any;
import { BusinessService, IDataProxy, IRule } from "peasy-js";
import { IPerson } from "./person";
import { PersonNameRule } from "./personNameRule";

export class PersonService extends BusinessService<IPerson, number> {

  constructor(dataProxy: IDataProxy<IPerson, number>) {
    super(dataProxy);
  }

  protected _getRulesForInsertCommand(person: IPerson, context: any): Promise<IRule[]> {
    return Promise.resolve([
      new PersonNameRule(person.name)
    ]);
  }
}

Testing it out ...

// example.ts

import { PersonService } from "./personService";
import { PersonDataProxy } from "./personDataProxy";
import { IPerson } from "./person";

const personService = new PersonService(new PersonDataProxy());
const command = personService.insertCommand({name: "Jimi Hendrix"} as IPerson);

command.execute().then(result => {
  if (result.success) {
    console.log(result.value);
  } else {
    console.log(result.errors[0]); // prints {association: "name", message: "Name cannot be Jimi Hendrix"}
  }
});

Testing it out from command line: tsc && node example.js

Let's create one more rule (validCityRule.ts), just for fun:

// validCityRule.ts

declare const Promise: any;
import { Rule } from "peasy-js";

export class ValidCityRule extends Rule {

  constructor(private city: string) {
    super();
    this.association = "city"; // optional
  }

  protected _onValidate(): Promise<void> {
    if (this.city === "Nowhere") {
      this._invalidate("Nowhere is not a city");
    }
    return Promise.resolve();
  }
}

We'll associate this one with inserts too:

// personService.ts

declare const Promise: any;
import { BusinessService, IDataProxy, IRule } from "peasy-js";
import { IPerson } from "./person";
import { PersonNameRule } from "./personNameRule";
import { ValidCityRule } from "./validCityRule";

export class PersonService extends BusinessService<IPerson, number> {

  constructor(dataProxy: IDataProxy<IPerson, number>) {
    super(dataProxy);
  }

  protected _getRulesForInsertCommand(person: IPerson, context: any): Promise<IRule[]> {
    return Promise.resolve([
      new PersonNameRule(person.name),
      new ValidCityRule(person.city)
    ]);
  }
}

And test it out ...

// example.ts

import { PersonService } from "./personService";
import { PersonDataProxy } from "./personDataProxy";
import { IPerson } from "./person";

const personService = new PersonService(new PersonDataProxy());
const command = personService.insertCommand({name: "Jimi Hendrix", city: "Nowhere"} as IPerson);

command.execute().then(result => {
  if (result.success) {
    console.log(result.value);
  } else {
    console.log(result.errors[0]); // prints {association: "name", message: "Name cannot be Jimi Hendrix"}
    console.log(result.errors[1]); // prints {association: "city", message: "Nowhere is not a city"}
  }
});

Testing it out from command line: tsc && node example.js

Finally, let's pass in valid data and watch it be a success

// example.ts

import { PersonService } from "./personService";
import { PersonDataProxy } from "./personDataProxy";
import { IPerson } from "./person";

const personService = new PersonService(new PersonDataProxy());
const command = personService.insertCommand({name: "James Hendrix", city: "Madison"} as IPerson);

command.execute().then(result => {
  if (result.success) {
    console.log(result.value); // prints the inserted object with the assigned id
  } else {
    console.log(result.errors);
  }
});

Testing it out from command line: tsc && node example.js

Want to learn more?

  • You can read in-depth coverage about peasy-js on the wiki.
  • An entire business logic implementation using peasy-js can be viewed here. This sample application is an order entry / inventory management system written with peasy-js, react, angular (with TypeScript), mongoDB, nodejs, and express.
Clone this wiki locally