InversifyJS - injectable is not a function - node.js

I'm trying to set up a NodeJS/TypeScript project using InversifyJS but I'm having some trouble getting off the ground. I've been looking at this for the past couple of days and can't figure out the issue. Everything builds correctly, but when I try to launch the Node server I get the following error:
TypeError: inversify_1.injectable is not a function
I'm running NodeJS v6.2.2 (Windows 10 x64) and have TypeScript 1.8 installed. I've tried building using both VS2015 and gulp. Below is a minimal example that I've tried that experiences the same error. I can't see anything wrong but I must be missing something obvious. (Sloppy code aside, it should still work.)
server.ts:
/// <reference path="./node_modules/reflect-metadata/reflect-metadata.d.ts" />
/// <reference path="./node_modules/inversify-dts/inversify/inversify.d.ts" />
/// <reference path="./typings/index.d.ts" />
import "reflect-metadata"
import { Application } from "./app/app"
import { ITest, TestClass } from "./app/testclass"
import { Kernel, injectable, inject } from "inversify"
var kernel = new Kernel();
kernel.bind<ITest>("ITest").to(TestClass);
kernel.bind<Application>(Application).to(Application);
var app = kernel.get<Application>(Application);
app.initialize();
app/app.ts
/// <reference path="../node_modules/inversify-dts/inversify/inversify.d.ts" />
/// <reference path="../typings/index.d.ts" />
import * as Hapi from "hapi";
import { inject, injectable } from "inversify"
import { ITest } from "../app/testclass"
#injectable()
export class Application {
private testClass: ITest;
constructor( #inject("ITest") test: ITest) {
this.testClass = test;
};
initialize() {
var server = new Hapi.Server({
connections: {
router: {
isCaseSensitive: false,
stripTrailingSlash: true
}
}
});
server.connection({
port: '3000',
host: 'localhost'
});
server.route({
method: 'GET',
path: '/',
handler: (request: Hapi.Request, reply: Hapi.IReply) => {
var str = this.testClass.test();
reply(str);
}
});
server.start(function () {
console.log('Server running at:', server.info.uri);
});
}
}
app/testclass.ts
/// <reference path="../node_modules/inversify-dts/inversify/inversify.d.ts" />
import { injectable } from "inversify"
export interface ITest {
test(): string;
}
#injectable()
export class TestClass implements ITest {
test(): string {
return "testing";
}
}
tsconfig.json
{
"files": [
"server.ts",
"app/app.ts",
"app/testclass.ts"
],
"compilerOptions": {
"module": "commonjs",
"noImplicitAny": true,
"target": "es6",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"outDir": "../release/"
}
}
full error output from node:
C:\Projects\inversifytest\release\app\app.js:50
inversify_1.injectable(),
^
TypeError: inversify_1.injectable is not a function
at Object.<anonymous> (C:\Projects\inversifytest\release\app\app.js:50:17)
at Module._compile (module.js:541:32)
at Object.Module._extensions..js (module.js:550:10)
at Module.load (module.js:458:32)
at tryModuleLoad (module.js:417:12)
at Function.Module._load (module.js:409:3)
at Module.require (module.js:468:17)
at require (internal/module.js:20:19)
at Object.<anonymous> (C:\Projects\inversifytest\release\server.js:6:15)
at Module._compile (module.js:541:32)

I figured it out - it was a dumb mistake as I suspected. I had the wrong version of InversifyJS installed. The decorators only work with the new version 2.x and npm had installed 1.3.1 (because I didn't explicitly specify a version).

Related

TypeError: Cannot read properties of undefined (reading 'listen') when testing with Jest, Supertest, Express, Typescript

Problem:
when running jest and supertest, i get an error before it reaches the actual tests i've described. Server runs fine using the start script, app is defined. But when running test script, app is undefined.
Background:
I'm fairly new to typescript and this is the first time I'm using any kind of testing.
I want to seperate the server instance, as seen on several blog posts and tutorials, as i plan on running multiple tests in different files.
If you have any suggestions even if they are something I already tried, ill try again and let you know. I am at my wits end so any help is much apprecitated. Thank you.
Error:
FAIL src/components/users/user.test.ts
● Test suite failed to run
TypeError: Cannot read properties of undefined (reading 'listen')
6 |
7 | dbConnection();
> 8 | export const server = app.listen(config.server.port, () => {
| ^
9 | logger.info(`Server is running on port: ${config.server.port}`);
10 | });
11 |
at Object.<anonymous> (src/index.ts:8:27)
at Object.<anonymous> (src/library/exitHandler/exitHandler.ts:2:1)
at Object.<anonymous> (src/library/errorHandler/errorHandler.ts:2:1)
at Object.<anonymous> (src/middleware/validateSchema.ts:3:1)
at Object.<anonymous> (src/components/users/routes.ts:2:1)
at Object.<anonymous> (src/server.ts:2:1)
at Object.<anonymous> (src/components/users/user.test.ts:2:1)
user.test.ts
import request from 'supertest';
import app from '../../server';
describe('User registration', () => {
it('POST /register --> return new user instance', async () => {
await request(app) // error occurs when reaching this point
.post('/user/register')
.send({
firstName: 'Thomas',
lastName: 'Haek',
email: 'thomashaek#gmail.com',
password: '12345678aA',
confirmPassword: '12345678aA'
})
.expect(201)
.then((response) => {
expect(response.body).toEqual(
expect.objectContaining({
_id: expect.any(String),
firstName: expect.any(String),
lastName: expect.any(String),
email: expect.any(String),
token: expect.any(String)
})
);
});
});
});
server.ts
import express, { Application } from 'express';
import userRouter from './components/users/routes';
import { routeErrorHandler } from './middleware/errorHandler';
import httpLogger from './middleware/httpLogger';
import './process';
const app: Application = express();
app.use(httpLogger);
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use('/user', userRouter);
app.use(routeErrorHandler);
export default app
index.ts
import { createHttpTerminator } from 'http-terminator';
import config from './config/config';
import dbConnection from './config/dbConnection';
import logger from './library/logger';
import app from './server'
dbConnection();
export const server = app.listen(config.server.port, () => {
logger.info(`Server is running on port: ${config.server.port}`);
});
export const httpTerminator = createHttpTerminator({ server });
package.json scripts
"scripts": {
"test": "env-cmd -f ./src/config/test.env jest --watchAll",
"start": "env-cmd -f ./src/config/dev.env node build/index.js",
},
tsconfig.json
{
"compilerOptions": {
"outDir": "./build",
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"module": "commonjs",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"target": "es6",
"noImplicitAny": true,
"moduleResolution": "node",
"sourceMap": true,
},
"include": ["src/**/*"]
}
jest.config.ts
import { Config } from 'jest';
/** #type {import('ts-jest').JestConfigWithTsJest} */
const config: Config = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['./src'],
moduleFileExtensions: ['js', 'ts'],
clearMocks: true,
collectCoverage: true,
coverageDirectory: 'coverage',
coveragePathIgnorePatterns: ['/node_modules/', '/src/config/'],
coverageProvider: 'v8',
coverageReporters: ['json', 'text', 'lcov', 'clover'],
verbose: true
};
export default config;
exitHandler.ts
import mongoose from 'mongoose';
import { httpTerminator, server } from '../..';
import logger from '../logger';
class ExitHandler {
public async handleExit(code: number, timeout = 5000): Promise<void> {
try {
logger.info(`Attempting graceful shutdown with code: ${code}`);
setTimeout(() => {
logger.info(`Forcing a shutdown with code: ${code}`);
process.exit(code);
}, timeout).unref();
if (server.listening) {
logger.info('Terminating HTTP connections');
await httpTerminator.terminate();
await mongoose.connection.close();
}
logger.info(`Exiting gracefully with code ${code}`);
process.exit(code);
} catch (error) {
logger.error(error);
logger.error(
`Unable to shutdown gracefully... Forcing exit with code: ${code}`
);
process.exit(code);
}
}
}
export const exitHandler = new ExitHandler();
Things I've tried:
using the same env files for both test and server script (same error)
messing around with the tsconfig and jest config files (same error)
using module.exports = app instead of export default app or export const server = app (same error)
commenting out all the middleware and routes and just exporting the app (same error)
I believe this is caused by circular dependency. From the error stack
at Object.<anonymous> (src/index.ts:8:27)
at Object.<anonymous> (src/library/exitHandler/exitHandler.ts:2:1
…
at Object.<anonymous> (src/server.ts:2:1)
at Object.<anonymous> (src/components/users/user.test.ts:2:1)
I see that server.ts deps on exitHandler.ts which in turn deps on index.ts. But in index.ts you import app from './server' forming a circle.
More specifically, in order for app from server.ts to be created, it needs exitHandler, but that thing needs index, and index needs server. It’s like recursion without a base case return. Unlike indefinite function recursion, dependency resolution will just give you app as undefined.
Thus you need to break the circle. Use some dependency injection trick to break the tie between exitHandler and index will do.
If you don’t know how to do that, post the exitHandler.ts code and I’ll follow up.
Instead of import { httpTerminator, server } from '../..'; try this:
let server, httpTerminator;
export function injectDependency(s, h) {
server = s;
httpTerminator = h;
}
Now in index.ts
import { injectDependency } from "…/exitHandler"
injectDependency(server, httpTerminator);

i cannot declare new property in request object of express

I'm trying to attach the new property to the request object in typescript.
this is the code :
import { request, Request, response, Response } from "express";
((req: Request, res: Response) => {
console.log(req.user);
})(request, response)
i'm declaring like this :
declare global {
namespace Express {
interface Request {
user: string;
}
}
}
and then I'm running it with ts-node. result is :
/home/mahdi/Desktop/learn-stuf/test/node_modules/ts-node/src/index.ts:843
return new TSError(diagnosticText, diagnosticCodes, diagnostics);
^
TSError: ⨯ Unable to compile TypeScript:
x.ts:9:21 - error TS2339: Property 'user' does not exist on type 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>'.
9 console.log(req.user);
~~~~
at createTSError (/home/mahdi/Desktop/learn-stuf/test/node_modules/ts-node/src/index.ts:843:12)
at reportTSError (/home/mahdi/Desktop/learn-stuf/test/node_modules/ts-node/src/index.ts:847:19)
at getOutput (/home/mahdi/Desktop/learn-stuf/test/node_modules/ts-node/src/index.ts:1057:36)
at Object.compile (/home/mahdi/Desktop/learn-stuf/test/node_modules/ts-node/src/index.ts:1411:41)
at Module.m._compile (/home/mahdi/Desktop/learn-stuf/test/node_modules/ts-node/src/index.ts:1596:30)
at Module._extensions..js (node:internal/modules/cjs/loader:1159:10)
at Object.require.extensions.<computed> [as .ts] (/home/mahdi/Desktop/learn-stuf/test/node_modules/ts-node/src/index.ts:1600:12)
at Module.load (node:internal/modules/cjs/loader:981:32)
at Function.Module._load (node:internal/modules/cjs/loader:827:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12) {
diagnosticCodes: [ 2339 ]
}
I tested too many answers of sites, but one of them did not work. please help.
First, I think your declare file got some problems.
edit the file like
export {}
declare global {
namespace Express {
interface Request {
user: string;
}
}
}
or
namespace Express {
interface Request {
user?: string
}
}
add directory that contains the declare file in tsconfig. Since I usually name it express.d.ts and place in src/types folder, in my case, tsconfig.json will be edited like this
{
"compilerOptions": {
"typeRoots": ["src/types"],
}
}
lastly, also add ts-node configuration in tsconfig.json. (not in compilerOptions)
{
"ts-node": {
"files": true
}
}
Are you maybe looking for #types/express ?
You can also fix it with intersection type :
function endpoint (req: Request, res: Response & {user: string;}) {
console.log(req.user);
}
But maybe you are looking for req.body.user, type Response<{user: string;}> ?

Nest can't resolve dependencies of the searchService (?). Please make sure that the argument at index

I am using Nestjs framework to develop my Elastic Service application.
I am using '#nestjs/elasticsearch' library inside my code and i am simply trying to establish database connection and use inside all other module. Please find my code example here.
My App Module looks below
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from './config/config.module';
import { DatabaseModule } from './database/database.module';
import { LayoutmgmtModule } from './layoutmgmt/layoutmgmt.module';
#Module({
imports: [ConfigModule,DatabaseModule, LayoutmgmtModule],
controllers: [AppController],
providers: [AppService]
})
export class AppModule {}
My Database Module is,
import { Module } from '#nestjs/common';
import { ElasticsearchModule } from '#nestjs/elasticsearch';
import {ConfigModule} from '../config/config.module';
import {ConfigService} from '../config/config.service';
import {DatabaseService} from './database.service';
#Module({
imports:[ElasticsearchModule.registerAsync({
imports:[ConfigModule],
useFactory: async (configService: ConfigService) => ({
host: configService.get('ELASTIC_URL'),
log: 'trace',
requestTimeout: 3000
}),
inject:[ConfigService]
})],
providers:[DatabaseService],
})
export class DatabaseModule {}
My Database Service is,
import { Injectable,HttpException } from '#nestjs/common';
import { ElasticsearchService } from '#nestjs/elasticsearch';
import { Client } from 'elasticsearch';
#Injectable()
export class DatabaseService {
private readonly esClient:Client;
constructor(private readonly elasticsearchService: ElasticsearchService) {
try {
this.esClient = elasticsearchService.getClient();
this.esClient.ping({ requestTimeout: 3000 },function(err,res,status){
if (err || !(res)) {
console.log('Unable to connect to the server. Please start the server. Error:', err);
throw new HttpException({
status: 'error',
message: 'Unable to connect to the server. Please start the server. Error:'
}, 500);
} else {
console.log('Connected to Server successfully!',res, status);
}
});
}
catch(err) {
console.log('Error in connection' + err);
throw new HttpException({
status: 'error',
message: 'Unable to reach Elasticsearch cluster'
}, 500);
}
}
}
Now Above i had initialized the connection and its getting connected to the database without issues, But i am trying to re-use ElasticsearchService in another module/service called layout module
Layout Module looks below
import { Module } from '#nestjs/common';
import { LayoutmgmtController } from './layoutmgmt.controller';
import { LayoutmgmtService } from './layoutmgmt.service';
#Module({
controllers: [LayoutmgmtController],
providers: [LayoutmgmtService],
})
export class LayoutmgmtModule {}
Layout Service Looks below
import { Inject, Injectable, Dependencies } from '#nestjs/common';
import { ElasticsearchService } from '#nestjs/elasticsearch';
import { Client } from 'elasticsearch';
#Injectable()
export class LayoutmgmtService {
private readonly esClient:Client;
constructor(#Inject(ElasticsearchService) private readonly elasticsearchService: ElasticsearchService) {
this.esClient = elasticsearchService.getClient();
if (!this.esClient){
console.log("Elastic alreayd connected")
}
}
}
If i use the ElasticSErachService in above service inside the constructor i am getting the below error, I wanted to reuse the existing connection ..
[Nest] 10724 - 10/14/2019, 4:50:41 PM [ExceptionHandler] Nest can't resolve dependencies of the LayoutmgmtService (?). Please make sure that the argument at index [0] is available in the LayoutmgmtModule context. +40ms
Error: Nest can't resolve dependencies of the LayoutmgmtService (?). Please make sure that the argument at index [0] is available in the LayoutmgmtModule context.
at Injector.lookupComponentInExports (C:\Subu\Elastic\elastic-nest-js\node_modules#nestjs\core\injector\injector.js:183:19)
at process._tickCallback (internal/process/next_tick.js:68:7)
at Function.Module.runMain (internal/modules/cjs/loader.js:744:11)
at Object. (C:\Subu\Elastic\elastic-nest-js\node_modules\ts-node\src\bin.ts:158:12)
at Module._compile (internal/modules/cjs/loader.js:688:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:699:10)
at Module.load (internal/modules/cjs/loader.js:598:32)
at tryModuleLoad (internal/modules/cjs/loader.js:537:12)
at Function.Module._load (internal/modules/cjs/loader.js:529:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:741:12)
LayoutmgmtModule and DatabaseModule are not related anyway in your code.
You have registered the ElasticsearchModule in DatabaseModule but not in LayoutmgmtModule so it is unable to find the service.
Solution 1
You can get rid of the LayoutmgmtModule by just adding LayoutmgmtController and LayoutmgmtService in DataBaseModule and it should start working
Solution 2
You can make DataBaseModule as global by just adding #Global() before #Module decorator as mentioned here
You are not exporting ElasticsearchService anywhere. Perhaps your DatabaseModule should export it together with DatabaseService (LayoutmgmtService should use either of those).
On top of that, you should add given Service to providers of LayoutmgmtModule

Failed loading config.ts due to import protractor

I am trying to start a new protractor project to test an angular site. I installed node.js, typescript, protractor globally and jasmine. I go to the project folder and do webdriver-manager update. Then I do webdriver-manager start. I also build the config.ts using tsc config.ts. Everything works fine until i try protractor config.ts. Here i will provide my config.ts and my package.json.
{
"name": "protractortests",
"version": "1.0.0",
"description": "Automated tests for a game platform",
"main": "index.js",
"dependencies": {
"#types/jasmine": "^3.3.12",
"#types/node": "^12.0.2",
"jasmine": "^3.4.0",
"protractor": "^5.4.2"
},
"devDependencies": {},
"scripts": {
"test": "protractor config.ts"
}
and my config.ts:
import { ProtractorBrowser, Config } from "protractor";
export let config: Config = {
seleniumAddress: 'http://localhost:4444/wd/hub',
capabilities: {
'browserName': 'chrome'
},
framework: 'jasmine',
specs: ['./FirstSpec.ts'],
jasmineNodeOpts: {
defaultTimeoutInterval: 90000
},
onPrepare: () => {
let globals = require('protractor/built');
let browser = globals.browser;
browser.manage().window().maximize();
browser.manage().timeouts().implicitlyWait(5000);
}
}
E/configParser - Error code: 105
[11:40:53] E/configParser - Error message: failed loading configuration file config.ts
[11:40:53] E/configParser - C:\Users\Victor\Documents\ProtractorTests\config.ts:1
(function (exports, require, module, __filename, __dirname) { import { ProtractorBrowser, Config } from "protractor";
^
SyntaxError: Unexpected token {
at new Script (vm.js:80:7)
at createScript (vm.js:274:10)
at Object.runInThisContext (vm.js:326:10)
at Module._compile (internal/modules/cjs/loader.js:664:28)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:712:10)
at Module.load (internal/modules/cjs/loader.js:600:32)
at tryModuleLoad (internal/modules/cjs/loader.js:539:12)
at Function.Module._load (internal/modules/cjs/loader.js:531:3)
at Module.require (internal/modules/cjs/loader.js:637:17)
at require (internal/modules/cjs/helpers.js:22:18)
npm ERR! Test failed. See above for more details.
By referring to example at link https://github.com/angular/protractor/tree/5.4.1/exampleTypescript
You don't need to import ProtractorBrowser. You can work with browser directly with object Browser.
Commenters pointed out that you can't give Protractor a config file in native Typescript, and need to compile it to config.js then pass that. There's really no point in writing the file in Typescript at all, it just adds an extra step that provides no value to you. If you want editor autocomplete, you can decorate your JS with type annotations:
const { join } = require("path");
const { register } = require("ts-node");
const { SpecReporter, StacktraceOption } = require("jasmine-spec-reporter");
/** #type {import("protractor").Config} */
const config = {
directConnect: true,
baseUrl: "http://localhost:8080",
framework: "jasmine",
noGlobals: true,
specs: [ "./src/**/*.e2e.ts" ],
onPrepare() {
register({
project: join(__dirname, "./tsconfig.json")
});
jasmine.getEnv().addReporter(new SpecReporter({
spec: {
displayStacktrace: StacktraceOption.PRETTY
}
}));
}
};
module.exports = { config };
I adapted my config from this excellent example.

Setting up TypeScript for node by using module=system is not working

I tried to setup a restify project using typescript.
After various tries I was able to create a working version by using "module: commonjs" in the tsconfig.json
I'd prefer to use system - but I wasn't able to set it up with systemjs
boot.ts
import {AppServer} from './app';
var _appServer = new AppServer();
tsconfig.json
{
"compilerOptions": {
"target": "ES5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": false
},
"exclude": [
"node_modules"
]
}
app.ts
/// <reference path="typings/restify/restify.d.ts" />
import {Server, Response, Request, createServer} from 'restify';
export class AppServer {
private server: Server;
constructor() {
this.init();
}
init() {
this.server = createServer();
this.server.get('/hello/:name', this.respond);
this.server.listen(8080, () => {
console.log('%s listening at %s', this.server.name, this.server.url);
});
}
respond(req: Request, res: Response, next: Function) {
res.send('hello ' + req.params.name);
next();
}
}
using
"module": "system"
in the tsconfig.json, I get the following output (even with import System = require('systemjs')in the boot.ts):
➜ server git:(master) ✗ npm run server
> server#1.0.0 server /Users/maquh/Development/02_Backgular/server
> node boot.js
/Users/maquh/Development/02_Backgular/server/boot.js:1
(function (exports, require, module, __filename, __dirname) { System.register(['./app'], function(exports_1) {
^
ReferenceError: System is not defined
at Object.<anonymous> (/Users/maquh/Development/02_Backgular/server/boot.js:1:63)
at Module._compile (module.js:425:26)
at Object.Module._extensions..js (module.js:432:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:313:12)
at Function.Module.runMain (module.js:457:10)
at startup (node.js:138:18)
at node.js:974:3
Transpiled boot.js
System.register(['./app'], function(exports_1) {
var app_1;
var _appServer;
return {
setters:[
function (app_1_1) {
app_1 = app_1_1;
}],
execute: function() {
//System.import('./app.ts').
_appServer = new app_1.AppServer();
}
}
});
//# sourceMappingURL=boot.js.map
UPDATE:
I also tried another alternative version of boot.ts
var System = require('systemjs');
System.transpiler = 'ts';
System.import('./app.js').then(function(m) {
console.log(m);
}, function(err) {
console.error(err);
});
those leads to the following error:
[Error: ENOENT: no such file or directory, open '/Users/markusbellgardt/Development/02_Backgular/server/restify']
System uses ES6 module loaders which nodejs (to my knowledge) does not currently support, your initial use case was right where you were outputting to commonjs. If you want to use the ES6 style module resolution within node you will need to tell node how to load it such as:
https://www.npmjs.com/package/es6-module-loader
I use typescript in node fine but I use commonjs, I have used system in the browser before and it works fine when you have ES6 module loaders available such as SystemJs (JSPM).

Resources