Mongoose + Typescript - Unable to require file: mongodb\index.ts - node.js

I have a Node + Express application with Typescript, which was running fine, until I decided to include Mongoose in the equation. When I try running it, I get the following error:
TypeError: Unable to require file: mongodb\index.ts
This is usually the result of a faulty configuration or import. Make sure there is a '.js', '.json' or other executable extension with loader attached before 'ts-node' available.
I'm running the application with Nodemon, and have the following configuration in nodemon.json:
{
"execMap": {
"ts": "ts-node"
}
}
Here's my tsconfig.json:
{
"compilerOptions": {
"esModuleInterop": true,
"moduleResolution":"node",
"baseUrl": ".",
"target": "es6",
"paths": {
"#controllers/*": ["./controllers/*"],
"#services/*": ["./services/*"],
"#routes/*": ["./routes/*"],
"#customTypes/*": ["./types/*"],
"#utils/*": ["./utils/*"],
"#graphql/*": ["./graphql/*"]
}
}
}
I'm kind of new to Node with Typescript, so I probably made some mistakes, but cannot find any info regarding what exactly is wrong.
Tried downgrading Mongoose, installing MongoDB manually and changed versions of #types/mongoose, but to no avail.

Related

TypeScript on AWS Lambda: to bundle imports (how?) or not to bundle? or: Runtime.ImportModuleError: Cannot find module '#aws-sdk/..."

I have the following lambda.ts code I'm trying to make running on an AWS Lambda:
import 'aws-sdk'
import { /* bunch of stuff... */ } from "#aws-sdk/client-cloudwatch-logs";
import {Context, APIGatewayProxyResult} from 'aws-lambda';
import {DateTime} from "luxon";
export const lambdaHandler = async (event: any, context: Context): Promise<APIGatewayProxyResult> => {
/* ... stuff ... */
}
which gets transpiled to:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.lambdaHandler = void 0;
require("aws-sdk");
//import {CloudWatchClient} from "#aws-sdk/client-cloudwatch";
const client_cloudwatch_logs_1 = require("#aws-sdk/client-cloudwatch-logs");
const luxon_1 = require("luxon");
const lambdaHandler = async (event, context) => {
/* ... transpiled stuff ... */
}
When hitting the button, I'm getting this Response:
{
"errorType": "Runtime.ImportModuleError",
"errorMessage": "Error: Cannot find module '#aws-sdk/client-cloudwatch-logs'\nRequire stack:\n- /var/task/lambda.js\n- /var/runtime/index.mjs",
"trace": [
"Runtime.ImportModuleError: Error: Cannot find module '#aws-sdk/client-cloudwatch-logs'",
"Require stack:",
"- /var/task/lambda.js",
"- /var/runtime/index.mjs",
" at _loadUserApp (file:///var/runtime/index.mjs:951:17)",
" at async Object.UserFunction.js.module.exports.load (file:///var/runtime/index.mjs:976:21)",
" at async start (file:///var/runtime/index.mjs:1137:23)",
" at async file:///var/runtime/index.mjs:1143:1"
]
}
I played a lot with tsconfig.json, trying many things from Google / GitHub / SO / Rumors / Astrology / Numerology / Praying (monotheistic, pantheon-dwelling, neither..), but it only made me more confused.
For instance:
Using the tsconfig.json from Building Lambda functions with TypeScript still emits a single transpiled .js file without embedding the imported #aws-sdk/client-cloudwatch-logs module in the emitted output lambda.js file
Installing the AWS Common Runtime (CRT) Dependency
states that I need to npm install #aws-sdk/... (naturally), but doesn't explain anything beyond, which makes me think that maybe I shouldn't bundle them at all, but simply import them (in the assumption that they are pre-defined/loaded in AWS's Lambda's runtime)
Runtime is Node.js 16.x, Handler is lambda.lambdaHandler (emitted file is called lambda.js), and this is my current tsconfig.json:
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "Node",
"target": "ES2022",
"sourceMap": true,
"lib": [
"ES2021"
],
"typeRoots": ["node_modules/#types"],
"outDir": "build",
"baseUrl": "src",
"strict": true,
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"inlineSources": true,
"rootDir": "src",
"preserveConstEnums": true,
"isolatedModules": true,
"incremental": true,
"importHelpers": true
},
"exclude": [
"node_modules",
"**/*.test.ts"
]
}
So I'm trying to understand:
Do I even need to bundle those imported modules (such as #aws-sdk/client-cloudwatch-logs) at all, or they are already loaded by AWS Lambda's runtime?
If I do need to bundle, then how? do I need to use some bundler or is it just a matter of configuring tsconfig.json properly?
If bundler isn't mandatory, then how do I setup tsconfig.json to emit those 3rd-party modules?
If a bundler is mandatory, then can they all fit (WebPack, Babel, etc..)? or since no frontend (index.html) is involved, then not all of them can fit?
AWS SDK for JavaScript v3 (AKA modular) is not installed globally in the lambda execution context. You are using a v3 module (#aws-sdk/client-cloudwatch-logs) which is why it fails. AWS SDK v2 is installed globally, you are also using it (aws-sdk) so that require works fine.
You should use a bundler like webpack, esbuild, parcel or rollup. If you are using AWS CDK, there is a nodejs function construct that will do the bundling with esbuild for you.
TS will only emit your compiled javascript. If you are depending on javascript found in your node_modules directory, simply include that directory in your deployment package.
Generally, bundlers will take your application entry points (main.js, handler.js, whatever you want really) and recursively resolve all the dependencies, tree-shake any unreachable code then create one file for each entry point that has no other external dependencies. There is a runtime performance cost to this of course but it does simplify things and in a serverless context it isn't usually too impactful.
So, to resolve your error you can take one of two approaches:
Include your node_modules directory in your deployment package. (trivial)
Use a bundler or CDK (more complex)
Note that in either case, you need to be careful about dependencies with native bindings (binaries basically) as the one installed on your dev machine likely isn't supported in the lambda environment.

"Right-hand side of 'instanceof' is not an object" when testing NestJS + TypeORM using Jest

I have a working NestJS server that performs some TypeORM repository.insert() command. However when running the same operation from a Jest test (using #nestjs/testing's Test.createTestingModule(...), the infamous Right-hand side of 'instanceof' is not an object appears.
Looking in more details, it appears that this is due to some dynamic loading occurring in TypeORM's QueryBuilder:
// loading it dynamically because of circular issue
const InsertQueryBuilderCls = require("./InsertQueryBuilder").InsertQueryBuilder;
That line succeeds when running the NestJS server but fails when running the Jest test. More specifically:
in the NestJS server: QueryBuilder require("./InsertQueryBuilder") returns an ES module with a InsertQueryBuilder in it. When setting a breakpoint here, strangely the debugged file appears located at src/query-builder/QueryBuilder.ts (which is non-existent, and should rather be node_modules/typeorm/query-builder/QueryBuilder.js), but this succeeds.
in the Jest test: require("./InsertQueryBuilder") return an empty object, and so InsertQueryBuilder is undefined which not an object indeed. The debugged file is as expected node_modules/typeorm/query-builder/QueryBuilder.js, but this fails.
I looks like it could be related to my Jest configuration, which is:
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
moduleDirectories: ['node_modules', 'src']
}
as my Typescript sources are under a src directory under the project root. Those path issues are also be related to my tsconfig.json, which contains:
{
"compilerOptions": {
"module": "commonjs",
"allowSyntheticDefaultImports": true,
"target": "es2019",
"baseUrl": "./src",
"outDir": "./dist",
"incremental": true,
"lib": [
"es2019"
]
}

How should I configure Typescript to compile code to run in Node.JS?

First, let me be clear that this is not related to the browser. There are a hundred different questions & answers about this that all start off with "you can't use CommonJS modules in the browser...", but that's not what I'm trying to do.
I have some Typescript code which I'm trying to compile to Javascript for execution via node file-scanner.js on a server. It seems like the configuration should be pretty straightforward, but I always wind up with an error: ReferenceError: exports is not defined.
I am using import/export exclusively in my code. The main entry point looks like this:
import { loadEnv } from './modules/environment';
import { getDatabase } from './modules/models/helpers/database';
import { indexFiles } from './modules/services/indexFiles';
loadEnv();
getDatabase()
.then((db) => {
return indexFiles(db);
})
.catch((err: Error) => {
console.error('[ERROR] Unable to index files');
console.error(`[ERROR] Message = ${err.message}`)
console.error('[ERROR] Error object:', err);
console.error('[ERROR] Stack Trace', err.stack || '--no trace available--');
});
And my tsconfig.json (copied from #tsconfig/bases node 10 configuration) looks like this:
{
"compilerOptions": {
"lib": ["es2018"],
"module": "CommonJS",
"target": "es2018",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": [
"src/file-scanner.ts",
"src/modules/**/*/ts"
],
"exclude": [
"src/**/*.spec.ts"
]
}
I'm building it with:
./node_modules/.bin/tsc -p tsconfig.file-scanner.json --outDir lib
And running it with:
node lib/file-scanner.js
From what I understand, the Typescript compiler should be converting all my import/export statements to CommonJS module.exports & require statements. And I would hope that it includes all the appropriate code. But for some reason, when it comes time to execute the code, exports is not defined.
There's probably something obvious here that's staring me in the face, but I can't seem to figure it out. How do I get Typescript to compile for execution via Node.JS?

Three.js with typescript autocomplete

I have a Node.js project that uses Typescript and Three.js. To import modules, I use the commonjs syntax, which I configured via
{
"compilerOptions": {
"module": "commonjs"
}
}
in my tsconfig.json. I downloaded Three.js via NPM have a typescript file like this:
const THREE = require('three');
const scene = new THREE.Scene();
which compiles fine, but I do not get any autocomplete. I don't think this specific to the editor used, as both Visual Studio Code as well as Neovim with YouCompleteMe don't work. Both work if I use the ES6 module syntax:
import * as THREE from 'node_modules/three/src/Three';
const scene = new THREE.Scene();
Here however I cannot get it to work without giving the actual path to the library (which is a problem later on when using webpack). What did I forget to configure to get autocomplete (or the ES6 syntax without explicitly defining the path, at this point I am fine with both solutions)?
EDIT
As mentioned in the comments to accepted answer, I was not able to find my mistake, but found a working solution while trying to create a minimal working project. So I will post this here, in case it might help someone else. If you have the same problem, please still read the answer, as it is correct.
My source file (in src/main.ts):
import * as THREE from 'three';
const scene = new THREE.Scene();
package.json (with webpack to test if the library can be resolved there):
{
"devDependencies": {
"#types/node": "^12.0.4",
"three": "^0.105.2",
"ts-loader": "^6.0.2",
"typescript": "^3.5.1",
"webpack": "^4.32.2",
"webpack-cli": "^3.3.2"
}
}
tsconfig.json:
{
"compilerOptions": {
"baseUrl": ".",
"paths": { "*": ["types/*"] },
"target": "es6",
"module": "es6",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true,
"sourceMap": true
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules/"
]
}
webpack.config.js:
const path = require('path');
module.exports = {
entry: './src/main.ts',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
resolve: {
extensions: [ '.ts', '.js' ]
},
module: {
rules : [
{ test: /\.ts$/, use: [ 'ts-loader' ], exclude: /node_modules/ }
]
}
};
What version of three is installed in your package.json file? Make sure it's 0.101 or later, since that's when TypeScript support began. I recommend you use the latest (105 as of this writing), since it gets updated definition files on each release.
Then, in your .ts files, you can import it with:
import * as THREE from "three";
// After importing, THREE is available for auto-complete.
var lala = new THREE.WebGLRenderer();
Edit:
You might need to perform path-mapping in your .tsconfig file to force the compiler to find the correct module address. I've never had to do this, but the Typescript documentation suggests something like this:
{
"compilerOptions": {
"baseUrl": ".", // This must be specified if "paths" is.
"paths": {
"three": ["node_modules/three/src/Three"] // relative to "baseUrl"
}
}
}
Update r126
As of revision r126, Three.js has moved Typescript declaration files to a separate repository. If you're using r126 or later, you'll have to run npm install #types/three as a second step. Make sure you install the version of #types/three that targets your version of Three.js. For example:
"three": "0.129.0",
"#types/three": "^0.129.1",

importing class in Node js with typescript

I would import class in nodejs and use it in app.ts
var nano = require("nano");
import { EnvConfig } from './envConfig.service';
let config = new EnvConfig();
const dbCredentials: any = config.appEnv.getServiceCreds('dataservices');
export const nanodb = nano({
url: dbCredentials.url,
});
export const nanodbCockpitLight = nanodb.use('data');
console.log(dbCredentials);
When I try to compile I get this error.
import { EnvConfig } from './envConfig.service';
^
SyntaxError: Unexpected token {
I have created the tsconfig file :
{
"compilerOptions": {
"module": "commonjs",
"declaration": false,
"noImplicitAny": false,
"removeComments": true,
"noLib": false,
"allowSyntheticDefaultImports": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist",
//"baseUrl": "src" // Attention !! nécessite l'utilisation d'un loader de module node pour fonctionner sur node
},
"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.spec.ts"]
}
I get this warning
No inputs were found in config file 'c:/Users/EHHD05911.COMMUN/Documents/cockpitLight/DB mananger/tsconfig.json'. Specified 'include' paths were '["src//"]' and 'exclude' paths were '["node_modules","/.spec.ts"]'
You cannot run node app.ts file directly that won't work
You need transpiler like babel js or typescript compiler tsc so first transpile to js file and then run node app.js
You're using .js extension, you need .ts extension, e.g.: app.ts instead of app.js.
Make sure you have typescript either in npm global or in dev dependencies.
I suspect whatever you're importing has typescript syntax (strong typing and such), and so running node directly won't work. You need to run tsc first, which will transpile everything to javascript in a dist folder, and then run node dist/app.js.
This is a bit cumbersome though, which is why there is ts-node. It's exactly what it sounds like, a node REPL for typescript. You should be able to run ts-node src/app.ts.
import { something } is a typescript syntax, it won't work in a .js file. That is a separate language. Try using require instead.
Use babel js which is a toolchain that is mainly used to convert ECMAScript 2015+ code into a backwards compatible version of JavaScript in current and older browsers or environments.
package.json
"dependencies": {
"#babel/polyfill": "^7.0.0",
}
"babel": {
"presets": [
"#babel/preset-env"
]
},
"scripts": {
"start": "server.js --exec babel-node",
}
https://babeljs.io/docs
This will enable/resolve your import statements.

Resources