willsoto / nestjs-objection

NestJS module for Objection
Apache License 2.0
137 stars 8 forks source link

Model binding #1272

Closed jaesam closed 3 years ago

jaesam commented 3 years ago

Hi, I cannot wrap my head around how the model binding is done with the package.

I am getting this error:

ERROR [ExceptionsHandler] no database connection available for a query. You need to bind the model class or the query to a knex instance.

This is my database module:

import { ObjectionModule } from '@willsoto/nestjs-objection';
import { Global, Module } from '@nestjs/common';
import { knexSnakeCaseMappers } from 'objection';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { BaseModel, User } from '../models';

@Global()
@Module({
  imports: [
    ObjectionModule.registerAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory(configService: ConfigService) {
        return {
          Model: BaseModel,
          config: {
            client: 'pg',
            connection: {
              host: configService.get('DATABASE_HOST'),
              user: configService.get('DATABASE_USER'),
              password: configService.get('DATABASE_PASSWORD'),
              database: configService.get('DATABASE_NAME'),
              port: configService.get('DATABASE_PORT'),
            },
            ...knexSnakeCaseMappers(),
          },
        };
      },
    }),
    ObjectionModule.forFeature([User]),
  ],
  exports: [ObjectionModule],
})
export class DatabaseModule {}

And this is my App module:

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UsersModule } from './modules/users/users.module';
import { validateEnv } from './config/env-validation';
import { DatabaseModule } from './database/database.module';
@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      cache: true,
      validate: validateEnv,
    }),
    DatabaseModule,
    UsersModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

And here is my user service:

import { Inject, Injectable } from '@nestjs/common';
import { from, Observable } from 'rxjs';
import { User } from '../../models';

@Injectable()
export class UsersService {
  constructor(@Inject(User) private readonly userModel: typeof User) {}

  create(user: User): Observable<User> {
    return from(this.userModel.query().insertAndFetch(user));
  }

  findOne() {}

  findAll(): Observable<User[]> {
    return null;
  }

  update() {}

  remove() {}
}

I have read the docs over and over but couldn't figure out how to bind the model properly. I have tested my code with KNEX_CONNECTION and it worked just fine so I know there is an issue with model binding. So could you please share not only the snippets but the example repo you got the code snippets from? It will definitely help users understanding how to use the package much better.

willsoto commented 3 years ago

Where is UserService provided? Is that actually registered somewhere? I assume the UsersModule but can you paste that module definition?

willsoto commented 3 years ago

Second question: do you have User extends BaseModel somewhere?

jaesam commented 3 years ago

yup the user module is pretty much directly from the starter:

import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';

@Module({
  exports: [UsersService],
  controllers: [UsersController],
  providers: [UsersService],
})
export class UsersModule {}

As for the second question, no I thought putting the custom base model to the option "Model" would do the similar job. Do I need to change my user model to extend the base model? what does the option "Model" actually mean?

willsoto commented 3 years ago

Ok then that is your issue. Yes, you need to extend your base model. The Nest integration can't do that cleanly because I would have to be binding to something every-time you registered a model - which raises a question, with multiple connections how would I know what to connect to? It would just increase the API surface area with no real gain since extending from different bases solves that problem already.

The option for model will do the work of actually binding Knex to Objection since that has to be done during the nest initialization process. I essentially coordinate all that work for you so you don't have to.

Under the hood, it's all Objection. Objection knows what connection to use and how to query based on the superclass so you need to follow normal Objection rules for that.

jaesam commented 3 years ago

The option for model will do the work of actually binding Knex to Objection since that has to be done during the nest initialization process. I essentially coordinate all that work for you so you don't have to.

Got it! and got my app to work. Thanks for the help and the package!

willsoto commented 3 years ago

Thanks! Glad I could help.