DEV Community

Cover image for GraphQL Server Using NEXT.js and MongoDB Atlas
Joshua Pozos
Joshua Pozos

Posted on

GraphQL Server Using NEXT.js and MongoDB Atlas

First, sorry if something is not well written, I am still learning English

Add the following dependencies in your project

yarn add apollo-server-micro@2.25.1 mongoose graphql
Enter fullscreen mode Exit fullscreen mode

I used this version of apollo-server-micro because the new version 3 sends you to a sandbox that in my opinion is uncomfortable.

In the root of your project create a folder with the name you prefer, in this case I use "db". Inside create two more folders, one called "config" and the other "models"

mkdir db && cd db && mkdir config && mkdir models
Enter fullscreen mode Exit fullscreen mode

Alt Text

Config

Inside the config folder create an index.js file and add "moongose". For the MongoDb uri it is good practice to add it inside an .env file and bring it with proccess.env.

const mongoose = require('mongoose')

const MongoDb = process.env.MONGODB_URI

const connectDb = async () => {
  try {
    await mongoose.connect(MongoDb, {
      useNewUrlParser: true,
      useUnifiedTopology: true,
      useFindAndModify: false,
      useCreateIndex: true,
    })
    console.log('db success connect')
  } catch (err) {
    console.log('error connecting to database')
    console.log(err)
    process.exit(1)
  }
}

module.exports = connectDb
Enter fullscreen mode Exit fullscreen mode

Models

Now we are going to create an example model, in this case the product model. So inside the "models" folder I create a file called product.js and add moongose and what I need for the model.

import mongoose from 'mongoose'

const { Schema } = mongoose

mongoose.Promise = global.Promise

const ProductsSchema = new Schema({
  name: {
    type: String,
    required: true,
    trim: true,
  },
  productionCapacity: {
    type: Number,
    required: true,
    trim: true,
  },
  price: {
    type: Number,
    required: true,
    trim: true,
  },
  description: {
    type: String,
    trim: true,
  },
  createAt: {
    type: Date,
    defalut: Date.now(),
  },
})

ProductsSchema.index({ name: 'text' })

module.exports =
  mongoose.models.Product || mongoose.model('Product', ProductsSchema)

Enter fullscreen mode Exit fullscreen mode

Schema and resolvers

I'll leave the code here as an example because your project can be totally different.

Schema

For the schema we will import gql from apollo-server-micro and create a constant called "typeDefs" in which we will use gql and inside we will define our types, inputs, queries and mutations

import { gql } from 'apollo-server-micro'

const typeDefs = gql`
  # Products
  type Product {
    id: ID
    name: String
    productionCapacity: Int
    price: Float
    description: String
  }

  input ProductInput {
    name: String!
    productionCapacity: Int!
    price: Float!
    description: String
  }

  type Query {
    getProducts: [Product]
    getProduct(id: ID!): Product
  }

  type Mutation {
    #Products
    newProduct(input: ProductInput): Product
    updateProduct(id: ID!, input: ProductInput): Product
    deleteProduct(id: ID!): String
  }
`

module.exports = typeDefs

Enter fullscreen mode Exit fullscreen mode

Resolvers

I will leave the code that I use, but remember that your project can be very different. We bring the models, in this case I bring my "product" model to be able to use it within the queries or mutations that I define.

const Product = require('./models/product')

const resolvers = {
  Query: {
    // products
    getProducts: async () => {
      try {
        const products = await Product.find({})

        return products
      } catch (err) {
        console.log(err)
      }
    },
    getProduct: async (_, { id }) => {
      const product = await Product.findById(id)

      if (!product) {
        throw new Error('Product not found')
      }

      return product
    },
  },

  Mutation: {
    // products
    newProduct: async (_, { input }) => {
      try {
        const product = new Product(input)

        const result = await product.save()

        return result
      } catch (err) {
        console.log(err)
      }
    },
    updateProduct: async (_, { id, input }) => {
      let product = await Product.findById(id)

      if (!product) {
        throw new Error('Product not found')
      }

      product = await Product.findOneAndUpdate({ _id: id }, input, {
        new: true,
      })

      return product
    },
    deleteProduct: async (_, { id }) => {
      const product = await Product.findById(id)

      if (!product) {
        throw new Error('Producto no encontrado')
      }

      await Product.findOneAndDelete({ _id: id })

      return 'Producto eliminado'
    },
  },
}

module.exports = resolvers

Enter fullscreen mode Exit fullscreen mode

Creating the endpoint

First we create a graphql.js file (it can be called whatever) inside /pages/api.
In the graphql.js file we are going to import ApolloServer and makeExecutableSchema from apollo-server-micro and typeDefs, resolvers and connectDb from their corresponding files. Then we run the connectDb() function and create a schema with makeExecutableSchema which will have the typeDefs and the resolvers.
And at the end we export the Apollo Server passing it our schema variable and defining the path that must match the name of the file that we put in /pages/api.

import { ApolloServer, makeExecutableSchema } from 'apollo-server-micro'
import typeDefs from '../../db/schema'
import resolvers from '../../db/resolvers'
import connectDb from '../../db/config'

connectDb()

export const schema = makeExecutableSchema({
  typeDefs,
  resolvers
})

export const config = {
  api: {
    bodyParser: false,
  },
}

export default new ApolloServer({ schema }).createHandler({
  path: '/api/graphql',
})
Enter fullscreen mode Exit fullscreen mode

Your project should have been more or less like this

Alt Text

Now run the project and go to the url of your api with graphql

yarn dev
Enter fullscreen mode Exit fullscreen mode

Alt Text

I leave the package.json for you to check the version of the dependencies used in this post.

{
  "name": "graphql-nextjs",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "apollo-server-micro": "2.25.1",
    "graphql": "^15.5.1",
    "mongoose": "^5.13.5",
    "next": "11.0.1",
    "react": "17.0.2",
    "react-dom": "17.0.2"
  },
  "devDependencies": {
    "eslint": "7.32.0",
    "eslint-config-next": "11.0.1"
  }
}
Enter fullscreen mode Exit fullscreen mode

Repo

GraphQL Server Using NEXT.js and MongoDB Atlas

Setup

yarn install

Create ur own MONGODB_URI into .env file (.env.development or .env.local) and add ur mongodb uri.

Ready

Modify all u need to ur project

Development mode

yarn dev

Top comments (6)

Collapse
 
nosovandrew profile image
Andrew Nosov

Very good article, thanks!🙏 I've tried to repeat this approach with my Next.JS app, but I got the error with ApolloServer in api/graphql.js file (need to await start() before createHandler()). I know that Next.JS runs it's own server and start() func isn't needed. What's the problem?

Note: I'm using apollo-server-micro version 3.*, could this be the reason?

Collapse
 
alvaroaquijediaz profile image
imalvaroad

just try to store the start in a constant like const startTheServer = server.start where server is your ApolloServer instance, and then inside the function that you'll export just do await startTheServer

Collapse
 
nosovandrew profile image
Andrew Nosov

I've checked my version assumption and got expected result. For Apollo-server-micro v2.* all works fine, but v3.* requires server.start() before createHandler.

I found worked example there

Collapse
 
joshuapozos profile image
Joshua Pozos

So cool! Thanks for sharing.

Collapse
 
gonzavic profile image
Victor Gonzalez

Super useful post! 🔥

Collapse
 
vashisth00 profile image
Vashisth Bhushan

Can someone add mutation here to add products to mongo db