Now Mongoose Cannot overwrite model once compiled - mongodb

I'm using MongoDB Atlas to host the DB and just querying data with this severless function:
import { NextApiRequest, NextApiResponse } from "next";
// models
import { Section } from "../../../models/Section";
// db connection
import { useDatabase } from "../../../middleware/useDatabase";
async function find(req: NextApiRequest, res: NextApiResponse) {
const { content } = req.query;
const response = await Section.find({ contentType: content });
res.send(response);
}
export default useDatabase(find);
The middleware useDatabase is configured like this:
import * as mongoose from "mongoose";
import { NextApiRequest, NextApiResponse } from "next";
export const useDatabase = handler => async (req: NextApiRequest, res: NextApiResponse) => {
if (mongoose.connections[0].readyState) return handler(req, res);
// Using new database connection
await mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useFindAndModify: false,
useUnifiedTopology: true
});
return handler(req, res);
}
The first time I execute the query doing a fetch to the api, works well, then if I edit some code, save it and now dev recompile the code, I got this error when I do the query again:
MongooseError [OverwriteModelError]: Cannot overwrite `Section` model once compiled.
at new OverwriteModelError (/home/andres/Documents/shooter-app/node_modules/mongoose/lib/error/overwriteModel.js:20:11)
at Mongoose.model (/home/andres/Documents/shooter-app/node_modules/mongoose/lib/index.js:521:13)
at Module../models/Section.ts (/home/andres/Documents/shooter-app/.next/server/static/development/pages/api/sections/[content].js:154:63)
at __webpack_require__ (/home/andres/Documents/shooter-app/.next/server/static/development/pages/api/sections/[content].js:23:31)
at Module../pages/api/sections/[content].ts (/home/andres/Documents/shooter-app/.next/server/static/development/pages/api/sections/[content].js:198:73)
at __webpack_require__ (/home/andres/Documents/shooter-app/.next/server/static/development/pages/api/sections/[content].js:23:31)
at Object.3 (/home/andres/Documents/shooter-app/.next/server/static/development/pages/api/sections/[content].js:233:18)
at __webpack_require__ (/home/andres/Documents/shooter-app/.next/server/static/development/pages/api/sections/[content].js:23:31)
at /home/andres/Documents/shooter-app/.next/server/static/development/pages/api/sections/[content].js:91:18
at Object.<anonymous> (/home/andres/Documents/shooter-app/.next/server/static/development/pages/api/sections/[content].js:94:10)
at Module._compile (internal/modules/cjs/loader.js:955:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:991:10)
at Module.load (internal/modules/cjs/loader.js:811:32)
at Function.Module._load (internal/modules/cjs/loader.js:723:14)
at Module.require (internal/modules/cjs/loader.js:848:19)
at require (internal/modules/cjs/helpers.js:74:18)
at DevServer.handleApiRequest (/home/andres/Documents/shooter-app/node_modules/next/dist/next-server/server/next-server.js:420:28)
at async Object.fn (/home/andres/Documents/shooter-app/node_modules/next/dist/next-server/server/next-server.js:350:37)
at async Router.execute (/home/andres/Documents/shooter-app/node_modules/next/dist/next-server/server/router.js:42:32)
at async DevServer.run (/home/andres/Documents/shooter-app/node_modules/next/dist/next-server/server/next-server.js:468:29) {
message: 'Cannot overwrite `Section` model once compiled.',
name: 'OverwriteModelError'
}
How can I solve it? Thank you!

This happens when the development server tries to recompile your model during hot module replacement.
You can prevent this from happening by changing your code to check whether the model has already been compiled when exporting it.
export default mongoose.models['Section'] || mongoose.model('Section', SectionSchema);

Related

Minimal Sveltekit + pg integration fails with "status" error

I'm trying to get Postgres working with sveltekit and a very minimal example is giving me issues. This is probably a configuration thing but the error I'm getting back from sveltekit makes no sense to me.
I start by installing a new project:
npm create svelte#latest my-testapp
Then I install "pg" to get Postgres pooling:
npm i pg
Then I add a page under src/lib/db.js:
import { Client, Pool } from 'pg';
const pool = new Pool({
user: 'xxx',
host: 'xxx',
database: 'xxx',
password: 'xxx',
port: 5432,
})
export const connectToDB = async () => await pool.connect();
Finally I add src/hooks.server.js to give me access to the pool within routes:
import { connectToDB } from '$lib/db';
export const handle = async ({event, resolve}) => {
const dbconn = await connectToDB();
event.locals = { dbconn };
const response = await resolve(event);
dbconn.release();
}
The server fails to compile with a couple of these errors:
Cannot read properties of undefined (reading 'status')
TypeError: Cannot read properties of undefined (reading 'status')
at respond (file:///C:/Users/user/code/svelte/my-testapp/node_modules/#sveltejs/kit/src/runtime/server/index.js:314:16)
at async file:///C:/Users/user/code/svelte/my-testapp/node_modules/#sveltejs/kit/src/exports/vite/dev/index.js:406:22
Not sure where "status" is coming from, seems to be part of the initial scaffolding. Any help appreciated.
Also - if there is a more straightforward way to integrate pg with sveltekit then I'm happy to hear about it. Thanks
My bad - the hooks function wasn't returning the response.
Hooks.server.js should read:
import { connectToDB } from '$lib/db';
export const handle = async ({event, resolve}) => {
const dbconn = await connectToDB();
event.locals = { dbconn };
const response = await resolve(event);
dbconn.release();
return response
}

MongoDB Atlas connections to cluster(s) exceeded- NextJS + NextAuth

I was creating a website using NextJS and for authentication uses NextAuth. And the database is on free tier in MongoDB Atlas.
I have two versions of code for database connection. One is this:
/**
* MongoDB Connection
*
* */
import mongoose from 'mongoose'
const MONGODB_URI = process.env.MONGODB_URL
if (! process.env.MONGODB_URL) {
throw new Error(
'Please define the MONGODB_URI environment variable inside .env.local'
)
}
/**
* Global is used here to maintain a cached connection across hot reloads
* in development. This prevents connections growing exponentially
* during API Route usage.
*/
let cached = global.mongoose
if (!cached) {
cached = global.mongoose = { conn: null, promise: null }
}
async function dbConnect() {
if (cached.conn) {
return cached.conn
}
if (!cached.promise) {
const opts = {
useNewUrlParser: true,
useUnifiedTopology: true,
bufferCommands: false,
// bufferMaxEntries: 0,
// useFindAndModify: false,
// useCreateIndex: true,
}
cached.promise = mongoose.connect(process.env.MONGODB_URL, opts).then((mongoose) => {
return mongoose
})
}
cached.conn = await cached.promise
return cached.conn
}
export default dbConnect
So, before making any DB related queries via code, I call await dbConnect(). It is working fine.
But for storing the sessions in DB, in NextAuth, I was not able to use the above function. So for that, am using this custom code (/lib/mongodb.js):
/**
*
* Used only for Next-Auth
*
*/
import { MongoClient } from "mongodb"
const uri = process.env.MONGODB_URL
const options = {
useUnifiedTopology: true,
useNewUrlParser: true,
}
let client
let clientPromise
if (!process.env.MONGODB_URL) {
throw new Error("Please add your Mongo URI to .env.local")
}
if (process.env.NODE_ENV === "development") {
// In development mode, use a global variable so that the value
// is preserved across module reloads caused by HMR (Hot Module Replacement).
if (!global._mongoClientPromise) {
client = new MongoClient(uri, options)
global._mongoClientPromise = client.connect()
}
clientPromise = global._mongoClientPromise
} else {
// In production mode, it's best to not use a global variable.
client = new MongoClient(uri, options)
clientPromise = client.connect()
}
// Export a module-scoped MongoClient promise. By doing this in a
// separate module, the client can be shared across functions.
export default clientPromise
And the code in my /pages/api/auth/[...nextauth].js is like this:
import NextAuth from 'next-auth'
import { MongoDBAdapter } from "#next-auth/mongodb-adapter"
import mongodb from '../../../lib/mongodb'
//...
export default async function auth(req, res) {
return await NextAuth(req, res, {
//....
adapter: MongoDBAdapter({
db: (await mongodb).db("my_db_name_here")
}),
//....
})
}
Here's the packages am using:
"mongodb": "^4.1.2",
"mongoose": "^6.0.1",
"next": "11.0.1",
"next-auth": "^4.0.0-beta.6",
"react": "17.0.2",
"react-dom": "17.0.2",
The problem is, am receiving email notifications sometimes like the following:
My website is still in testing phase(tested by two persons only) and is hosting in Vercel server. I believe this could be because NextAuth is creating new db connections each time? Any thoughts on what went wrong?
clientPromise in next-auth is local, you create new client and 5 connections every time. Just use global.mongoose.conn.
The docs for MongoDBAdapter says it needs a promise that resolves to a client, so it must be something like this:
export default NextAuth({
adapter: MongoDBAdapter(dbConnect().then(mon => mon.connection.getClient())),
...
})
In you case you seem to use db. I couldn't find any references for MongoDBAdapter to accept something liek {db: ...} but you can get the db from mongoose as following:
await (dbConnect().then(mon => mon.connection.getClient().db("my_db_name_here")))
Or without parameter to use the same database as configured in mongoose connection.
UPDATE
The issue with number of connections from Vercel is covered in Vercel creates new DB connection for every request

How to initialize Mongodb in NextJS startup

As you know NextJS is Jamstack framework and I'm migrating from node/express to it but my problem is how to connect server to database at startup of server as i did in express?
there is now where to put my initalizing code in NextJS? Am I saying correct?
I saw some code to to that but there were typescript codes that im not familiar with them
On the other hand i'm able to do that on serverside functions like getStaticProps or getServerSideProps this is my code
dbinit.js
import mongoose from "mongoose";
export const dbStatus = () => mongoose.connection.readyState;
export default function dbConnect() {
if (dbStatus == 1) return "database is connected";
mongoose.connect(
`mongodb://localhost:${process.env.DBPORT}/${process.env.DBNAME}`
);
}
index.js
export async function getServerSideProps() {
const result = await dbConnect();
console.log(dbStatus());
return {
props: {},
};
}
with this code im able to connect to mongodb but there are some problems and the most important is that mycode isnot cleancode
In NextJS, we can connect MongoDB as middleware. This is very similar to the Express middleware approach as shown below.
// middleware/database.js
import { MongoClient } from 'mongodb';
import nextConnect from 'next-connect';
const client = new MongoClient('{YOUR-MONGODB-CONNECTION-STRING}', {
useNewUrlParser: true,
useUnifiedTopology: true,
});
async function database(req, res, next) {
if (!client.isConnected()) await client.connect();
req.dbClient = client;
req.db = client.db('MCT');
return next();
}
const middleware = nextConnect();
middleware.use(database);
export default middleware;
For more details, you can refer to this official how-to doc for step-by-step guidance. Here is the example repository used.

Mongoose TypeError 'cannot read namespace' of undefined in collection.js in Mongoose module

When I run my application using NPM it runs without issue. When running it with my VSC debugger I receive the following error on two occasions in collection.js located in node_modules/mongodb/lib/collection.js:
TypeError: Cannot read property 'namespace' of undefined
at Object.get [as dbName] (/xxxx/xxxx/xxxx/xxxx/node_modules/mongodb/lib/collection.js:184:19)
at Object.<anonymous> (/xxxx/xxxx/xxxx/xxxx/node_modules/mongoose/lib/drivers/node-mongodb-native/collection.js:184:36)
at Module._compile (internal/modules/cjs/loader.js:689:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
at Module.load (internal/modules/cjs/loader.js:599:32)
at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
at Function.Module._load (internal/modules/cjs/loader.js:530:3)
at Module.require (internal/modules/cjs/loader.js:637:17)
at require (internal/modules/cjs/helpers.js:22:18)
at Object.<anonymous> (/xxxx/xxxx/xxxx/xxxx/node_modules/mongoose/lib/drivers/node-mongodb-native/index.js:8:22)
Instance 1 picked up here:
Object.defineProperty(Collection.prototype, 'dbName', {
enumerable: true,
get: function() {
return this.s.namespace.db;
}
});
Instance 2 picked up here:
Object.defineProperty(Collection.prototype, 'collectionName', {
enumerable: true,
get: function() {
return this.s.namespace.collection;
}
});
My connection configuration located in app.js looks like this (it works when running the application using NPM without any errors):
const dbUser = process.env.xxxxx;
const dbPass = process.env.xxxxx;
const dbURL = process.env.xxxxx;
const dbName = process.env.xxxxxx;
mongoose.set('useCreateIndex', true);
mongoose.connect(`mongodb+srv://${dbUser}:${dbPass}#${dbURL}/${dbName}`, {useNewUrlParser: true , useUnifiedTopology: true} )
.then(_ => console.log('MongoDB successfully connected!'))
.catch(err => console.error(err));
The only Mongoose model in this application that loads Mongoose is as follows:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// UserSchema has been omitted for privacy
const UserSchema = new Schema({
});
module.exports = User = mongoose.model('User', UserSchema);
There are no other instances where the Mongoose module is loaded. I've also checked the options I'm passing in the mongoose.connect method and it doesn't appear to be them either. Additionally I checked values stored in the env file and they also don't appear to be causing the issue.
Thanks, any help greatly appreciated!
update nodemon , mongoose
npm i -g nodemon
npm i mongoose
if you are getting warnings for package dependencies e.g:
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents#1.2.11 (node_modules\chokidar\node_modules\fsevents):
=> npm i fsevents
than install the optional dependencies too.
Update mongoose v5.8.11. Today (01/31/2020) it has been released.
FYI -
https://github.com/Automattic/mongoose/issues/8528#issuecomment-578221253

Write test axios-mock-adapter with axios.create()

I want to test my http service but get error.
So, my test file
api.js
import axios from 'axios';
export const api = axios.create();
fetchUsers.js
import api from './api';
export const fetchUsers = (params) api.get('/api/users', { params })
.then(({data}) => data)
fetchUsers.spec.js
import MockAdapter from 'axios-mock-adapter'
import api from './api';
const mock = new MockAdapter(api);
describe('fetchUsers', () => {
it('should send request', (done) => {
const data = { data: ['user'] };
mock.onGet('/api/users').reply(200, data);
fetchUsers().then((response) => {
expect(response).toEqual(data.data);
done();
});
});
});
But I get error here
Error: connect ECONNREFUSED 127.0.0.1:80
at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1158:14)
If I replace in api.js axios.create() with axios its' working. But how to test with created axios instance? I'll need to ass there parameters when create it.
Anyone can help with that?
Hi I had the same issue and had to answer myself here https://stackoverflow.com/a/51414152/73323
Here is the gist:
First off, you don't need the axios-mock-adapter library.
Create a mock for axios in src/__mocks__:
// src/__mocks__/axios.ts
const mockAxios = jest.genMockFromModule('axios')
// this is the key to fix the axios.create() undefined error!
mockAxios.create = jest.fn(() => mockAxios)
export default mockAxios
Then in your test file, the gist would look like:
import mockAxios from 'axios'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
// for some reason i need this to fix reducer keys undefined errors..
jest.mock('../../store/rootStore.ts')
// you need the 'async'!
test('Retrieve transaction data based on a date range', async () => {
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
const store = mockStore()
const mockData = {
'data': 123
}
/**
* SETUP
* This is where you override the 'post' method of your mocked axios and return
* mocked data in an appropriate data structure-- {data: YOUR_DATA} -- which
* mirrors the actual API call, in this case, the 'reportGet'
*/
mockAxios.post.mockImplementationOnce(() =>
Promise.resolve({ data: mockData }),
)
const expectedActions = [
{ type: REQUEST_TRANSACTION_DATA },
{ type: RECEIVE_TRANSACTION_DATA, data: mockData },
]
// work
await store.dispatch(reportGet())
// assertions / expects
expect(store.getActions()).toEqual(expectedActions)
expect(mockAxios.post).toHaveBeenCalledTimes(1)
})