nestjs-objection

Nest - modern, fast, powerful node.js web framework (@objection)

Usage no npm install needed!

<script type="module">
  import nestjsObjection from 'https://cdn.skypack.dev/nestjs-objection';
</script>

README

NestJS Objection

NPM Version NPM Downloads Package License

Table of Contents

Description

Integrates Objection.js and Knex with Nest

Installation

$ npm install nestjs-objection knex objection

You can also use the interactive CLI

npx nestjs-modules

Features

  • Decorators @InjectModel, @Table, @Column, @Relation
  • Synchronization synchronize(model, force?)
  • SoftDelete @Table({ softDelete: true })

Table options

Name Type Required Default
tableName string true className
softDelete boolean / string false false

Column options

Name Type Required Default
type columnTypes true ---
default any false ---
columnName string false false
nullable boolean false false
notNullable boolean false false
unique boolean false false
unsigned boolean false false
primary boolean false false

synchronize(model, force?)

Name Type Required Default
model Model true ---
force boolean false false

softDelete

Method Type Options Return
delete function --- QueryBuilder
softDelete function --- QueryBuilder
forceDelete function --- QueryBuilder
withDeleted function --- QueryBuilder
onlyDeleted function --- QueryBuilder
restore function --- QueryBuilder

columnTypes

--- --- --- ---
increments bigIncrements integer bigInteger
text string float decimal
boolean date datetime time
timestamp timestamps binary json
jsonb uuid

Examples

$ npm install nestjs-objection knex objection sqlite3

Models

// app.models.ts
import { 
  Model, Column, Relation, Table, relationTypes, columnTypes, 
} from 'nestjs-objection';

@Table({ tableName: 'posts' })
export class Post extends Model {
  @Column({ type: columnTypes.increments })
  id: number;
  @Column({ type: columnTypes.integer })
  userId: number;
  @Column({ type: columnTypes.string })
  title: string;
  @Column({ type: columnTypes.json })
  json: object;
}

@Table({ tableName: 'users' })
export class User extends Model {
  @Column({ type: columnTypes.increments })
  id: number;
  @Column({ type: columnTypes.string })
  name: string;
  @Relation({ 
    modelClass: Post, 
    relation: relationTypes.HasManyRelation, 
    join: { from: 'users.id', to: 'posts.userId' } 
  })
  posts: Post[];
}

ObjectionModule.forRoot(options, connection?)

// app.module.ts
import { Module } from '@nestjs/common';
import { ObjectionModule, Model } from 'nestjs-objection'
import { AppController } from './app.controller';
import { User, Post } from './app.models';

@Module({
  imports: [
    ObjectionModule.forRoot({
      Model,
      config: {
        client: "sqlite3",
        useNullAsDefault: true,
        connection: ':memory:',
      }
    }),
    ObjectionModule.forFeature([User, Post]),
  ],
  controllers: [AppController],
})
export class AppModule {}

ObjectionModule.forRootAsync(options, connection?)

// app.module.ts
import { Module } from '@nestjs/common';
import { ObjectionModule, Model } from 'nestjs-objection'
import { AppController } from './app.controller';
import { User, Post } from './app.models';

@Module({
  imports: [
    ObjectionModule.forRootAsync({
      useFactory: () => ({
        Model,
        config: {
          client: "sqlite3",
          useNullAsDefault: true,
          connection: ':memory:',
        },
      }),
    }),
    ObjectionModule.forFeature([User, Post]),
  ],
  controllers: [AppController],
})
export class AppModule {}

InjectModel(Model, connection?)

// app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { 
  InjectModel, 
  synchronize, 
  InjectConnection, 
  Connection, 
} from 'nestjs-objection';
import { User, Post } from './app.models';

@Controller()
export class AppController {
  constructor(
    @InjectModel(User) private readonly userModel: typeof User,
    @InjectModel(Post) private readonly postModel: typeof Post,
    @InjectConnection() private readonly connection: Connection,
  ) {}

  @Get()
  async getHello() {
    await synchronize(User);
    await synchronize(Post);
    await this.userModel.query().insert({ name: 'Name' });
    await this.postModel.query().insert({ title: 'Title', userId: 1 });

    const users = await this.userModel
      .query()
      .select(['users.name'])
      .withGraphJoined('posts')
      .modifyGraph('posts', q => q.select(['posts.title']));

    const posts = await this.connection.table('posts');

    return { users, posts };
  }
}

SoftDeleteModel

import { SoftDeleteModel, columnTypes } from 'nestjs-objection';

@Table({ tableName: 'users', softDelete: true })
export class User extends SoftDeleteModel {
  @Column({ type: columnTypes.increments })
  id: number;
  @Column({ type: columnTypes.datetime })
  deletedAt: Date;
}
ObjectionModule.forRoot({
  Model: SoftDeleteModel,
  config: { /* ... */ }
})
this.userModel.query().where({ id: 1 }).delete(); // or softDelete()
this.userModel.query().where({ id: 1 }).withDeleted();
this.userModel.query().where({ id: 1 }).onlyDeleted();
this.userModel.query().where({ id: 1 }).forceDelete();
this.userModel.query().where({ id: 1 }).restore();

Typescript

// src/index.d.ts
declare module 'objection' {
  interface WhereMethod<QB extends AnyQueryBuilder> {
    <T>(columns: Partial<T>): QB;
    <T>(column: Partial<keyof T>, op: string, value: any): QB;
  }
  interface OrderByMethod<QB extends AnyQueryBuilder> {
    <T>(column: keyof T, order?: 'asc' | 'desc'): QB;
    <T>(columns: (Array<{ column: keyof T; order?: 'asc' | 'desc' }>)): QB;
  }
  interface SelectMethod<QB extends AnyQueryBuilder> {
    <T>(...columnNames: Array<Partial<keyof T>>): QB;
    <T>(columnNames: Array<Partial<keyof T>>): QB;
  }
  interface QueryBuilder<M extends Model, R = M[]> extends Promise<R> {
    forceDelete(): this;
    withDeleted(): this;
    onlyDeleted(): this;
    softDelete(): this;
    restore(): this;
  }
}
// with type-safe
const users = await this.userModel
  .query()
  .select<User>(['name'])
  .where<User>({ name: 'Name' })
  .orderBy<User>('name', 'desc')
  .withGraphFetched('posts')
  .modifyGraph('posts', q => q.select<Post>(['title']));

// without type-safe
const users = await this.userModel
  .query()
  .select(['name'])
  .where({ name: 'Name' })
  .orderBy('name', 'desc')
  .withGraphFetched('posts')
  .modifyGraph('posts', q => q.select(['title']));

License

MIT