Nesting GraphQL with MongoDB
Tags: mongodb, node, graphql, database
Published: 2020-11-04 20:45:11

## Getting Started
*GraphQL*, *Apollo server* and *MongoDB* all connected on your app.
Dependencies to install
_devDependencies_ are optional, only for the sake of your convenience.

```javascript
// package.json
{
  "name": "server",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "nodemon --exec babel-node src/index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
  "apollo-server-express": "^2.19.0",
  "express": "^4.17.1",
  "graphql": "^15.4.0",
  "mongoose": "^5.10.11"
  },
  "devDependencies": {
    "@babel/cli": "^7.12.1",
    "@babel/core": "^7.12.3",
    "@babel/node": "^7.12.1",
    "@babel/preset-env": "^7.12.1",
    "nodemon": "^2.0.6"
  }
}
```


## How it works
There are three things to define to use graphQL and the logic might not be specifically applied to MongoDB + graphQL. The logic is simple.
1. Let MongoDB how your schemas look like
2. Let GraphQL how your schemas look like
3. Let Apollo Server how you are going to use these schemas


# Logic 1. Defining MongoDB Schema
We are making Transaction schema looking like this:

```javascript
Transaction {
  price
  method
  cardNumber
  paidTime
  items: [
    {
      amount
      quantity  
    }
  ]
}
```


We are going to use _mongoose_ as ORM for MongoDB. You just need to define its data type and any additional options if any. It is also very simple. You just define each schema and put them together. MongoDB Schema would look like this:

```javascript
import mongoose from 'mongoose';
const Schema = mongoose.Schema;
const itemSchema = new Schema({
  amount: { type: Number },
  quantity: { type: Number },
});

const transactionSchema = new Schema({
  price: { type: Number, required: true },
  method: { type: String, default: 'VISA', required: true },
  cardNumber: { type: String, required: true },
  paidTime: { type: Date, default: new Date(), required: true },
  items: [itemSchema],
});

export const Transaction = mongoose.model('Transaction', transactionSchema);
```

#### Break down
1. Import mongoose
2. Create a schema instance for item (itemSchema)
3. Create a schema instance for transaction (transactionSchema)
4. put itemSchema into items property of transactionSchema object

Notice _itemSchema_ is going to be part of _transactionSchema_ as an array.


# Logic 2. Defining TypeDefs
Let's create a type definition. We are going to use Apollo Server as middleware to handle graphQL. There are other middlewares such as graphql yoga, but Apollo Server is a standard.
_Query_ does things corresponding to *GET* request, _Mutation_ takes care of any other requests that cause mutation of data such as *POST*, *PUT* and *DELETE*. We are starting by _Mutation_, because we will push data first, and then fetch them to check if the data properly saved.


There are four type definitions I used in this tutorial:
*_type_ SchemaName {types}* : Defining schema type
*_input_ nameOfInput {types}* : Defining schema's input, used to type argument's type
*_type_ Query {types}*: Defining query structure matching to your resolver
*_type_ Mutation {types}*: Defining mutation structure matching to your resolver


```javascript
// typeDefs.js

import { gql } from 'apollo-server-express';

export const typeDefs = gql`
  scalar Date

// Defining your Query

// 1 Defining your graphql schema type
  type Item {
    id: ID!
    amount: Float
    quantity: Int
  }

  type Transaction {
    id: ID!
    price: Float!
    method: String!
    cardNumber: String!
    paidTime: Date!
    items: [Item]
  }

  // 2 Defining input type
  input ItemInput {
    transactionId: String!
    amount: Float
    quantity: Int
  }
 
  input TransactionInput {
    price: Float!
    method: String!
    cardNumber: String!
    items: [ItemInput]
  }

  // 3 Defining your Muation
  type Mutation {
    createTransaction(TransactionInput: TransactionInput!): Transaction
    createItem(ItemInput: ItemInput): Transaction

`;
```

Note:*!* mark means *this field is required*, notice *createItem* returns _Transaction_ schema

#### Break down
1. defined Schema of Item and Transaction
2. defined type of argument which going to be passed into Mutation function
3. defined two functions to create transaction and to create item.


# Logic 3. Defining how you are going to create or update api
Resolver is a function to handle populating your data into your database, you can define how you are going to fetch data and how you are going to update, create data. Since Apollo-server reads from schema from typeDefs, they will need to match how it is structured.
Basic Resolver Structure

```javascript
const resolver = {
  Query: {
    some async function 
  },

  Mutation: {
    some async function
  }
}
```

Let's create resolver file for function and you will need to pass it to the apollo server instance. In the _Mutation_ object, add code like so:

###### Transaction mutation (parent schema)

```javascript
import { Transaction } from '../models/transaction';
export const transactionResolver = {
  Mutation: {
    createTransaction: async (
      _, { TransactionInput: { price, method, cardNumber } }
    ) => {

      const newtransaction = new Transaction({
        price,
        method,
        cardNumber,
      });

      await transaction.save();
      
      return newtransaction;
    },
  },
}
```

![1_HAV3inx1W2kKT2A9WBKWfg](https://dev-to-uploads.s3.amazonaws.com/i/gbosx31vnwehkkyv4bsi.png)

Firstly, createTransaction is an async function, takes a few arguments but *we only care about the second argument* which is what we are going to push to the database and its type.
Secondly, create a Transaction instance imported from mongoose model with *input arguments* (price, method, cardNumber) then save the instance using mongoose.
Finally, return the instance.
Item resolver (child schema)


```javascript
import { Transaction } from '../models/transaction';
export const itemResolver = {
  Mutation: {
    createItem: async (
      -, {ItemInput: {transactionId, amount, quantity} }
    ) => {
      // find the transaction by id
      const transaction = await Transaction.findById(transactionId);
    
      // check if the transaction exists
      if (transaction) {
         // if exists, push datas into items of transaction
         transaction.items.unshift({
           amount,
           quantity
         });
      } else throw new Error('transaction does not exist');
```


###### Create transaction
Now let's create a transaction! You can open up graphql testing playground at your _localserver/graphql_

![1_JmocGsZYL3icZVf047D61Q](https://dev-to-uploads.s3.amazonaws.com/i/v3sejs37pu05kf8cy65f.png)


```
mutation {
   functionName(input arguments) { 
     data you want to return, you can be selective
   }
}
```


###### Create Items
You can push items into a transaction that you selected with id.

![1_tfTyl7J56SqE6QwmmX9LHw](https://dev-to-uploads.s3.amazonaws.com/i/hreo38ljl2uvvf671avm.png)

Remember, the transactionId should exist in your database.


# Fetching query


```javascript
// typeDefs.js
import { gql } from 'apollo-server-express';
export const typeDefs = gql`
  scalar Date
// Defining your Query
  type Query {
    transactions: [Transaction!]!
  }
// Defining your graphql schema type
  type Item {
    id: ID!
    amount: Float
    quantity: Int
  }
 
  type Transaction {
    id: ID!
    price: Float!
    method: String!
    cardNumber: String!
    paidTime: Date!
    items: [Item]
  }
`;
```


### Type definition of Item Shcema
Type definition of Transaction Schema (notice Item type definition is nested in Transaction type definition in items field)
Create Query type that fetches an array of transaction


### Transaction Resolver

```javascript
import { Transaction } from '../models/transaction';
export const transactionResolver = {
  Query: {
    transactions: async () => {
      try {
        const transactions = await Transaction.find()
        return transactions;
      } catch (error) {
         throw new Error(error);
      }
    },
  },
  Mutation: { mutation code ... }
}
```

Define an async function corresponding to the one in our typeDefs. We defined transactions in Query type in our typeDefs like so:

```javascript
// typeDef.js - our Query type
type Query {
    transactions: [Transaction!]!
  }
```


Now let's fetch data in our _localhost:port/graphql_. Easy peasy. Isn't it? If you are querying data, you can omit *query* at the beginning of the object as the image below.

```
query {
  transactions {
     id
     method
     cardNumber
     PadTime
     items {
       id
       amount
       quantity
     }
   }
}
```


![1_1llSDAjC-arKXMl3PXRALQ](https://dev-to-uploads.s3.amazonaws.com/i/s56s7b96riddfv6k5wcl.png)


# Conclusion
Nesting schema is easy, however, we need to be precise how we want it to be. If things are not working check whether your schema field's names are matching with the one in your resolver and its structure.