typedi + fastify - initialize service asynchronously - node.js

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"

Related

Inversify - Jest - call API endpoint to test auth guard

Iam using Inversify & inversify-express-utils for building an Express Server. I am new to testing(Jest).
I need to call API instead of calling the controller class so that I can test the AuthGuard and RolesGuard. I tried using supertest library. Iam facing some issues. Im pretty sure that I have a wrong test setup.
user.controller.spec.ts:
import 'reflect-metadata';
import { mock } from 'jest-mock-extended';
import request from 'supertest';
import { MailService } from '../../../shared/mail/mail. Service';
import { UserController } from '../controller/users.controller';
import { UserService } from '../service/users.service';
import { ValidationException } from '../../../core/exception';
import { createUserFixture, updateUserFixture, userFixture } from '../fixtures/userFixture';
import { bootstrap } from '../../../main';
const userServiceMock = mock<UserService>();
const mailServiceMock = mock<MailService>();
describe('Users Controller', () => {
let app: any;
let sut: UserController;
sut = new UserController(userServiceMock, mailServiceMock);
beforeEach(async () => {
app = await bootstrap();
});
test('should throw auth error', async () => {
const req = await request(app).get('/user');
console.log(req);
});
});
main.ts
import 'reflect-metadata';
import cors from 'cors';
import express, { NextFunction, Response, Request } from 'express';
import helmet from 'helmet';
import { InversifyExpressServer } from 'inversify-express-utils';
import { container } from './core/inversify/inversify.config';
import { Sql } from './database/sql';
import { GlobalErrorConfig } from './core/server';
export const bootstrap = () => new Promise((resolve) => {
/// /Connect to SQL Server
new Sql().connect();
/// Start Server
const server = new InversifyExpressServer(container);
server.setConfig((app) => {
app.use(express.json());
app.use(cors());
app.use(helmet());
});
/// Global Error Config
GlobalErrorConfig(server);
/// Build Server
const app = server.build();
app.listen(process.env.PORT || 5000, () => {
console.log(
`Server is running on http://localhost:${process.env.PORT || 5000}`,
);
resolve(app);
});
});
bootstrap();
While trying to start the test, I get these following error:
The error:
Jest trying to warn delayed logs.
Same controller name error(app working normally on npm start).
Jest warning a file import after Jest environment torn down.
I have no clue what went wrong. I would really appreciate if anyone points out the issue or suggesting me the proper Jest API testing setup. Thanks in advance.

A way to shutdown my IPFS node when nextjs recompiles the API?

Using NextJS, I have a Db.js file:
import { create } from 'ipfs'
let dbs = null
export async function createDbs() {
if(!dbs){
console.log('starting up IPFS node and databases...')
const node = await create({
preload: { enabled: false },
repo: './ipfs',
EXPERIMENTAL: { pubsub: true },
config: {
Bootstrap: [],
Addresses: { Swarm: [] }
}
})
dbs = { node }
console.log('Startup successful.')
}
return Promise.resolve(dbs)
}
So that the files in my api folder use it in their handlers. Example:
import { createDbs } from '../../lib/Db.js'
export default async function handler(req, res) {
return new Promise(async resolve => {
try {
const dbs = await createDbs()
...
} catch(e) {
res.status(500).json({ error: 'Unexpected error : ' + e })
} finally {
resolve()
}
})
}
Works fine, until I change a source file in the api folder (happens quite a lot during development), and NextJs auto-recompiles the api server.
Then the reference to the Ipfs node is GC'ed, and the node initialization code in 'Db.js' runs again when the Api handler is called.
Problem: when the node was first created, it locked the files using some standard Ipfs mechanism, and as the node was not gracefully stopped before NextJs auto-recompiled the Api, I get an exception when trying to create the Ipfs node l {"code":"ERR_LOCK_EXISTS","level":"error","name":"LockExistsError"}.
I can't find a place where to stop the node gracefully.
For example:
// shutdown calls 'node.stop()'
process.on('exit', () => shutdown(node, 'exit'))
does work when I exit the nextJs dev process, but not when it recompiles.
Where could I call node.stop() so that the lock is released when NextJs recompiles the Api ?

How can I fix this error with my API (404 not found)

I'm making an application that takes codes from mongoDB and lists them onto a web page, but currently I'm getting an error in the console:
GET http://localhost:4200/api/code/codes/ 404 (Not Found) zone.js:2863
I haven't encountered this error before & I'm not sure if there is an error in my code or a problem with a port, my server starts on port 3000 and angular portion starts up on port 4200. here is my api to find all codes
router.get("/codes", async (req, res) => {
try {
Code.find({}, function (err, codes) {
if (err) {
console.log(err);
res.status(501).send({
message: `MongoDB Exception: ${err}`,
});
} else {
console.log(codes);
res.json(codes);
}
});
} catch (e) {
console.log(e);
res.status(500).send({
message: `Server Exception: ${e.message}`,
});
}
});
my service file
export class CodeService {
constructor(private http: HttpClient) {}
findAllCodes() {
return this.http.get('api/code/codes/');
}
}
and my home component ts where I'm trying to display the codes
export class HomeComponent implements OnInit {
code: Code[];
constructor(private codeService: CodeService) {
this.codeService.findAllCodes().subscribe(
(res) => {
// Logging for debugging purposes
console.log('--Server respons from findAllCodes--');
console.log(res);
},
(err) => {
console.log('--Server error--');
console.log(err);
},
() => {
console.log('--codes--');
console.log(this.code);
}
);
}
ngOnInit(): void {}
}
When you call the API as you did, you will make the request to the same source of your web application, so when you call this route, instead of calling your API route, you are calling a route of your own web app.
To fix that, you need to check in which port your API is running and do one of the following:
Create a proxy file that will redirect your API calls to the port that you are using. Example:
In your root dir: proxy.json
{
"/api": {
"target": "http://localhost:9000",
"secure": false
}
}
Do the calls with the full path for the API:
return this.http.get('http://localhost:[SERVER_PORT]/api/code/codes/');
If you don't have CORS configuration on your server, you might need to configure it, because you are going to make a call to your API from a different domain. If you are using node.js with express check this link.

NestJs Eventbridge Lambda function

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)

Load custom configuration runtime

I have a nuxt application in which I will need to append data from a generated configuration file when the application is first started. The reason I cannot do this in the actual build is because the configuration file does not exists at this point; it is generated just before calling npm start by a bootstrap script.
Why don't I generated the configuration file before starting the application you may ask and this is because the application is run in a docker container and the built image cannot include environment specific configuration files since it should be used on different environments such as testing, staging and production.
Currently I am trying to use a hook to solve this, but I am not really sure on how to actually set the configuration data in the application so it can be used everywhere:
# part of nuxt.config.js
hooks: {
listen(server, listener) {
# load the custom configuration file.
fs.readFile('./config.json', (err, data) => {
let configData = JSON.parse(data));
});
}
},
The above hook is fired when the application first starts to listen for connecting clients. Not sure this is the best or even a possible way to go.
I also made an attempt of using a plugin to solve this:
import axios from ‘axios’;
export default function (ctx, inject) {
// server-side logic
if (ctx.isServer) {
// here I would like to simply use fs.readFile to load the configuration, but this is not working?
} else {
// client-side logic
axios.get(‘/config.json’)
.then((res) => {
inject(‘storeViews’, res.data);
});
}
};
In the above code I have problems both with using the fs module and axios.
I was also thinking about using a middleware to do this, but not sure on how to proceed.
If someone else has this kind of problem here is the solution I came up with in the end:
// plugins/config.js
class Settings
{
constructor (app, req) {
if (process.server) {
// Server side we load the file simply by using fs
const fs = require('fs');
this.json = fs.readFileSync('config.json');
} else {
// Client side we make a request to the server
fetch('/config')
.then((response) => {
if (response.ok) {
return response.json();
}
})
.then((json) => {
this.json = json;
});
}
}
}
export default function ({ req, app }, inject) {
inject('config', new Settings(app, req));
};
For this to work we need to use a server middleware:
// api/config.js
const fs = require('fs');
const express = require('express');
const app = express();
// Here we pick up requests to /config and reads and return the
// contents of the configuration file
app.get('/', (req, res) => {
fs.readFile('config.json', (err, contents) => {
if (err) {
throw err;
}
res.set('Content-Type', 'application/json');
res.end(contents);
});
});
module.exports = {
path: '/config',
handler: app
};

Resources