A New ORM for Node.js
Presented by:
- Asjad Saboor
- Hussain Ali Akbar
Prevalent Company Culture
- It is mandatory to use Typescript in all the Node.js projects
- Sequelize is the current de facto ORM for all the Node.js projects
Sequelize + Typescript
- Written in Javascript. No official Typescript typings.
- The maintainers have rejected the proposal to provide official typings.
- The community driven typings at definitely typed make Sequelize workable but do not provide any significant advantage for typescript.
- Complicated and confusing documentation
- Higher learning curve
- ts-sequelize is available but not popular
Why not Sequelize? (completely opinionated)
- We explored the following options:
- Knex
- Loopback
- Typeorm
In search for an Alternative
- Knex has an amazing query builder
- But it is not a full blown ORM per se
Why not Knex?
- Typescript typings are maintained by the community
- knex-next (typescript + internal rewrite) is a work in progress but does not have a complete release
- It is a complete framework like express and koa and not an ORM
- Loopback 4 is in Typescript but wasn't released back then
Why not Loopback?
- Loopback 3 doesn't have official typings
- Written in Typescript
- Influenced by Hibernate and Entity Framework
- Easy to learn
- Easy to maintain
- Typescript classes allows extension for custom logic
- Well documented
- Built in Cache (with a catch)
- Amazing knex like query builder
- Support for all major databases
- And much more!
Why TypeORM?
- Entities
import { Entity, PrimaryGeneratedColumn, Column, OneToMany, Index } from 'typeorm';
import { LanguageData } from './language-data';
@Entity('states')
export class State {
@PrimaryGeneratedColumn()
public id: number;
@Column({
type: 'character varying',
length: 255,
unique: true,
nullable: false,
})
@Index()
public code: string;
@OneToMany(() => LanguageData, languageData => languageData.state)
public languageData: LanguageData[];
}
// complete decorator reference:
// https://github.com/typeorm/typeorm/blob/master/docs/decorator-reference.md
// https://github.com/typeorm/typeorm/blob/master/docs/entities.md
TypeORM 101
- Entities
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany, JoinColumn } from 'typeorm';
import { LanguageData } from './language-data';
import { State } from './state';
@Entity('cities')
export class City {
@PrimaryGeneratedColumn()
public id: number;
@Column({
type: 'number',
default: false,
})
public stateId: number;
@ManyToOne(() => State, { nullable: false })
@JoinColumn({
name: 'stateId',
referencedColumnName: 'id',
})
public state: State;
@Column('point', { nullable: true })
public geom: object;
@OneToMany(() => LanguageData, languageData => languageData.city)
public languageData: LanguageData[];
}
// complete decorator reference:
// https://github.com/typeorm/typeorm/blob/master/docs/decorator-reference.md
// https://github.com/typeorm/typeorm/blob/master/docs/entities.md
TypeORM 101
- Extending entities
import { CreateDateColumn, UpdateDateColumn } from 'typeorm';
export abstract class AuditColumn {
@UpdateDateColumn()
public updatedAt: Date;
@CreateDateColumn()
public createdAt: Date;
}
@Entity('users')
export class User extends AuditColumn {}
TypeORM 101
- Queries with builtin caching
export const findById = async (id: number, roleName?: string):
Promise<City | undefined> => {
const cacheKey = `${CachePrefixes.CITY}_findById_id_${id}`;
const cityRepo = RepositoryFactory.getCityRepository(roleName);
return cityRepo.findOne({
where: {
id,
},
cache: {
id: cacheKey,
milliseconds: config.default.database.cacheDuration,
},
});
};
// https://github.com/typeorm/typeorm/blob/master/docs/caching.md
// https://github.com/typeorm/typeorm/blob/master/docs/find-options.md
TypeORM 101
- Query Builder
export const countAllOwners = async (roleName?: string): Promise<number> => {
return await RepositoryFactory.getUserRepository(roleName)
.createQueryBuilder('users')
.innerJoin('users.userType', 'userType')
.where(`userType.key in ('${UserTypes.CLIENT}', '${UserTypes.BETTS}')`)
.getCount();
};
// https://github.com/typeorm/typeorm/blob/master/docs/select-query-builder.md
// https://github.com/typeorm/typeorm/blob/master/docs/relational-query-builder.md
TypeORM 101
- Extending TypeORM through the Repository Factory
TypeORM 101
class TypeORMRepositoryWrapper<Entity> {
public entityTableName: string;
public roleName: string;
public entityClass: ObjectType<Entity>;
public repository: Repository<Entity>;
constructor(
entityTableName: string, roleName: string, entityClass: ObjectType<Entity>,
connectionName: string = 'default',
) {
this.entityTableName = entityTableName;
this.roleName = roleName;
this.entityClass = entityClass;
this.repository = getConnectionManager()
.get(connectionName)
.getRepository<Entity>(this.entityClass);
this.repository.save = async ( // and more methods like update, find, findOne etc
entityOrEntities: Entity | Entity[], options?: SaveOptions,
): Promise<any> => {
let sanitizedData: any;
sanitizedData = await sanitizeRecursively(entityOrEntities, this.roleName);
return this.repository.manager.save(
this.repository.metadata.target,
sanitizedData as any,
options,
);
};
}
- Extending TypeORM through the Repository Factory
TypeORM 101
export class RepositoryFactory {
public static getUserRepository = (roleName: string = ROLES.default.name): Repository<User> => {
return new TypeORMRepositoryWrapper(ENTITIES.users.tableName, roleName, User).repository;
};
public static getCandidateRepository = (
roleName: string = ROLES.default.name,
): Repository<Candidate> => {
return new TypeORMRepositoryWrapper(ENTITIES.candidates.tableName, roleName, Candidate)
.repository;
};
public static getCityRepository = (roleName: string = ROLES.default.name): Repository<City> => {
return new TypeORMRepositoryWrapper(ENTITIES.cities.tableName, roleName, City).repository;
};
}
// and many more
- TypeORM is far from being as mature as Sequelize
- TypeORM 1.0 is still not released but its expected to be available by the end
of this year
- A lot of major features are not supported such as views
- Builtin Cache creates its own Redis Connection
- The documentation for the next version of TypeORM does not exist so we
have to go look into the code.
- Slow response on github issues.
Some Gotchas and Drawbacks of TypeORM
Feel free to contact these persons if you get stuck with TypeORM:
- Asjad Saboor
- Hussain Ali Akbar
- M. Owais Kalam
- Waqas Kiffal
Questions?
- dont forget typeORM's own documentation:
https://github.com/typeorm/typeorm/tree/master/docs
http://typeorm.io/
Thank You