GraphQL + Mongodb. The easy way.
Tags: graphql, mongodb, node, javascript
Published: 2019-06-02 21:55:05

EDIT: Seeing the repercussion this post had I'll make one updated, using Prisma, new GraphQL features and will be available for whatever database you want to use. Thanks, Álvaro.
IT'S HERE! New version: https://dev.to/alvarojsnish/graphql-mongo-v2-the-easy-way-6cb

Hi everyone! My name is Alvaro and this is my first post here. I've been writing in other websites such as medium.

BUT! Now I'm here, and I hope to stay here for a while.

I've been playing with GraphQL the last months and, literally, I love it.

Today, we'll learn to:

1. How to set up a GraphQL server
2. How to query this API
3. Connect it to mongo

In the app, we'll have authenticated users, and only that way the will create posts.

Let's start!

# 1. Set up node with babel

```
mkdir graphql-test && cd graphql-test
yarn init -y
yarn add --dev nodemon @babel/core @babel/node @babel/preset-env
```

I'm using yarn, but you can use npm. 
Create a .babelrc file in your root directory, then pase this config:

```javascript
{
  "presets": ["@babel/preset-env"]
}

```

# 2. Create our files and directories organization
1. In the root, create the folder src
2. Inside src: models, schemas and resolvers
3. Now, in src, create index.js
4. Install all the packages that we will use: 
```
yarn add mongoose jsonwebtoken bcrypt express graphql cors apollo-server apollo-server-express
```
5. Create a script in package.json to start the server:
```json
{
  "name": "graphql-test",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "dev": "nodemon --exec babel-node src/index.js"
  },
  "devDependencies": {
    "@babel/core": "^7.4.5",
    "@babel/node": "^7.4.5",
    "@babel/preset-env": "^7.4.5",
    "apollo-server": "^2.6.1",
    "apollo-server-express": "^2.6.1",
    "bcrypt": "^3.0.6",
    "cors": "^2.8.5",
    "express": "^4.17.1",
    "graphql": "^14.3.1",
    "jsonwebtoken": "^8.5.1",
    "mongoose": "^5.5.12",
    "nodemon": "^1.19.1"
  }
}

```

In index.js is where everything begins.

# 3. Create the mongo models
Since we wanna focus on GraphQL, lets speed up a bit all the mongo things:

Inside models, create userModel and postModel:

postModel.js
```javascript
import mongoose from 'mongoose';

const postSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true,
  },
  content: {
    type: String,
    required: true,
  },
  author: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'user',
  },
});

const post = mongoose.model('post', postSchema);

export default post;
```

userModel.js
```javascript
import bcrypt from 'bcrypt';
import mongoose from 'mongoose';

const userSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true,
    unique: true,
  },
  password: {
    type: String,
    required: true,
  },
  posts: [
    {
      type: mongoose.Schema.Types.ObjectId,
      ref: 'post',
    },
  ],
});

userSchema.pre('save', function() {
  const hashedPassword = bcrypt.hashSync(this.password, 12);
  this.password = hashedPassword;
});

const user = mongoose.model('user', userSchema);

export default user;
```

# 4. Create our schemas
Inside /src/schemas, we'll create postSchema.js and userSchema.js
```javascript
import { gql } from 'apollo-server';

export default gql`
  type Post {
    id: ID!
    title: String!
    content: String!
    author: User!
  }

  extend type Query {
    post(id: ID!): Post!
    posts: [Post!]!
  }

  extend type Mutation {
    createPost(title: String!, content: String!): Post!
  }
`;
```
```javascript
import { gql } from 'apollo-server';

export default gql`
  type User {
    id: ID!
    name: String!
    posts: [Post!]!
  }

  type Token {
    token: String!
  }

  extend type Query {
    user(id: ID!): User!
    login(name: String!, password: String!): Token!
  }

  extend type Mutation {
    createUser(name: String!, password: String!): User!
  }
`;
```
1. We use the *extend* anotation because we'll create a linkSchema, to use every schema we add together. We can only have one Query type, so extending it we can use both, also works for mutations and subscriptions.
2. In the user we don't add the password (security reasons), so the client can't query for it.

This is our link schema:
```javascript
import userSchema from './user';
import postSchema from './post';
import { gql } from 'apollo-server';

const linkSchema = gql`
  type Query {
    _: Boolean
  }
  type Mutation {
    _: Boolean
  }
`;

export default [linkSchema, userSchema, postSchema];

```
I created it in schemas/index.js, and it's the schema we'll import later on our index.

#5. Create our resolvers
The same way as schemas, we created postResolvers.js and userResolvers.js in src/resolvers
```javascript
import { AuthenticationError } from 'apollo-server';

export default {
  Query: {
    post: async (parent, { id }, { models: { postModel }, me }, info) => {
      if (!me) {
        throw new AuthenticationError('You are not authenticated');
      }
      const post = await postModel.findById({ _id: id }).exec();
      return post;
    },
    posts: async (parent, args, { models: { postModel }, me }, info) => {
      if (!me) {
        throw new AuthenticationError('You are not authenticated');
      }
      const posts = await postModel.find({ author: me.id }).exec();
      return posts;
    },
  },
  Mutation: {
    createPost: async (parent, { title, content }, { models: { postModel }, me }, info) => {
      if (!me) {
        throw new AuthenticationError('You are not authenticated');
      }
      const post = await postModel.create({ title, content, author: me.id });
      return post;
    },
  },
  Post: {
    author: async ({ author }, args, { models: { userModel } }, info) => {
      const user = await userModel.findById({ _id: author }).exec();
      return user;
    },
  },
};
```
```javascript
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import { AuthenticationError } from 'apollo-server';

export default {
  Query: {
    user: async (parent, { id }, { models: { userModel }, me }, info) => {
      if (!me) {
        throw new AuthenticationError('You are not authenticated');
      }
      const user = await userModel.findById({ _id: id }).exec();
      return user;
    },
    login: async (parent, { name, password }, { models: { userModel } }, info) => {
      const user = await userModel.findOne({ name }).exec();

      if (!user) {
        throw new AuthenticationError('Invalid credentials');
      }

      const matchPasswords = bcrypt.compareSync(password, user.password);

      if (!matchPasswords) {
        throw new AuthenticationError('Invalid credentials');
      }

      const token = jwt.sign({ id: user.id }, 'riddlemethis', { expiresIn: 24 * 10 * 50 });

      return {
        token,
      };
    },
  },
  Mutation: {
    createUser: async (parent, { name, password }, { models: { userModel } }, info) => {
      const user = await userModel.create({ name, password });
      return user;
    },
  },
  User: {
    posts: async ({ id }, args, { models: { postModel } }, info) => {
      const posts = await postModel.find({ author: id }).exec();
      return posts;
    },
  },
};
```

1. Query will resolve all the "functions" we created in our schema, in the type Query.
2. Mutations, will resolve all the "functions" we created in our schema, in the type Mutation.
3. User / Post, will resolve a concrete field or type everytime we query the API looking for a user or post. This means that everytime we query for a User > Posts, the server first will go throught the Query > user, and then, will go thought User > posts (posts is the name of the field).
We need to do this because we store the data in different collections.

### As we see, a resolver is a function, and it has 4 arguments (parent, args, context and info).
parent: will have the data returned from the parent resolver. Example: we go thought Query > user > posts. Posts will have all the data returned un user as a parent argument.

args: will have the arguments we use in the query/mutation. If we see our schemas, post(id: ID!): Post! will have 1 argument, id.

context: the context is an object that will contain everything we pass to it in our server configuration, in our case, it has de mongo models for user and post, and "me", the current user logged in.

info: this is more complex, and Prisma goes in deep here: https://www.prisma.io/blog/graphql-server-basics-demystifying-the-info-argument-in-graphql-resolvers-6f26249f613a


As we did with schemas, create an index.js inside src/resolvers:
```javascript
import postResolver from './postResolver';
import userResolver from './userResolver';

export default [userResolver, postResolver];
```

#6 Setting everything up
Finally, in our index.js in the src/ folder:
```javascript
import cors from 'cors';
import express from 'express';
import jwt from 'jsonwebtoken';
import mongoose from 'mongoose';
import { ApolloServer, AuthenticationError } from 'apollo-server-express';

import schemas from './schemas';
import resolvers from './resolvers';

import userModel from './models/userModel';
import postModel from './models/postModel';

const app = express();
app.use(cors());

const getUser = async (req) => {
  const token = req.headers['token'];

  if (token) {
    try {
      return await jwt.verify(token, 'riddlemethis');
    } catch (e) {
      throw new AuthenticationError('Your session expired. Sign in again.');
    }
  }
};

const server = new ApolloServer({
  typeDefs: schemas,
  resolvers,
  context: async ({ req }) => {
    if (req) {
      const me = await getUser(req);

      return {
        me,
        models: {
          userModel,
          postModel,
        },
      };
    }
  },
});

server.applyMiddleware({ app, path: '/graphql' });

app.listen(5000, () => {
  mongoose.connect('mongodb://localhost:27017/graphql');
});
```

1. With de function getUser, we are passing the token and verifying it, if the token is not valid, the "me" object will be null, then the client couldn't do request.
2. When we create the ApolloServer, we pass the schemas as typeDefs, the resolvers as resolvers, and the context, will be an async function that will resolve the previous function we created. Either a user or null, the context will have the mongo models we created, so we can operate with the database in the resolvers.
3. We add the express server middelware to our app, and set the API Endpoint to /graphql.
4. We set the port of our app to 5000, then, we connect to the db. Our db will be named graphql.


# 7. Testing our newborn.
1. Run "yarn dev" or "npm run dev".
2. Go to http://localhost:5000/graphql
3. Make some querys and mutations!

Create user
![](https://thepracticaldev.s3.amazonaws.com/i/gqvttc3vwnw8zk38chp2.JPG)

Login user
![](https://thepracticaldev.s3.amazonaws.com/i/69z1g1g1vqyc2ggkym30.JPG)

Set token in headers
![](https://thepracticaldev.s3.amazonaws.com/i/5f9c69as4ngbge0bcdar.JPG)

Create posts
![](https://thepracticaldev.s3.amazonaws.com/i/mrsjf4i7z0uvomn5ia5o.JPG)

Query posts
![](https://thepracticaldev.s3.amazonaws.com/i/urll8g57k23kgypkqiyw.JPG)


### I hope you enjoyed this as much as I did! You can contact me anytime you want to! And If you want better explanations, just ask for them, I'll be pleased to do it!