Is it possible to run Mongoose inside next.js api?

12,809

Solution 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

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

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.

Share:
12,809
Admin
Author by

Admin

Updated on June 05, 2022

Comments

  • Admin
    Admin almost 2 years

    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!