We're planting a tree for every job application! Click here to learn more

Type Safety, ORM and Dependency Injection with node.js

Paulo Suzart

28 Feb 2018

•

5 min read

Type Safety, ORM and Dependency Injection with node.js
  • TypeScript

Many people know I come from Java world. Did JVM stuff from 2004 to 2016 without a break. So you might think I miss the static type check, etc, right? And yes, I do. Wouldn't say missing is the right word, but I think it brings a lot of advantages!

Intro

Although extremely slow JVM startup time, poor ecosystem and crazy build tools, Java (and here I extend to Kotlin and Scala) provides good type systems with support for OO and FP styles. This may be seen as a slow down to productivity at first, but as you repeatedly work with dynamic langs like Python and ES6, the productivity you get for not having to care about types can escalate and become a burden if you have insufficient documentation and testing.

Working in a product entirely written in Node.js allowed us to try pure ES6, ES6 + Facebook's Flow and lately, our main API with TypeScript. Let's check it out.

ORM

All projects involved require ORM for one or other reason. We have one project using Bookshelf and I have to confess this is a pretty neat piece of software. It is unlikely that for this specific service we have to replace Bookshelf for anything else due to the dynamic nature of the service. It plays nicely with Flow with one not interfering with the other.

But this is a singular case where the server queries an arbitrary database with models, relations and everything else defined at runtime. For a more traditional API, we had the chance to use types everywhere.

For this case we used TypeORM and considering a lib in its early 0.1.0-alpha5.0, I have seen few projects with such maturity and excellence in the Node world.

It resembles Hibernate, but simpler and with a powerful QueryBuilder API that doesn't get in your way. Take the simple entity definition below as an example.

@Entity()
export class Organization extends BaseEntity {

  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column()
  jwtSecret: string;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;

  @ManyToOne(type => User, owner => owner.owns, { eager: true })
  @JoinColumn({ name: "ownerId" })
  owner: User;

  @AfterInsert()
  addJwtSecret() {
    this.jwtSecret = 'some-random-secret';
  }
}


// sample use
const org = const Organization.findOneById(3);

Check that by extending BaseEntity you can use Active Record like static methods, making it easy to use and manipulate data. Also, pay attention to @AfterInsert() decorator that allows for modifying the entity itself before it is flushed to the database. More listeners available.

TypeORM also makes it easy to add common columns like the date of creation of a record and the date of the last update by leveraging @UpdateDateColumn() and @CreateDateColumn().

Migrations

Another super key point here is the ability to generate migrations. There are couple options out there like Sequelize and Loopback provide some good tools, but none of them compares to Django's migration capability, for example. This is not the case for TypeORM that is able to generate precise migrations.

By using ts-node node_modules/.bin/typeorm migrations:generate -n Bootstrap, TypeORM will generate a migration like this:

export class Bootstrap1507151187498 implements MigrationInterface {

    public async up(queryRunner: QueryRunner): Promise<any> {
        await queryRunner.query("CREATE TABLE `organization` (`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT, `name` varchar(255) NOT NULL, `jwtSecret` varchar(255) NOT NULL, `createdAt` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), `updatedAt` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), `ownerId` int(11)) ENGINE=InnoDB");
    }
    // ... down function continues from here

It looks awesome to me. You can customize the generated migration, as long as you keep up and down compatible, or you can create an empty migration where you specify everything by hand.

Migrations are an important piece of productivity and safety IMO, and TypeORM passes with flying colours.

DI (Dependency Injection)

What is a life of a lifelong Java developer without dependency injection? :D Of course, this is a joke, it is not something required, but helps keep things pretty tidy.

Take for example GraphQL resolvers. What people usually do is passing a bloated context full of whatever might be needed in a given depth of a query. This actually works, but you can use TypeDI and use its container to manage and use Services, Factories, and also most TypeORM related objects.

Taking our sample entity, we could have a service written like this:

@Service()
export class OrganizationService {
  private repository: Repository<Organization>;

  constructor(@OrmRepository(Organization) repository: Repository<Organization>) {
    this.repository = repository;
  }

  byId(id: number): Promise<Organization> {
    return this.repository.createQueryBuilder("organization")
      .where("id=:id")
      .setParameters({ id: id })
      .getOne();
  }

}

And in your GraphQL resolver, you could get an instance of this Service like this:

import { Container } from "typedi";

const resolvers = {
  addMember(root: any, { input }: { input: AddMemberInputType } , context: any, info: any): any {
    const organizationService = Container.get(OrganizationService);
    // ... do your logic here
  }
}

Of course, by using Organization as a BaseEntity such service is not needed, but take it as an illustration.

In this case, the Container is being accessed directly, but if you had this addMember as a member of a class you could inject the OrganizationService and much more.

TypeDI supports factories, so you can take full control on how / when / etc services are instantiated. Being somehow optimistic, TypeORM is the Google Guice of TypeScript.

By adding typeorm-typedi-extensions, it is possible to use @OrmRepository and other decorators to help you grab the instances at will.

TypeScript

This language reminds me C# a lot. Not that I'm a specialist in C#, but from my researches, it is a close relative.

You might be wondering, if you are using node.js with a strongly typed language, why don't you continue your life with Java or Kotlin or Scala?. Well, I wouldn't say TypeScript is strongly typed, it is just typed a la carte. You could replace everything with an any and it would still be more practical than using java.lang.Object. Of course, you don't want to do that and waste your type support, right?

This is the good thing, you can step down and use javascript maps or any other "typeless" structure at any time without ceremony. You can also sort of cast an object to a rich TypeScript type. I think TypeScript is the middle ground giving you full control, safety and protecting your team from discovering that bug only in production.

It also offers cool Advanced Type features like Intersection Types, Union Types, Nullable Types, String Literal Types and Discriminated Unions. These features make this language really powerful and expressive, even looking like Java or C#.

What else do you get? I would summarize other important points like:

  • No require, just import
  • Forward export. So you can re-export in an index.ts the local exports of other modules
  • Turn off types at will. You may produce types that are hard to predict their shape, or if it isn't worth the effort, just use any
  • Better IDE / Editor support
  • Documentation that works. We've struggled to get decent code documentation generation for ES6 and Flow. But using Typedoc it works VSCode like a glove

Conclusion


I like Facebook's Flow and for some scenarios, will be forced to continue using it. But for many others, TypeScripts is the best option IMO. As your team grows, your code base grows and as you release more to production, having support from types without compromising productivity is simply awesome.

People that started their careers in the node.js world may dislike this position, but I can assure these conclusions come with a relevant background, blood and sweat.

Happy TypeScript!


If you're looking to work with Typescript, check out Typescript jobs on our job-board!

Did you like this article?

Paulo Suzart

More than 13 years of experience with Java, Enterprise Applications, and 5 of these years working hard as Hands On CTO / Tech Leader in exciting startups. Currently a remoter (based in Colombia)

See other articles by Paulo

Related jobs

See all

Title

The company

  • Remote

Title

The company

  • Remote

Title

The company

  • Remote

Title

The company

  • Remote

Related articles

JavaScript Functional Style Made Simple

JavaScript Functional Style Made Simple

Daniel Boros

•

12 Sep 2021

JavaScript Functional Style Made Simple

JavaScript Functional Style Made Simple

Daniel Boros

•

12 Sep 2021

WorksHub

CareersCompaniesSitemapFunctional WorksBlockchain WorksJavaScript WorksAI WorksGolang WorksJava WorksPython WorksRemote Works
hello@works-hub.com

Ground Floor, Verse Building, 18 Brunswick Place, London, N1 6DZ

108 E 16th Street, New York, NY 10003

Subscribe to our newsletter

Join over 111,000 others and get access to exclusive content, job opportunities and more!

© 2024 WorksHub

Privacy PolicyDeveloped by WorksHub