#How can i access my NestApplication in a controller
197 messages · Page 1 of 1 (latest)
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { ApplicationOptions } from "./types";
import { NestFastifyApplication, FastifyAdapter } from "@nestjs/platform-fastify";
import { Database } from "A Custom package built on top of sequelize";
export class Application {
server: NestFastifyApplication;
constructor(options: ApplicationOptions) {
this._init();
}
async _init() {
const server = await NestFactory.create<NestFastifyApplication>(
Module,
new FastifyAdapter()
);
this.server = server;
// Custom property that can be accessed by controllers
this.server.db = new Database(
}
start() {
this.server.listen(880, () =>
console.log("server started on port 880")
);
}
}
You can’t
How would i then add constructor arguments to a service
using custom providers? I'm not sure if I follow
any examples?
the docs covers custom providers very well
what are you trying to achieve here? I didn't get
Hi! i had one more query before we close this thread
I have a configurable module
import { Module } from '@nestjs/common';
// import { AppController } from './app.controller';
// import { AppService } from './app.service';
import { DatabaseModule } from './modules/database/database.module';
@Module({
imports: [],
controllers: [],
providers: [DatabaseModule.register({
dialect: 'sqlite',
storage: './blogifi.db',
})],
})
export class BlogifiServerModule {}
I want to pass the options given to BlogifiServerModule during NestFactory.create to DatabaseModule
Is that posible
Or i register it directly from NestApplication
@tropic marlinyou there?
yep
ok wait, it doesnt allow me to set variables
you cannot pass options to modules
I mean, you can supply them via custom providers
But its depends on where you want to access those options
just learn about custom providers and async providers
That's all you have to know to see if what you trying to do is feasible or not
Custom providers don't work with my use case
yes
this.server.get(DatabaseModule.register)
I think this will work but i get a types error
I don't think that that's feasible
might be in a hacky way
maybe
as we can't define custom providers from main.ts
so.. what am i left with?
import { Global, Module } from '@nestjs/common';
import { ConfigurableModuleClass } from './database.definition';
import { DatabaseService } from './database.service';
@Global()
@Module({
providers: [DatabaseService],
exports: [DatabaseService],
})
export class DatabaseModule extends ConfigurableModuleClass {}
import { ConfigurableModuleBuilder } from '@nestjs/common';
import { Options } from '@sequelize/core';
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
new ConfigurableModuleBuilder<Options>({
moduleName: 'Database',
})
.build();
import { BlogifiDatabase } from '@blogifi/database';
import { Inject, Injectable } from '@nestjs/common';
import { Options } from '@sequelize/core';
import { MODULE_OPTIONS_TOKEN } from './database.definition';
@Injectable()
export class DatabaseService {
database: BlogifiDatabase;
constructor(
@Inject(MODULE_OPTIONS_TOKEN)
private readonly options: Options,
) {
this.database = new BlogifiDatabase(options as any);
}
}
This is my Database Module which requires those options
@tropic marlin Would you prefer to join my live share session for seeing the whole codebase?
no problem
I work remotely and we use discord, that's why I need to have the status 'online'
I'll see if I follow what you're trying to do later
this.server.get(DatabaseModule).register(this.options.database);
This is what i was doing, but it does not seem to work
show us the whole context around that line
Question: why did a dynamic missing not satisfy your use case? What's the need to set values from the main?
because i am basically using nestjs for making a package and i need to handle the database provided by user
I'm not really sure I follow. I mean, you could make your AppModule take in options via a static method, just like a dynamic module, and pass them on to the database module
how would i pass them on to the database module
I'll need to be back at my computer to give an example, but basically it would be a dynamic module passing input data to another dynamic module
i can't understand ;-;
when you get back to the computer just provide me an example
turn that AppModule into a dynamic module, first
Just a simple static register(options) that returns the new module object. No need for an async variant
import { Module } from '@nestjs/common';
// import { AppController } from './app.controller';
// import { AppService } from './app.service';
import { DatabaseModule } from './modules/database/database.module';
import { ConfigModule } from '@nestjs/config';
import config from './config';
import { ConfigurableModuleClass } from './app.definition';
import { AuthModule } from './modules/auth/auth.module';
@Module({
imports: [ConfigModule.forRoot({
isGlobal: true,
load: [config]
}), AuthModule],
controllers: [],
providers: [DatabaseModule],
})
export class BlogifiServerModule extends ConfigurableModuleClass {}
import { ConfigurableModuleBuilder } from '@nestjs/common';
import { Options } from '@sequelize/core';
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
new ConfigurableModuleBuilder<Options>({
moduleName: 'BlogifiApp',
})
.build();
Done
Why is DatabaseModule in providers? It's a module isn't it?
oh yeah didnt notice
how am i gonna initialize the DatabaseModule via the config provided?
What? I thought you wanted to pass config from main?
DatabaseModule is also a dynamic module, right?
yeah
So you pass the config to AppModule in main and then AppModule passes the config to DatabaseModule
It's just nested static method calls
how is AppModule gonna pass the config to DatabaseModule
Via DatabaseModule's register method
where am i gonna call the register method while having access to the config?
When you import DatabaseModule I'm AppModule
Do you get how module imports and dynamic modules work here?
import { Inject, Module } from '@nestjs/common';
// import { AppController } from './app.controller';
// import { AppService } from './app.service';
import { DatabaseModule } from './modules/database/database.module';
import { ConfigModule } from '@nestjs/config';
import config from './config';
import { ConfigurableModuleClass } from './app.definition';
import { AuthModule } from './modules/auth/auth.module';
import { MODULE_OPTIONS_TOKEN } from './app.definition';
import { BlogifiServerOptions } from './types';
@Module({
imports: [ConfigModule.forRoot({
isGlobal: true,
load: [config]
}), AuthModule, DatabaseModule],
controllers: [],
providers: [],
})
export class BlogifiServerModule extends ConfigurableModuleClass {
constructor(
@Inject(MODULE_OPTIONS_TOKEN)
private readonly options: BlogifiServerOptions,
) {
super();
DatabaseModule.register(options.database);
}
}
Like this?
BlogifiServerModule is the AppModule
you need to learn a bit on dynamic modules and modules in general
just calling DatabaseModule.register doesn't mean that it got registered
the return of DatabaseModule.register() is a dynamic module
Like any nestjs module, it must be in the imports array somewhere
It is
do i need call register in imports?
you need to put the output of .register() in the imports
that's how you import nestjs modules
i didnt get that
did you read the docs?
docs dont mention anything on this
it won't mention every thing you could build, of course
just the building blocks
see, the output of .register() is in the imports array
but what should i keep the output to be?
I meant the return of .register() method
it should be in the imports array otherwise your dynamic module won't be imported, you know
that's why this won't work
but with what values should i call the method
right
show me your root module's code and I'll give you a code example on what you could
import { Inject, Module } from '@nestjs/common';
// import { AppController } from './app.controller';
// import { AppService } from './app.service';
import { DatabaseModule } from './modules/database/database.module';
import { ConfigModule, ConfigService } from '@nestjs/config';
import config from './config';
import { AuthModule } from './modules/auth/auth.module';
import { AppConfigOptions, BlogifiServerOptions } from './types';
@Module({
imports: [ConfigModule.forRoot({
isGlobal: true,
load: [config]
}), AuthModule, DatabaseModule.register({
})],
controllers: [],
providers: [],
})
export class BlogifiServerModule {
constructor(private readonly configService: ConfigService) {
DatabaseModule.register({
...this.configService.get('database')
})
}
}
This is my updated code
is this the root module?
wait, you only need to use ConfigService in order to create that DatabaseModule?
or do you need some value from main.ts as well?
only the ConfigService
i removed the main.ts thing
than it's easier
Also, If i am using the Database module in another module i wont have to register it in imports right?
you need to register it in some module
but only once
yeah i am doing it in the main app module
show us your DatabaseModule
this
this is the DatabaseModule
right so you'll need to use the async version of it
in order to initialize its providers with data coming from ConfigService
so instead of imports: [DatabaseModule] you'll have
imports: [DatabaseModule.registerAsync({ ... })]
again, just follow the docs
in there you'll have inject: [ConfigService]
and then
// ...
useFactory: (configService: ConfigService) => configService.get('database')
// ...
pretty easy
after this is there any need for ```ts
export class BlogifiServerModule {
constructor(private readonly configService: ConfigService) {
DatabaseModule.register({
...this.configService.get('database')
})
}
}
nope
aight
like I said many times, you need to have the return of .register() in some imports array otherwise it's useless
instead of 'register' you could see it as 'createDynamicModule'
[Nest] 6021 - 20/10/2022, 22:39:21 ERROR [ExceptionHandler] Cannot read properties of undefined (reading 'get')
TypeError: Cannot read properties of undefined (reading 'get')
at InstanceWrapper.useFactory [as metatype] (/Users/gaurish/Desktop/Coding/blogifi/src/file:/Users/gaurish/Desktop/Coding/blogifi/src/app.module.ts:16:65)
show us how your root module looks like rn
import { Inject, Module } from '@nestjs/common';
// import { AppController } from './app.controller';
// import { AppService } from './app.service';
import { DatabaseModule } from './modules/database/database.module';
import { ConfigModule, ConfigService } from '@nestjs/config';
import config from './config';
import { AuthModule } from './modules/auth/auth.module';
import { AppConfigOptions, BlogifiServerOptions } from './types';
import { AuthController } from './controllers/auth/auth.controller';
@Module({
imports: [ConfigModule.forRoot({
isGlobal: true,
load: [config]
}), AuthModule, DatabaseModule.registerAsync({
useFactory: (configService: ConfigService) => configService.get('database')
})],
controllers: [AuthController],
providers: [],
})
export class BlogifiServerModule {}
that errors is due to configService being undefined, somehow
you missed the inject
inject?
DatabaseModule.registerAsync({
inject: [ConfigService],
useFactory: (configService: ConfigService) => configService.get('database')
})
yep
otherwise nestjs won't know that it should inject something in there useFactory
isn't magical
haha
[Nest] 6041 - 20/10/2022, 22:41:31 ERROR [ExceptionHandler] Nest can't resolve dependencies of the DatabaseService (?). Please make sure that the argument DATABASE_MODULE_OPTIONS at index [0] is available in the DatabaseModule context.
bruh moment
wait
do i really need DatabaseModule to be a dynamic module
since i can just use the configService
if you want supply data coming from some provider in it, it must be async and dynamic
you can't inject providers in modules
not like we have for @Injectable()
DatabaseModule has DatabaseService
import { Global, Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ConfigurableModuleClass } from './database.definition';
import { DatabaseService } from './database.service';
@Global()
@Module({
imports: [ConfigService],
providers: [DatabaseService],
exports: [DatabaseService],
})
export class DatabaseModule {}
Okay, let's take a step backand get this straightend out
import { BlogifiDatabase } from '@blogifi/database';
import { Inject, Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Options } from '@sequelize/core';
import { MODULE_OPTIONS_TOKEN } from './database.definition';
@Injectable()
export class DatabaseService {
database: BlogifiDatabase;
constructor(
private configService: ConfigService
) {
const options = this.configService.get<Options>('database');
this.database = new BlogifiDatabase(options);
}
}
that will work but looks odd from nestjs point of view
You just need a way to pass database credentials to your database service, right? No more passing values from main.ts or elsewhere in the app, you're just gonna let the ConfigService be used and read from the .env file, correct?
Yes
What is BlogifiDatabase and how is it different than the DatabaseService? I see you instantiating it in the DatabaseService, but I generally try to stay away from new inside of constructors if I can help it. Would it be better/preferable to just pass an instance to the DatabaseService?
Well its the database wrapper
this does most of my work
My question still stands, would it be better/preferable to pass the instance of BlogifiDatabase rather than instantiating it in the DatabaseService in your opinion
Or better yet, what's the reason for the DatabaseService? What does it do for you?
yes, It would be better to pass the instance of BlogifiDatabase but the thing is i wanted to be accessible in every controller/module
and that's the main reason i created DatabaseService
Okay, so why have this wrapper in the first place? Why not just have a custom provider that links an injection token to the instance?
{
provide: 'BlogIfiDatabase',
inject: [ConfigService],
useFactory: (config: ConfigService) => {
return new BlogifiDatabase(config.get('database');
}
}
Now it's injectable with @Inject('BlogifiDatabase') private readonly database: BlogifiDatabase
Add the above to the providers array of DatabaseModule and add exports: ['BlogifiDatabase'] and then just add imports: [DatabaseModule] top modules where you need the database
Bada-bing bada-boom it's all hooked up and gets values from the ConfigService, nothing is hard coded and everything just works™️
Where am i gonna do @Inject('BlogifiDatabase') private readonly database: BlogifiDatabase
In any service that you need to use the database
and for controllers?
Do you use the database in your controllers?
yes
Then do the same thing. It's just a way to set metadata for Nest to read
Though, I would suggest not having business logic in the controllers, but that's my opinion
For services?
import { Blog } from '@blogifi/database/lib/types';
import { Injectable } from '@nestjs/common';
import { DatabaseService } from 'src/modules/database/database.service';
@Injectable()
export class APIService {
constructor(private readonly databaseService: DatabaseService) { }
}
I have this service
that utilizes the DatabaseService
Right, and I showed a way that didn't even need the DatabaseService, it just uses the BlogifiDatabase directly via a custom provider
Was that not clear?
Have you tried reading the docs?
https://docs.nestjs.com/fundamentals/custom-providers#non-class-based-provider-tokens
import { BlogifiDatabase } from '@blogifi/database';
import { Blog } from '@blogifi/database/lib/types';
import { Inject, Injectable } from '@nestjs/common';
import { DatabaseService } from 'src/modules/database/database.service';
@Injectable()
export class APIService {
private readonly database: BlogifiDatabase;
constructor(
@Inject('BlogifiDatabase') database: BlogifiDatabase
) {
this.database = database;
}
}
So this should work right?
You don't need the this.database = database, just use @Inject('BlogifiDatabase') private readonly database: BlogifiDatabase)
The private readonly is a shorthand that TS compiles to the this.database = database for you, less code for you to worry about
constructor(
@Inject('BlogifiDatabase') private readonly database: BlogifiDatabase
) { }
perfect right
Now you can access this.database inside the class
and i am gonna do the same for controllers?
Why wouldn't you?
in their constructor
Why would you do something different for controllers?
@solar raptor yo sorry for the ping i'm facing some weird problem this time
import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
import { Controller, Get, Post, Patch, Delete, Body, Res, Inject, Param } from '@nestjs/common';
import { CreateBlogBodyType, CreateBlogRequest, GetAndDeleteBlogRequest, UpdateBlogRequest } from './api.schema';
import { APIService } from './api.service';
import { BlogifiDatabase } from '@blogifi/database';
@Controller({ path: '/api/v1' })
export class APIController {
constructor(
@Inject('BlogifiDatabase') private readonly database: BlogifiDatabase
) { }
@Get('/blogs')
async getAllBlogsHandler() {
const blogs = await this.database.getBlogs()
return blogs;
}
}
I have this simple controller which uses the database, It does reply the request for some reason
i keep getting a null in response body
in db logs the statement is executed
Does the statement actually return data?
So a null response makes sense, right?
yeah
Also
Does the request method matter for @nestjs/passport
import { Controller, Get, Inject, Request, UseGuards } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
import { LocalAuthGuard } from "../../modules/auth/local.guard";
@Controller({ path: "/auth"})
export class AuthController {
@UseGuards(LocalAuthGuard)
@Get("/session")
async loginHandler(@Request() req) {
return req.user
}
}
I have this thing but it does not create the session
Do you have it set up to use sessions?
Yes