Automattic / mongoose

MongoDB object modeling designed to work in an asynchronous environment.
https://mongoosejs.com
MIT License
26.96k stars 3.84k forks source link

Error on mongoose.connect with angular universal #11155

Closed A-Ghattas closed 2 years ago

A-Ghattas commented 2 years ago

Do you want to request a feature or report a bug? A bug

What is the current behavior? A new empty Angular 13.1.0 Universal app. I added mongoose 6.1.3 When I add mongoose.connect('mongodb://username:password@127.0.0.1:27017/database') I get the error

A server error has occurred.
node exited with 1 code.
connect ECONNREFUSED 127.0.0.1:52098

127.0.0.1:52098 IS NOT the uri I am passing to the connect function. I tried with a hosted mongodb server to be sure of that.

If the current behavior is a bug, please provide the steps to reproduce.

In a new Angular universal app add the following code to server.ts:

import mongoose from 'mongoose';
mongoose.connect('mongodb://username:password@127.0.0.1:27017/database')
{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "noImplicitOverride": true,
    "noPropertyAccessFromIndexSignature": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "sourceMap": true,
    "declaration": false,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "moduleResolution": "node",
    "importHelpers": true,
    "target": "es2017",
    "module": "es2020",
    "lib": [
      "es2020",
      "dom"
    ],
    "allowSyntheticDefaultImports": true
  },
  "angularCompilerOptions": {
    "enableI18nLegacyMessageIdFormat": false,
    "strictInjectionParameters": true,
    "strictInputAccessModifiers": true,
    "strictTemplates": true
  }
}

What is the expected behavior? To connect to the mongo database

What are the versions of Node.js, Mongoose and MongoDB you are using? Note that "latest" is not a version. mongoose: 6.1.3 MongoDB: 5.0.4 Node.js: 16.13.1 (also tried with the new v17, same behavior)

IslandRhythms commented 2 years ago

use `mongodb://locahost:27017' as your connection uri

A-Ghattas commented 2 years ago

use `mongodb://locahost:27017' as your connection uri

That was not helpful at all but thank you. I did some digging and I removed a try catch and now I have this error:

const AsyncIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf( /*#__PURE__*/_wrapAsyncGenerator(function* () {})).prototype);
                                      ^

TypeError: Cannot convert undefined or null to object
    at Function.getPrototypeOf (<anonymous>)

and it's related to webpack.

Edit: Another important thing. I think this is a bug because when I downgrade the mongoose version to 5, everything works fine.

IslandRhythms commented 2 years ago

Can you provide the full script you are running?

A-Ghattas commented 2 years ago

Of course To reproduce you can make an empty angular universal project:

ng new project-name
cd project-name
ng add @nguniversal/express-engine
npm i mongoose
npm i @types/mongoose
// add mongoose.connect() to ./server.ts
// and run with:
npm run dev:ssr

This is the script I am running: ./server.ts

import '@angular/localize/init';
import 'zone.js/dist/zone-node';

import { ngExpressEngine } from '@nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';

import { AppServerModule } from './src/main.server';
import { APP_BASE_HREF } from '@angular/common';
import { existsSync } from 'fs';
import { connect } from './src/express/utils/database';

import * as swaggerUI from 'swagger-ui-express';
import { getSwaggerJson } from './src/express/utils/swagger-json-files';
import to from 'await-to-js';

import { config } from './src/environments/config';
import { router } from './src/express/router';
import bodyParser from 'body-parser';
import multer from 'multer';
import morgan from 'morgan';
import { Routes } from './src/models/routes.enum';

// The Express app is exported so that it can be used by serverless Functions.
export async function app(): Promise<express.Express> {
  const server = express();
  const distFolder = join(process.cwd(), 'dist/school-crow/browser');
  const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';

  // Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
  server.engine(
    'html',
    ngExpressEngine({
      bootstrap: AppServerModule,
    })
  );

  server.set('view engine', 'html');
  server.set('views', distFolder);

  // Example Express Rest API endpoints
  // server.get('/api/**', (req, res) => { });
  // Serve static files from /browser
  server.use(morgan('dev'));
  server.use(bodyParser.json());
  server.use(bodyParser.urlencoded({ extended: true }));
  server.use(<any>multer().single('file'));
  const [err, swaggerDocument] = await to(getSwaggerJson());
  if (err) console.error(err);
  server.use(
    Routes.ApiDocs,
    express.static('node_modules/swagger-ui-dist/', { index: false }),
    swaggerUI.serve,
    swaggerUI.setup(swaggerDocument)
  );
  server.use(Routes.Api, router);
  server.get(
    '*.*',
    express.static(distFolder, {
      maxAge: '1y',
    })
  );

  // All regular routes use the Universal engine
  server.get('*', (req, res) => {
    res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
  });

  return server;
}

function run(): void {
  const port = process.env['PORT'] || 4000;

  connect() // <=================== This is where I call the function that connect to mongoose
    .then(() => {
      // Start up the Node server
      app().then((server) => {
        server.listen(port, () => {
          console.log(`Node Express server listening on http://localhost:${port}`);
        });
      });
    })
    .catch((e) => console.error('Mongoose connect error', e));
}

// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
const moduleFilename = (mainModule && mainModule.filename) || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
  run();
}

export * from './src/main.server';

./src/express/utils/database

import mongoose, { ConnectOptions } from 'mongoose';
import { config } from '../../environments/config';

export const connect = (url = config.db.url, opts: ConnectOptions = {}): Promise<typeof mongoose> => {
  console.log('connecting to ' + url);
  return mongoose.connect(config.db.url, {
    ...opts,
    useNewUrlParser: true,
    useUnifiedTopology: true,
    useFindAndModify: false,
    useCreateIndex: true,
  });
};

If you need anything else let me know.

vkarpov15 commented 2 years ago

We'll take a look at this, but one suggestion you might try is disabling Zone.js as discussed here: https://github.com/Automattic/mongoose/issues/7838 . In the past, we've run into issues with Zone.js badly monkey-patching Node built-ins, that may be a potential cause.

vkarpov15 commented 2 years ago

I put in PRs to work around this issue in Angular and in the upstream dep that's causing this issue.

Regardless, I strongly recommend figuring out a way to avoid the "downleveling" that Angular does to force Node code to ES2015 for Zone.js support. I haven't been able to find a way, but I'm also not terribly familiar with Angular configs. Mongoose and several of its upstream deps, like mongodb@4.x and whatwg-url@11, require Node >= 12, and these libs don't test in environments where they're transpiled down to lower ECMAScript versions.