How to use NestJS configurations outside managed class - nestjs

I'm trying to create some custom exceptions for my application. This mostly means i'm extending the HttpException class which is pretty simple.
However, as part of the exception message, I want to pass some of the configurations for the application.
The problem is that Exceptions are not part of the module or service. They're not managed classes, so I cannot use the ConfigService as described by the NestJS documentation.
I could use the process.env.<my_config> approach, but it seems dirty to have to use that when I'm using ConfigService everywhere in my services, specially when i'm also using .env files to load some other variables. My last alternative would be to use dotenv directly in the configuration. However all of them suffer from the same: I could be missing some important data updates/added during the app bootstrapping portion.
How can I access app level configurations from outside the managed classes?
Sample of what i'm trying to do:
import { HttpException, HttpStatus } from '#nestjs/common';
export class MyCustomException extends HttpException {
constructor(message) {
const serviceName = // Get the configuration value
const configA = // Get other configuration value
const payload = {
serviceName,
configA,
message,
}
super(payload, HttpStatus.BAD_REQUEST)
}
}

You can create an exception filter to catch your custom exception or the built-in BadRequestException, then inject ConfigService into the filter when registering the filter in main.ts.
create custom exception filter to catch your custom exception.
my-custom-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpStatus} from '#nestjs/common';
import { Response } from 'express';
import { ConfigService } from '#nestjs/config';
import { MyCustomException } from './my-custom.exception'; // replace this to your exception file path
#Catch(MyCustomException)
export class MyCustomExceptionFilter implements ExceptionFilter {
constructor(private readonly _configService: ConfigService) {}
catch(exception: MyCustomException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
// get configuration from .env
const serviceName = this._configService.get<string>(`SERVICE_NAME`); // replace to your key in .env
const configA = this._configService.get<string>(`CONFIG_A`); // replace to your key in .env
response
.status(HttpStatus.BAD_REQUEST)
.json({
serviceName,
configA,
message: exception.message
});
}
}
Pass configService to your custom exception filter when initializing app.
main.ts
...
const app = await NestFactory.create(AppModule);
// get configService instance, pass into filter
const configService = app.get<ConfigService>(ConfigService);
app.useGlobalFilters(new MyCustomExceptionFilter (configService));
...

Related

Use TypeORM repository or method on a service from outside any module

I have a nestjs app that has an AuthService which has these parts:
export class AuthService {
constructor(
#InjectRepository(User)
private readonly userRepo: Repository<User>,
) {}
async updateFromInternally () {
...
}
I have another file which is, crucially, outside of any module, which contains a number of helpful functions relating to Google oauth. For example, this file initiates Google's oauth2 client like so:
export const oauth2Client = new google.auth.OAuth2(
process.env.GOOGLE_CLIENT_ID,
process.env.GOOGLE_CLIENT_SECRET,
process.env.GOOGLE_CLIENT_REDIRECT
);
This file also has a listener function which I found in Google's documentation as a way to catch when my use of Google's oauth2 client automatically uses a refresh token to obtain a new access token:
oauth2Client.on('tokens', async (tokens) => {
[****]
})
At [****], I need to query in my database for a particular user and update them. Either of these conceptually work:
I somehow get userRepo into this file right here and use it to query + update
I somehow call updateFromInternally in AuthService from here
But I don't know how to interact with either TypeORM repositories or methods within services from outside of any module in nestjs! Can I do either of these?
The first question is why you're using nestjs?
You can access the internals of nestjs by using the instantiated app;
In your main.ts file you have something like this:
const app = await NestFactory.create(AppModule);
You can access the services or DataSource from the app by using the get method.
import {DataSource} from 'typeorm'
const dataSource = app.get<DataSource>(DataSource)
// or custom service
const someServices = app.get<SomeService>(SomeService)
you just need a way to export app from main.ts and import it in your outside world.
for example:
// main.ts
let app;
async bootstrap() {
app = await NestFactory.create(AppModule);
}
export const getApp = () => app;
bootstrap()
and in your other file
// outside.ts
import {getApp} from './main.ts'
const app = getApp()
I didn't write the whole logic, but I think it would give you an idea of what you need to do.
But in my opinion, it's the worst thing you can do. You're using 'nestjs` and try to respect its philosophy.
Just write a module that handles all the work you want.
I figured out #1:
import {getRepository} from "typeorm";
oauth2Client.on('tokens', async (tokens) => {
const gaRepo = getRepository(Googleauth);
// Can now use gaRepo like you do in a service
})

How to reference the app instance in a module in Nest.js

I'm working on a project that's using multiple Nest repos, around 4.
Every repo needs to implementing logging to log things like
Server lifecycle events
Uncaught errors
HTTP requests/responses
Ideally, I'd like to package everything up into a module which I can publish to my company's NPM organization and just consume directly in each of my projects.
That way, it would take very minimal code to get logging set up in each project.
One of the things I'd like to log in my server lifecycle event is the server's url.
I know you can get this via app.getUrl() in the bootstrapping phase, but it would be great to have access to the app instance in a module's lifecycle hook like so.
#Module({})
export class LoggingModule implements NestModule {
onApplicationBootstrap() {
console.log(`Server started on ${app.getUrl()}`)
}
beforeApplicationShutdown() {
console.log('shutting down')
}
onApplicationShutdown() {
console.log('successfully shut down')
}
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggingMiddleware).forRoutes('*')
}
}
Is this possible?
There's no way (besides hacky ones, maybe) to access the app itself inside modules.
As you can see here, app.getUrl() uses the underlying HTTP server. Thus I guess you can retrieve the same data using the provider HttpAdapterHost.
Ï thought I'd chime in and offer one of the hacky solutions. Only use this, if there is absolutely no other way or your deadline is coming in an hour.
Create a class that can hold the application instance
export class AppHost {
app: INesApplication
}
And a module to host it
#Module({
providers: [AppHost]
exports: [AppHost]
})
export class AppHostModule {}
In your bootstrap() function, retrieve the AppHost instance and assign the app itself
// after NestFactory.create() ...
app.select(AppHostModule).get(AppHost).app = app;
Now, the actual application will be available wherever you inject AppHost.
Be aware, though, that the app will not be available inside AppHost before the whole application bootstraps (in onModuleInit, onApplicationBootstrap hooks or in provider factories), but it should be available in shutdown hooks.
Not sure is that hacky... I'm using this to prevent the server from starting in case of pending migrations.
// AppModule.ts
export class AppModule implements NestModule {
app: INestApplication;
async configure(consumer: MiddlewareConsumer) {
if (await this.hasPendingMigrations()) {
setTimeout(()=> {
this.logger.error("There are pending migrations!")
process.exitCode = 1;
this.app.close();
}, 1000);
}
//...
}
public setApp(app: INestApplication) {
this.app = app;
}
//...
}
//main.ts
const app = await NestFactory.create(AppModule, {
logger: config.cfgServer.logger,
});
app.get(AppModule).setApp(app);

How to get FullURL with NestJS?

How do I get the full URL of the page that NestJS is processing?
(e.g. http://localhost:3000/hoge)
//
// If you implement it with express, it looks like this.
// e.g. http://localhost:3000/hoge
//
function getFullUrl(req: express.Request) {
return `${req.protocol}://${req.get('Host')}${req.originalUrl}`;
}
You can inject the request-object using the Req() decorator allowing you to do pretty much the same thing you did in your pure express-app.
import {Controller, Get, Req} from '#nestjs/common';
import {Request} from 'express';
#Controller()
export class AppController {
#Get()
getHello(#Req() req: Request): void {
console.log(`${req.protocol}://${req.get('Host')}${req.originalUrl}`);
}
}
This of course assumes that you're using Express as your http-adapter (which is the default).

Access model method inside express route (Loopback 4)

I will show you an example of what i'm trying to do :
server.ts
export class ExpressServer {
public readonly app: express.Application;
public readonly lbApp: ImportedApp;
private server?: Server;
constructor(options: ApplicationConfig = {}) {
this.app = express();
this.lbApp = new ImportedApp(options);
this.app.get('/hello', async function (_req: Request, res: Response) {
//Here i'd like to call a model like User.findById() but can't figure out how to do it..
});
}
}
As you see in the comment i'm trying to access my models method to use them in my route (Like showing users informations on my view) But can't figure out how to do it. I'v already tryed to import the DataSource, the model, the controller but nothing's containing my methods (FindById, Create etc..)
If i find nothing i will have to use something like Axios or Request to request the ressource from the api instead of inside my code like await request('api/users/myusername)
In LoopBack 4, we use Repository design patter for accessing data. In order to find a user instance by its id, you need to obtain an instance of UserRepository via dependency injection. Quoting from https://loopback.io/doc/en/lb4/Repository.html:
Repositories are adding behavior to Models. Models describe the shape of data, Repositories provide behavior like CRUD operations. This is different from LoopBack 3.x where models implement behavior too.
UPDATED SOLUTION
To obtain an instance of a Repository class, you can use the Service Locator design pattern and get the instance from the per-request Context object provided by LoopBack's REST layer.
import {MIDDLEWARE_CONTEXT, RequestContext} from '#loopback/rest';
import {UserRepository} from '../repositories';
function expressHandler(req, res, next) {
const ctx = (req as any)[MIDDLEWARE_CONTEXT];
const userRepo = await ctx.get<UserRepository>('repositories.UserRepository');
const users = await userRepo.find({limit: 10});
// render your view
}
We are discussing how to make this use case easier to implement in GitHub pull request loopback-next#6793, feel free to join the discussion there.
ORIGINAL ANSWER
Instead of writing an Express route for your rendered pages, I recommend you to write a LoopBack 4 Controller instead; and inject Express Response object to allow you to render the HTML view, as explained in https://loopback.io/doc/en/lb4/Accessing-http-request-response.html#inject-http-response
import {Response, RestBindings, oas} from '#loopback/rest';
import {inject} from '#loopback/core';
import {UserRepository} from '../repositories';
export class PingController {
constructor(
#inject(RestBindings.Http.RESPONSE)
private response: Response
#repository(UserRepository)
public userRepository: UserRepository,
) {}
// Hide this endpoint from OpenAPI spec generated for the app
#oas.visibility('undocumented')
#get('/users')
list(): Response {
// Access User data via this.userRepository API
const users = await this.userRepository.find({limit: 10});
// Access the response object via `this.response`
this.response.render('users', {users});
// Return the HTTP response object so that LoopBack framework skips the
// generation of HTTP response
return this.response;
}
}
Having said that, if you already know how to access DataSource instances from your LB4 app in your Express routes, then you can instantiate Repository classes manually from your routes too:
const db = // your datasource
this.app.get('/hello', async function (_req: Request, res: Response) {
const repo = new UserRepository(db);
const users = await this.userRepository.find({limit: 10});
});
To me the solution is not working. Started from the express-composition example, i just need to access lb repositories from a generic express route outside of the lb4 request handler:
constructor(options: ApplicationConfig = {}) {
this.app = express();
this.lbApp = new NoteApplication(options);
this.lbApp.basePath('')
// Expose the front-end assets via Express, not as LB4 route
this.app.use('/api', this.lbApp.requestHandler);
this.app.get('/hello', async (req: Request, res: Response) => {
const ctx = (req as any)[MIDDLEWARE_CONTEXT];
const userRepo = await ctx.get('repositories.UserRepository');
res.send('Hello world!');
});
}
the ctx in the line
const ctx = (req as any)[MIDDLEWARE_CONTEXT];
is always undefined.
My main goal is to have routes not under /api that can still access lb4 repositories.

How to split Nest.js microservices into separate projects?

Let's say I want to create a simplistic cinema-management platform. It needs few microservices: movies, cinemas, payments, etc.
How would you go about doing it in Nest.js? I don't want them in the same big folder as that feels like making a monolith. I want them to be separate Nest.js projects with their own git repositories so I can orchestrate them with Kubernetes later on.
How? How to connect from service cinemas to service movies if they are two separate projects and only share, let's say, Redis?
Edit:
This is not a question about microservices in general. This is a question Nest.js specific. I read the documentation, I know there are decorators like #Client for connecting to the transport layer. I just want to know where to use that decorator and maybe see a short snippet of code on "having two separate Nest.js repositories how to connect them together so they can talk to each other".
I don't care about the transport layer, that thing I can figure out myself. I just need some advice on the framework itself as I believe the documentation is lacking.
I got it working. Basically the way to do it is to create two separate projects. Let's say - one is a createMicroservice and another is just an HTTP app (but could easily be another microservice). I used a "normal" app just so I can call it easily for testing.
Here is the main.ts file that creates microservice.
import { NestFactory } from '#nestjs/core';
import { AppModule } from './app.module';
import { Transport } from '#nestjs/common/enums/transport.enum';
async function bootstrap() {
const app = await NestFactory.createMicroservice(AppModule, {
transport: Transport.REDIS,
options: {
url: 'redis://localhost:6379',
},
});
await app.listen(() => console.log('MoviesService is running.'));
}
bootstrap();
And one of the controllers:
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#MessagePattern({ cmd: 'LIST_MOVIES' })
listMovies(): string[] {
return ['Pulp Fiction', 'Blade Runner', 'Hatred'];
}
}
Now - in the microservice you declare to what kinds of events should controllers react to (#MessagePattern). While in the "normal" service you do this in the controller when you want to ask other microservices for something (the main.ts is the simplest example that you get when you create a new project using #nestjs/cli.
The controller code:
#Controller()
export class AppController {
private readonly client: ClientProxy;
constructor(private readonly appService: AppService) {
this.client = ClientProxyFactory.create({
transport: Transport.REDIS,
options: {
url: 'redis://localhost:6379',
},
});
}
#Get()
listMovies() {
const pattern = { cmd: 'LIST_MOVIES' };
return this.client.send<string[]>(pattern, []);
}
}
So as long a client is connected to the same transport layer as the microservice - they can talk to each other by using the #MessagePattern.
For nicer code you can move the this.client part from a constructor to a provider and then use dependency injection by declaring the provider in the module.

Resources