I'm using Jest with ts-jest module. I've followed the instructions on how to enalbe ESM, seems like ESM is working now. But when running tests, Jest doesn't see default export of my app, error:
SyntaxError: The requested module '../app' does not provide an export named 'default'
I've commented all tests, except the first one, now my test file looks like this:
describe("User registration", () => {
it("GET should return 404", async () => {
const res = await request(app).get("/api/register");
assert.equal(res.statusCode, 404);
});
}
app.ts:
import dotenv from "dotenv";
import express from "express";
import cors from "cors";
import cookieParser from "cookie-parser";
import fileUpload from "express-fileupload";
import router from "./router/index";
import errorMiddleware from "./middlewares/error-middleware";
dotenv.config();
const app = express();
// enable req.ip
app.set("trust proxy", true);
app.use(fileUpload());
app.use(express.json({ limit: "1000mb" }));
app.use(cookieParser());
app.use(
cors({
credentials: true,
origin: process.env.CLIENT_URL,
})
);
app.use("/api", router);
app.use(errorMiddleware);
export default app;
The only thing i tried was replacing
import app from "../app"; with import { default as app } from "../app";
Path to app.ts is correct
My tsconfig.json:
{
"compilerOptions": {
/* Language and Environment */
"target": "ES6" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
/* Modules */
"module": "ES6" /* Specify what module code is generated. */,
"rootDir": "./src" /* Specify the root folder within your source files. */,
"moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
"baseUrl": "./src" /* Specify the base directory to resolve non-relative module names. */,
"sourceMap": true /* Create source map files for emitted JavaScript files. */,
"outDir": "./build" /* Specify an output folder for all emitted files. */,
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */
"strict": true /* Enable all strict type-checking options. */,
"noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied 'any' type. */,
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}
Jest version: ^29.3.1
TypeScript version: 4.9.4
mate. Got rid of this error here. Looks like you just forgot to export the variable app... so, if everything is configured properly, adding this line to your app.ts should do the trick:
import dotenv from "dotenv";
import express from "express";
import cors from "cors";
import cookieParser from "cookie-parser";
import fileUpload from "express-fileupload";
import router from "./router/index";
import errorMiddleware from "./middlewares/error-middleware";
dotenv.config();
const app = express();
// enable req.ip
app.set("trust proxy", true);
app.use(fileUpload());
app.use(express.json({ limit: "1000mb" }));
app.use(cookieParser());
app.use(
cors({
credentials: true,
origin: process.env.CLIENT_URL,
})
);
app.use("/api", router);
app.use(errorMiddleware);
export default app; // <--- THIS LINE HERE
Related
I need your help on how I can use .env file on this application. here is my problem: I am building an app using ES6 module in my node express app. I am facing a problem while storing my variable in .env file, both these two ways below are giving this error : MongooseError: The uri parameter to openUri() must be a string, got "undefined". Make sure the first parameter to mongoose.connect() or mongoose.createConnection() is a string. did not connect. But when I only use the plain string connect is working, that means that I am not using the dotenv file correctly:
1-
import {} from "dotenv/config.js";
import express from "express";
import mongoose from "mongoose";
import cors from "cors";
const app=express()
...
//DB config
mongoose.connect(process.env.CONNECTION_URL,
{
useCreateIndex: true,
useNewUrlParser: true,
useUnifiedTopology: true,
})
app.listen(port,()=>console.log(`server on ${port}`)
2-
import dotenv from "dotenv";
import express from "express";
import mongoose from "mongoose";
import cors from "cors";
dotenv.config();
const app=express()
...
//DB config
mongoose.connect(process.env.CONNECTION_URL,
{
useCreateIndex: true,
useNewUrlParser: true,
useUnifiedTopology: true,
})
app.listen(port,()=>console.log(`server on ${port}`)
Here is how to use it as ES6 module
import * as dotenv from 'dotenv';
dotenv.config();
If you don't need the return value from the dotenv.config() function, you can use this pattern to execute the config function as a side-effect of the import:
import 'dotenv/config';
I recommend this syntax because it improves compatibility with Typescript compilers that are strict about the ESM spec (i.e. SWC) and also provides better compatibility with code-formatting rules that want to re-order your import statements.
Path is default to ./root/.
To add path to current directory (Dir), we need this code inside of the file that we want all variables to be imported
import dotenv from 'dotenv'
import path from 'path'
import {fileURLToPath} from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
dotenv.config({ path: path.join(__dirname, '.env') });
// const express = require('express')
import express from 'express';
server.js and .env can should be in the same directory if you don't manipulate the path.join(__dirname.
You can reach the variables process.env.myvariable. Just be sure it works and print all variables: console.log(process.env)
Thanks
You are right, you are not fetching the env variables.
I'll tell you how I do it. Just create a .env file with your variables.
Then in your app.js:
const dotenv = require('dotenv');
dotenv.config();
And to use the variable uri you have in the .env
const foo = process.env.uri
I've read a ton of the previous SO questions regarding this however, none seems to solve any of my problems.
index.test.ts
import request from 'supertest';
import * as server from '../server';
import 'jest'
//close server after each request
afterEach( //cannot find name 'afterEach'
async (): Promise<void> => {
await server.close(); // Property 'close' does not exist on type 'typeof import(<path to file)'
},
);
describe('get /', (): void => { //Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i #types/jest` or `npm i #types/mocha`
it('should respond as expected', async (): Promise<void> => {// cannot find 'it'...
const response = await request(server).get('/');
expect(response.status).toEqual(200); // cannot find 'expect'
expect(response.body.data).toEqual('Sending some JSON'); // cannot find 'expect'...
});
});
I have installed both jest and mocha types, just to make sure that it wasn't registering one over the other. Pretty much everything is not being recognized,
.babelrcc
{
"presets": ["#babel/preset-typescript"]
}
jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
};
tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"noImplicitAny": true,
"preserveConstEnums": true,
"outDir": "./dist",
"sourceMap": true,
"esModuleInterop": true
},
"include": ["./src/**/*"],
"exclude": ["test", "**/*/spec.ts", "**/*/test.ts"]
}
server
import * as dotenv from 'dotenv';
dotenv.config();
import Koa from 'koa';
import logger from 'koa-logger';
import apiRoutes from './Routes';
import cors from '#koa/cors';
import bodyParser from 'koa-bodyparser';
const app = new Koa();
const PORT = process.env.PORT || 3001;
const ENV = process.env.NODE_ENV || 'Development';
app.use(logger());
app.use(cors());
app.use(bodyParser());
app.use(apiRoutes);
export const server = app.listen(PORT, (): void => {
console.log(`🌍 Server listening on port ${PORT} - ${ENV} environment`);
});
I don't know what other information is relevant if there is something else I am missing I will update this with the information
You are exporting the named variable server directly while you are trying to import it as if it were the default export.
Just change it to import { server } from './server'; in index.test.ts and it will work as expected. You can also change the export to export default server in server.ts but I tend to avoid that based on this article.
Read more about export on MDN.
I have created a Typescript Express Server:
src/server.ts
import express from "express";
import { HomeController } from "./controllers";
const app: express.Application = express();
const port: number = ((process.env.PORT as any) as number) || 3000;
app.use(express.static("static"));
app.use("/", HomeController);
app.listen(port, () => {
// tslint:disable-next-line:no-console
console.log(`Listening at http://localhost:${port}/`);
});
src/controllers/index.ts
import { Request, Response, Router } from "express";
const router: Router = Router();
router.get("/", (req: Request, res: Response) => {
res.send("Hello World");
});
export const HomeController: Router = router;
structure
├───build
│ ├───common
│ ├───components
│ └───controllers
├───src
│ ├───common
│ ├───components
│ └───controllers
└───static
└───images
I have tried hosting a static file. Ex. index.html. via res.send('index.html'); The file is rendered but I am unable to import the element using a script tag. As the error returned is Exports is not defined
src/components/card.ts
import { html, LitElement } from "lit-element";
class Card extends LitElement {
protected render() {
return html`
<img src="../../static/images/AS.png" />
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"card-element": Card;
}
}
customElements.define("card-element", Card);
I am using TSC to build my application. I manually copied my static folder into by build folder to use. Im not sure if there is an automatic way to copy this folder on build.
Is there something that I am doing wrong with my compiler that may be giving me the error Exports is not defined research says its something to do with CommonJs but I tried installing and the result didnt change
tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"lib": ["es6", "dom"],
"outDir": "./build",
"strict": true,
"moduleResolution": "node",
"typeRoots": ["./modules"],
"esModuleInterop": true,
"skipLibCheck": true
}
}
I resolved my problem by bundling my code using webpack. This allowed me to create an Index.html importing my bundle.js file
I have a troubles with express.js it's trying to use ssl3 but I didn't use it anywhere. I see next error then running the server:
(node:7920) [DEP0026] DeprecationWarning: util.print is deprecated.
Use console.log instead. Error: 4776:error:1408F10B:SSL
routines:ssl3_get_record:wrong version
number:openssl\ssl\record\ssl3_record.c:252:
The server.js file looks like this:
// import npm modules
import fs from 'fs';
import path from 'path';
import express from 'express';
import bodyParser from 'body-parser';
import cors from 'cors';
import winston from 'winston';
import compression from 'compression';
import expressWinston from 'express-winston';
import winstonPapertrail from 'winston-papertrail';
import jwt from 'express-jwt';
import http from 'http';
// import custom configuration and utilities
import config from './config';
import logger from './utils/logger';
import db from './utils/db';
import routes from './routes';
// initialize the api
const api = express();
// initialize middleware
api.use(cors());
api.use(compression());
api.use(bodyParser.urlencoded({ extended: true }));
api.use(bodyParser.json());
// ignore authentication on the following routes
api.use(
jwt({ secret: config.jwt.secret }).unless({
path: [
'/',
'/auth/signup',
'/auth/login',
'/auth/forgot-password',
'/auth/reset-password',
],
}),
);
// throw an error if a jwt is not passed in the request
api.use((err, req, res, next) => {
if (err.name === 'UnauthorizedError') {
res.status(401).send('Missing authentication credentials.');
}
});
// initialize our logger (in our case, winston + papertrail)
api.use(
expressWinston.logger({
transports: [
new winston.transports.Papertrail({
host: config.logger.host,
port: config.logger.port,
level: 'error',
}),
],
meta: true,
}),
);
// listen on the designated port found in the configuration
api.listen(config.server.port, err => {
if (err) {
logger.error(err);
process.exit(1);
}
// require the database library (which instantiates a connection to mongodb)
db();
// loop through all routes and dynamically require them – passing api
Object.entries(routes).forEach(([ key, route ]) => {
route(api);
});
// output the status of the api in the terminal
logger.info(`API is now running on port ${config.server.port} in ${config.env} mode`);
});
export default api;
├───dist
└───src
├───config
├───controllers
├───models
├───routes
└───utils
How can I solve this problem? I wont use ssl right now. Thanks
I have set up an express application using typescript classes and have run into a strange issue. All tests have been passing, and today when I went to update some of the routes, my tests no longer run. When I run my test script, I get this error message back in the console:
$ mocha -c --reporter spec --compilers ts:ts-node/register ./test/*.test.ts --
timeout 20000
/Users/christiantodd/Development/projects/bby-react-api/src/index.ts:8
const app: Api = new Api();
^
TypeError: Api_1.default is not a constructor
at Object.<anonymous> (/Users/christiantodd/Development/projects/bby-react-api/src/index.ts:8:18)
at Module._compile (module.js:635:30)
at Module.m._compile (/Users/christiantodd/Development/projects/bby-react-api/node_modules/ts-node/src/index.ts:392:23)
at Module._extensions..js (module.js:646:10)
at Object.require.extensions.(anonymous function) [as .ts] (/Users/christiantodd/Development/projects/bby-react-api/node_modules/ts-node/src/index.ts:395:12)
My Api.ts file looks as follows:
import * as bodyParser from 'body-parser';
import * as express from 'express';
import * as expressValidator from 'express-validator';
import * as helmet from 'helmet';
import * as morgan from 'morgan';
import * as passport from 'passport';
import * as compression from 'compression';
/* import all routers */
import BestBuyRouter from './routes/BestBuyRouter';
import UserRouter from './routes/UserRouter';
export default class Api {
/* reference to the express instance */
public express: express.Application;
/* create the express instance and attach app level middleware and routes */
constructor() {
this.express = express();
this.middleware();
this.routes();
}
/* get current environment */
public currentEnv(): string {
return this.express.get('env');
}
/* apply middleware */
private middleware(): void {
this.express.use((req, res, next) => {
/* Don't allow caching. Needed for IE support :/ */
res.header('Cache-Control', 'no-cache, no-store, must-revalidate');
res.header('Pragma', 'no-cache');
res.header('Access-Control-Allow-Origin', '*');
res.header(
'Access-Control-Allow-Methods',
'PUT, GET, POST, DELETE, OPTIONS'
);
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept, Authorization, Access-Control-Allow-Credentials'
);
res.header('Access-Control-Allow-Credentials', 'true');
next();
});
this.express.use(compression());
this.express.use(helmet());
this.express.use(morgan('dev'));
this.express.use(bodyParser.json());
this.express.use(bodyParser.urlencoded({ extended: false }));
this.express.use(passport.initialize());
this.express.use(expressValidator());
this.express.use((err, req, res, next) => {
console.error(err);
res.status(err.status || 500).json({
message: err.message,
error: err
});
});
}
/* connect resource routers */
private routes(): void {
/* create an instance of the each of our routers */
const userRouter = new UserRouter();
const bestBuyRouter = new BestBuyRouter();
/* attach all routers to our express app */
this.express.use(userRouter.path, userRouter.router);
this.express.use(bestBuyRouter.path, bestBuyRouter.router);
}
}
and my index.ts:
import Api from './Api';
require('dotenv').config();
const mongoose = require('mongoose');
/* Set mongoose promise to native ES6 promise */
mongoose.Promise = global.Promise;
/* Instantiate our app instance */
const app: Api = new Api();
const connectOptions = {
useMongoClient: true,
keepAlive: true,
reconnectTries: Number.MAX_VALUE
};
/* Get current environment */
export const ENV = app.currentEnv();
let DATABASE_URL;
let PORT;
/* set environment variables */
if (ENV === 'production') {
DATABASE_URL = process.env.MONGODB_URI;
PORT = parseInt(process.env.PORT, 10);
} else {
DATABASE_URL = process.env.TEST_DATABASE_URL;
PORT = 3000;
}
let server;
export const runServer = async (
dbURL: string = DATABASE_URL,
port: number = PORT
) => {
try {
await mongoose.connect(dbURL, connectOptions);
await new Promise((resolve, reject) => {
server = app.express
.listen(port, () => {
console.info(`The ${ENV} server is listening on port ${port} 🤔`);
resolve();
})
.on('error', err => {
mongoose.disconnect();
reject(err);
});
});
} catch (err) {
console.error(err);
}
};
export const closeServer = async () => {
try {
await mongoose.disconnect();
await new Promise((resolve, reject) => {
console.info(`Closing server. Goodbye old friend.`);
server.close(err => (err ? reject(err) : resolve()));
});
} catch (err) {
console.error(err);
}
};
require.main === module && runServer().catch(err => console.error(err));
Lastly, my tsconfig.json
{
"compilerOptions": {
"lib": ["dom", "es7"],
"allowJs": true,
"watch": true,
"noImplicitAny": false,
"removeComments": true,
"sourceMap": false,
"target": "es6",
"module": "commonjs",
"outDir": "./lib",
"types": [
"body-parser",
"mongodb",
"mongoose",
"passport",
"node",
"nodemailer",
"mocha",
"chai",
"express",
"express-validator",
"chai-http"
],
"typeRoots": ["./node_modules/#types"]
},
"compileOnSave": true,
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "**/*.test.ts"]
}
To me it's really strange to get this behavior all of the sudden when this config has worked for me just fine in the past. I can still start my server just fine, but for some reason ts-node doesn't want to compile my *test.ts files for mocha to run my tests. Any idea what this could be?
Debugging "is not a ..." errors
So this is likely NOT going to be the answer but this question was the top result for the error I was debugging and here is a debugging tip.
I was running mocha with the following:
test/mocha.opts
--require ts-node/register
--require source-map-support/register
--watch-extensions ts
But no matter how I imported ./app I could not get classes or functions to work despite tsc compiling fine and node and mocha working fine on the compiled .js files.
import * as app from './app'
console.log({app}); // Pretty print object
As is would happen I had a Heroku project with app.json and app.ts.
The combination of mocha and ts-node were loading the .json file extension as a higher priority instead of the .ts file and Typescript won't allow me to specify a file extension. So this behaviour is different in tsc vs mocha + ts-node.
Bonus Points - Typescript Code Coverage
Unit tests: nyc mocha src/**/*-test.ts
Integration tests: nyc mocha test/**/*.ts
package.json
{
"nyc": {
"extension": [
".ts"
],
"include": [
"src/**/*.ts"
],
"exclude": [
"src/**/*-test.ts",
"test/**/*.ts"
],
"require": [
"ts-node/register"
],
"reporter": [
"text-summary"
],
"sourceMap": true,
"instrument": true,
"all": true
}
}
tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"target":"es2017",
"esModuleInterop":true,
// "strict": true,
"sourceMap": true,
"outDir": "dist",
"baseUrl": ".",
"types":["node"],
"rootDirs":[
"src"
]
},
"include": [
"src/**/*",
"test/**/*"
],
"exclude":[
"**/node_modules/**"
]
}