'Is it possible to run Mongoose inside next.js api?
I'm building a website for my sister so that she can sell her art. I am using Next.js to set everything up. The website renders the artwork by grabbing an array from a database and mapping through it.
Two Example objects
{
id: 8,
path: "images/IMG_0008.jpg",
size: "9x12x.75",
price: "55",
sold: false
}
{
id: 9,
path: "images/IMG_0009.jpg",
size: "9x12x.75",
price: "55",
sold: false
}
pages/Shop.js
import Card from "../Components/Card";
import fetch from 'node-fetch'
import Layout from "../components/Layout";
function createCard(work) {
return (
<Card
key={work.id}
id={work.id}
path={work.path}
size={work.size}
price={work.price}
sold={work.sold}
/>
);
}
export default function Shop({artwork}) {
return (
<Layout title="Shop">
<p>This is the Shop page</p>
{artwork.map(createCard)}
</Layout>
);
}
export async function getStaticProps() {
const res = await fetch('http://localhost:3000/api/get-artwork')
const artwork = await res.json()
return {
props: {
artwork,
},
}
}
The problem I am running into is that when I try to use mongoose in the api/get-artwork. It will only render the page once and once it is refreshed it will break I believe do to the fact the Schema and Model get redone.
pages/api/get-artwork.js/
const mongoose = require("mongoose");
mongoose.connect('mongodb://localhost:27017/ArtDB', {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false
});
const itemsSchema = {
id: String,
description: String,
path: String,
size: String,
price: Number,
sold: Boolean
};
const Art = mongoose.model("Art", itemsSchema);
export default (req, res) => {
Art.find({sold: false}, (err, foundItems)=> {
if (err) {
console.log(err);
} else {
console.log(foundItems);
res.status(200).json(foundItems);
}
});
};
So to try to fix this I decided to use the native MongoDB driver. Like this.
/pages/api/get-artwork/
const MongoClient = require('mongodb').MongoClient;
const assert = require('assert');
// Connection URL
const url = 'mongodb://localhost:27017';
// Database Name
const dbName = 'ArtDB';
// Create a new MongoClient
const client = new MongoClient(url, {useUnifiedTopology: true});
let foundDocuments = ["Please Refresh"];
const findDocuments = function(db, callback) {
// Get the documents collection
const collection = db.collection('arts');
// Find some documents
collection.find({}).toArray(function(err, arts) {
assert.equal(err, null);
foundDocuments = arts;
callback(arts);
});
}
// Use connect method to connect to the Server
client.connect(function(err) {
assert.equal(null, err);
const db = client.db(dbName);
findDocuments(db, function() {
client.close();
});
});
export default (req, res) => {
res.send(foundDocuments);
};
This works for the most part but occasionally the array will not be returned. I think this is because the page is loading before the mongodb part finishes? So I guess my question is how do I make 100% sure that it loads the art correctly every time whether that be using mongoose or the native driver.
Thanks!
Solution 1:[1]
The Next.js team has a good set of example code, which they add to regularly, one of them being Next.js with MongoDB and Mongoose. Check it out, https://github.com/vercel/next.js/tree/canary/examples/with-mongodb-mongoose, and hope this helps if you're still searching for solutions.
Solution 2:[2]
A little more complete answer might be helpful here. Here's what's working for us.
I would suggest using the next-connect library to make this a little easier and not so redundant.
Noticed unnecessary reconnects in dev so I bind to the global this property in Node. Perhaps this isn't required but that's what I've noticed. Likely tied to hot reloads during development.
This is a lengthy post but not nearly as complicated as it seems, comment if you have questions.
Create Middleware Helper:
import mongoose from 'mongoose';
// Get your connection string from .env.local
const MONGODB_CONN_STR = process.env.MONGODB_CONN_STR;
const databaseMiddleware = async (req, res, next) => {
try {
if (!global.mongoose) {
global.mongoose = await mongoose.connect(MONGODB_CONN_STR, {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false,
});
}
}
catch (ex) {
console.error(ex);
}
// You could extend the NextRequest interface
// with the mongoose instance as well if you wish.
// req.mongoose = global.mongoose;
return next();
};
export default databaseMiddleware;
Create Model:
Typically the path here might be src/models/app.ts
.
import mongoose, { Schema } from 'mongoose';
const MODEL_NAME = 'App';
const schema = new Schema({
name: String
});
const Model = mongoose.models[MODEL_NAME] || mongoose.model(MODEL_NAME, schema);
export default Model;
Implement Next Connect:
Typically I'll put this in a path like src/middleware/index.ts
(or index.js if not using Typescript).
Note: the ...middleware
here just allows you, see below, to pass in additional middleware on the fly when the handler here is created.
This is quite useful as our handler creator here can have things like logging and other useful middleware so it's not so redundant in each page/api file.
export function createHandler(...middleware) {
return nextConnect().use(databaseMiddleware, ...middleware);
}
Use in Api Route:
Putting it together, we can now use our App Model with ease
import createHandler from 'path/to/above/createHandler';
import App from 'path/to/above/model/app';
// again you can pass in middleware here
// maybe you have some permissions middleware???
const handler = createHandler();
handler.get(async (req, res) => {
// Do something with App
const apps = await App.find().exec();
res.json(apps);
});
export default handler;
Solution 3:[3]
In pages/api/get-artwork.js/
const mongoose = require("mongoose");
mongoose.connect("mongodb://localhost:27017/ArtDB", {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false,
});
const itemsSchema = {
id: String,
description: String,
path: String,
size: String,
price: Number,
sold: Boolean,
};
let Art;
try {
// Trying to get the existing model to avoid OverwriteModelError
Art = mongoose.model("Art");
} catch {
Art = mongoose.model("Art", itemsSchema);
}
export default (req, res) => {
Art.find({ sold: false }, (err, foundItems) => {
if (err) {
console.log(err);
} else {
console.log(foundItems);
res.status(200).json(foundItems);
}
});
};
It works just fine for me.
Solution 4:[4]
A concise OO approach
Inspired by @Blujedis answer this is what I ended up with.
/* /api/util/handler.js */
import nextConnect from "next-connect";
import mongoose from "mongoose";
export class Handler {
static dbConnection = null
static dbMiddleware = async (req, res, next) => {
try {
Handler.dbConnection ||= await mongoose.connect(process.env.MONGO_URL)
next()
} catch (err) {
console.error(err);
next(err)
}
}
constructor(...middleware) {
return nextConnect().use(Handler.dbMiddleware, ...middleware);
}
}
Example handler:
/* /api/people.js */
import { Handler } from "./util/handler"
import { Person } from "./models/person"
export default
new Handler()
.get(async (req, res) => {
const people = await Person.find({})
return res.status(200).json(people)
})
Solution 5:[5]
When we use mongoose with express.js
, we connect to mongodb and run the code once. But in next.js everytime we need to connect to mongodb, we have to run connection code.
Connection to mongodb
import mongoose from "mongoose";
const dbConnect = () => {
if (mongoose.connection.readyState >= 1) {
// if it is not ready yet return
return;
}
mongoose
.connect(process.env.DB_LOCAL_URI, {
//****** since mongoose 6, we dont need those******
// useNewUrlParser: true,
// useUnifiedTopology: true,
// useFindAndModify: false,
// useCreateIndex: true,
})
.catch((err) => console.log(err))
.then((con) => console.log("connected to db"));
};
export default dbConnect;
Use it inside next api
Before running handler code, you need to connect first
import dbConnect from "../../../config/dbConnect";
dbConnect();
... then write handler code
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Manny |
Solution 2 | Blujedis |
Solution 3 | |
Solution 4 | Luis Antonio Canettoli Ordoñez |
Solution 5 | Yilmaz |