NestJs Eventbridge Lambda function - node.js

I have a system writed in using NestJs and serverless framework were each endpoint is a lambda function on aws. One of the functions is not an endpoint, but a trigger from AWS eventbridge. As this function is not an endpoint it cannot be included on a NestJs module since it have to be exported separatelly. My problem is that when the event comes to Eventbridge and triggers the lambda I have to call a NestJs service but I'm not able to do this, since the lambda function is outside NestJs environment. Is that a way for me to call a NestJs service from outside the module?
Here is the serverless framework configs
functions:
function 1(NestJs controller):
handler: src/lambda.handler
events:
- http:
cors: true
method: post
path: entrypoint for function 1
Function 2 (External from NestJs modules):
handler: path to lambda function
events:
- eventBridge:
eventBus: eventbus name
pattern:
source:
- source
Currently I'm using axios to call another NestJs endpoint to just pass the received payload. As you can see on the lambda function file:
import { Context, Handler } from 'aws-lambda'
import axios from 'axios'
export const handler: Handler = async (event: any, context: Context) => {
return await axios
.post(
'lambda function production url',
event.detail
)
.then((data) => {
console.log('data', data)
return data
})
.catch((error) => {
console.log('error', error)
return error
})
}
Here is the controller of lambda function 1
import { Body, Controller, Post } from '#nestjs/common'
import { MyService } from './enrichment.service'
#Controller('function1')
export class EnrichmentController {
constructor(private readonly myService: MyService) {}
#Post('entrypoint')
sendForm(#Body() body) {
return this.myService.start(body)
}
}
and here is the service
import { forwardRef, Inject, Injectable } from '#nestjs/common'
import { EventbridgeService } from '../eventbridge/eventbridge.service'
import { CampaignsService } from '../campaigns/campaigns.service'
import { UploadedDataService } from '../uploaded-data/uploaded-data.service'
#Injectable()
export class MyService {
constructor(
private readonly anotherService: AnotherService,
) {}
async start(body) {
return this.anotherService.updateData(body)
}
}
The question is: Is that a way to call all this NestJs structure from the function file, since it is outside NestJs modules and since the trigger for this function is not an http request but a trigger from Eventbridge? Thank you so much.

You can use a "Standalone" Nest application and pass the event data directly to MyService

You can use NEstJs standalone app, and make your handler like this
export const checkDeletion: Handler = async (event: any, context: Context) => {
async function bootstrap() {
const app = await NestFactory.createApplicationContext(AppModule);
await app
.select(SchedulerModule)
.get(SchedulerService, { strict: true })
.runScheduler();
}
await bootstrap();
};
After that call your handler from serverless.yaml like
functions:
followup-emails:
environment:
STAGE: ${opt:stage}
name: followup-emails-${opt:stage}
handler: src/lambda.checkDeletion
events:
- schedule: rate(1 day)

Related

Nest.js app becomes unresponsive following an Error thrown in an async function without an await

The issue that if there is an exception thrown in an async function but that function was called without an await, the Nest application goes into a state where it no longer responds to requests but the process does not exist.
There may be times where we want to call a function in a request but don't need to wait for it to finish before returning to response to the caller but most of the time that we encounter this it was just an accidental omission of an await. The fact that it renders the server useless has been very problematic.
I suspect there may be a simple solution to this problem but I have been unsuccessful in finding it myself.
app.controller.ts
import { Controller, Get } from '#nestjs/common';
import { AppService } from './app.service';
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#Get()
getHello(): string {
return this.appService.getHello();
}
#Get('/crash')
public async crash(): Promise<void> {
await this.appService.crash();
}
}
app.service.ts
import { Injectable } from '#nestjs/common';
const sleep = async (ms: number): Promise<void> => {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
};
const functionThatThrows = async (): Promise<void> => {
await sleep(2000);
throw new Error('this will crash the app');
};
#Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
public async crash(): Promise<void> {
console.log('crashing the app in 3 seconds...');
await sleep(1000);
functionThatThrows(); // async function called without await
}
}
Calling the endpoint the first time succeeds:
curl http://localhost:3000/crash
Calling it afterward and the server does not respond:
curl http://localhost:3000/crash
curl: (7) Failed to connect to localhost port 3000 after 5 ms: Connection refused
This is due to the fact that node crashes on unhandled promise rejections. Nest's exception filter will catch errors that happen during the request, but if an error happens outside of the request (like a promise/async function without an await), then only the standard Node error handler is active. You'd need a process.on('unhandledRejection', errorHandler) to keep the process from crashing

NestJs include TypeORM repository in middleware

I have the following middleware to log all http requests:
#Injectable()
export class RequestMiddleware implements NestMiddleware {
constructor(
#InjectRepository(Request) private requestsRepository: Repository<Request>,
) {}
private readonly logger = new Logger('HTTP');
use(request: Request, response: Response, next: NextFunction) {
response.on('finish', () => {
const { method, originalUrl } = request;
const { statusCode, statusMessage } = response;
const message = `${method} ${originalUrl} ${statusCode} ${statusMessage}`;
return this.logger.log(message);
});
next();
}
}
My goal is to log all requests to a database. I am using TypeORM so I would like to inject the Request repository and log each request that way. When I do this I receive the following error:
Error: Nest can't resolve dependencies of the class RequestMiddleware
The issue is that this middleware isn't part of a module, it's just a single typescript file so how do I import the TypeORM repo module into a normal typescript file so I can use it?
In the module where RequestMiddleware is defined and used, TypeormModule.forFeature([Request]) needs to be added to the imports array

Nestjs - how to get session in middleware

im trying to add graphql to nestjs app. I use postgraphile for the graphql server.
everything works, but i don't know how to protect the '/graphql' endpoint.
the login is done via session, through the nestjs app.
on the needed endpoints (in the existed nestjs app) there is a Session guard which works fine.
i want to add the same behavior to the graphql endpoint.
this is the graphql code:
export const graphqlBootstrap = async (app: INestApplication) => {
app.use(cors());
// what i want to achieve
app.use('/graphql', (req, res, next) => {
if (!req.session.user) res.send('Authetication Error`);
next();
})
app.use(
postgraphile(...)
and the nestjs code the init it:
const bootstrap = async () => {
const { app, start } = await createApplication(TearupsApplicationModule);
app.useGlobalInterceptors(new ApiResponseInterceptor());
configureHiddenFileUploadEndpoint(app.getHttpAdapter());
await Promise.all(app.get(INITIATION_SEQUENCE).map((fn) => fn()));
await graphqlBootstrap(app); // <--
await start();
app.get(EventsEmitter).init(app);
};
is it possible to use the session in a middleware?
I don't know how to reach the ExecutionContext that exist in the Guard.
I read in the docs that there is also ArgumentsHost which might hold the session, but i also don't know how to get it in simple middleware(app.use(...))
Edit 1
i tried to add a middleware to the app module, with forRoutes call.
but the middleware isn't called with the graphql route /graphql.
also, when using route: / - there is no session on the request.
middleware:
export function Logger(req: any, res: Response, next: NextFunction) {
if (req.session) console.log(`Request...`, req.session.user);
next();
}
App Module:
export class MyApp implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(Logger).forRoutes('/');
}
Edit 2
tried this now, still didn't work:
export class MyApp implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(logger)
.forRoutes({ path: 'api/tearups/graphql', method: RequestMethod.ALL });
}
}
Write a functional middleware as follow signature:
import { Request, Response, NextFunction } from 'express';
export function Logger(req: Request, res: Response, next: NextFunction) {
console.log(`Request...`, req.session.user);
next();
};
Or, a class-based middleware as:
#Injectable()
export class Logger implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log('Request...', req.session.user);
next();
}
}
Now apply the middleware as follows:
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(Logger)
.forRoutes(CatsController);
}
}

Shouldn't NestJS exception filters catch event emitter errors?

I'm running some manual tests on a NestJS application I'm refactoring and just found an odd behaviour from nest's global exception filter.
The developer who created this project used this as a exception filter:
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
} from '#nestjs/common';
import { HttpAdapterHost } from '#nestjs/core';
import { LoggerService } from '../../config/logger.service';
#Catch()
export class GlobalExceptionsFilter implements ExceptionFilter {
constructor(
private readonly httpAdapterHost: HttpAdapterHost,
private readonly logger: LoggerService,
) {}
catch(exception: Error, host: ArgumentsHost): void {
const { httpAdapter } = this.httpAdapterHost;
const ctx = host.switchToHttp();
const httpStatus =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const responseBody = {
statusCode: httpStatus,
path: httpAdapter.getRequestUrl(ctx.getRequest()),
cause: `${exception.name}: ${exception.message}`,
timestamp: new Date().toISOString(),
};
this.logger.error(responseBody?.cause);
httpAdapter.reply(ctx.getResponse(), responseBody, httpStatus);
}
}
Which is almost exactly the same catch-everything filter example in Nest's docs, so it's very generic, indeed.
In a certain part of this project, there's a controller with something like this:
#OnEvent(Events.POTATO_EVENT)
async potatoMethod(payload) {
return await this.service.potatoMethod(payload);
Currently, there's no payload validation on the service's potatoMethod, so the controller receives a payload from another service using EventEmitter2 and forwards it to it's service, which then tries to fetch a record on the database by calling an ORM method.
If payload.potatoNumber is sent correctly, nothing wrong happens, but if I send payload.potatoNumber as undefined, the ORM will throw an error that will not be caught on that global filter.
The same thing doesn't happen if instead of using a #OnEvent() I just use a #Get() directly on the controller to turn it into an endpoint.
Currently, main looks something like this:
(async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
})();
I might be completely wrong, but this should only happen if the application is not using NestFactory.createMicroservice() on it's main.ts file, right? If positive, how exactly does microservices work natively in Nest?

typedi + fastify - initialize service asynchronously

I'm working on a nodejs fastify based app service and using typedi for dependency injection.
Some services I use need async initialization.
MyService.ts
export class MyService {
constructor() {
}
public async init() {
....
}
}
I am trying to initialize the service at application startup so that any service doing Container.get(MyService) gets this initialized instance of MyService
app.ts
export default async function(fastify: FastifyInstance, opts: Options, next: Function) {
// This loads everything under routes
fastify.register(autoload, {
dir: path.join(__dirname, "routes"),
options: opts,
includeTypeScript: true,
});
await Container.get(MyService);
next();
}
server.ts
import app from "./app";
const server = fastify({
logger: logger
});
server.register(oas, docs);
server.register(app);
server.ready(err => {
if (err) throw err;
server.oas();
});
server.listen(config.port, (err) => {
if (err) {
server.log.error(err);
process.exit(1);
}
server.log.info(`server listening on ${server.server.address()}`);
});
export default server;
My attempt to initialize MyService is failing.
MissingProvidedServiceTypeError [ServiceNotFoundError]: Cannot determine a class of the requesting service "undefined"
Any hints to what I'm doing wrong? I'm new to nodejs and would really appreciate sample code that is correct for this scenario.
Edit
I tried import
Container.import([CmkeService]);
MissingProvidedServiceTypeError [ServiceNotFoundError]: Cannot determine a class of the requesting service "undefined"

Resources