In this tutorial we’ll continue where we left off in our Apollo Starter guide. If you did that one you can just continue using that project, if not you can clone this repository:
Getting started
git clone https://github.com/CaveOfCodeBlog/apollo-tutorial-kit-part2.git
cd apollo-starter-kit-part2
npm install
npm start
I already added the folder structure I wish to use on my Apollo server. it includes 4 layers. Starting from the top:
- Schema
- Resolvers
- Models
- Connectors
We are going to be focusing on the model and connector layers. The model layer is what our resolvers are gonna use. They are there to expose a generic interface to our resolvers that have abstracted whatever database and possible ORM used in the connectors layer.
The connectors layer exposes functions to manipulate data which the model layer uses. These functions interfaces depend upon what database or ORM is chosen. It also takes care of setting up the different connections to databases.
We already made the schema and a couple of resolvers in the previous tutorial. So let’s start from the bottom this time with the connectors layer.
The connectors layer
We are going to set up a MongoDB connection in the connectors layer. We’ll use Mongoose for easier collection manipulation. So let’s start by adding Mongoose to our project.
npm install mongoose --save
If you don’t have MongoDB installed you can find it in the MongoDB Download Center.
When it is installed you should be able to run it from the command line with this command
Windows: mongod --dbpath=C:/data --port 27017
OSX: mongod --port 27017
You can also get a free 500mb mongodb at mLab and use that one instead of a local one…
In the index.js
file of the connectors
folder we are going to setup the connection to a Mongo database called “test” on port 27017 which is the default port. We do that like so:
import mongoose from 'mongoose';
const CarSchema = new mongoose.Schema({
model: String,
registrationNo: String,
owner: { type: String, ref: 'User' },
});
const UserSchema = new mongoose.Schema({
displayName: String,
email: String,
firstName: String,
lastName: String,
birthday: String,
});
export const User = mongoose.model('User', UserSchema);
export const Car = mongoose.model('Car', CarSchema);
export const MongooseConnection = mongoose.connect('mongodb://localhost:27017/test')
.catch((connectError) => {
console.error('Could not connect to MongoDB on port 27017', connectError);
});
Besides making a connection we also create 2 schemas and models using Mongoose. When we think back on our schema, the user type had an array of cars. Like I mentioned earlier, the schema doesn’t represent the data model. Our mongoose schema for users doesn’t have a cars array. But we do have a Car. We’ll connect the dots in the User resolver later.
The models layer
Let’s start by creating a user.js
file inside the models
folder. In this file we’ll import the User
from the connectors layer, define a couple of functions and export a User
object with those functions. Like this:
import { User as db } from '../connectors';
const findById = (id) => {
return db.findById(id);
};
const create = (user) => {
return db.create(user);
};
export const User = {
findById,
create,
};
So with this we can now import User
from the models layer, into our resolvers layer and start creating and finding users.
We’ll need a car.js
file as well for the Car
model. They are similar but Car
will have a function that can find them by their owners id.
import { Car as db } from "../connectors";
const findById = (id) => {
return db.findById(id);
};
const findByOwner = (owner) => {
return db.find({ owner });
};
const create = (user) => {
return db.create(user);
};
export const Car = {
findById,
findByOwner,
create
};
Now we just need the index.js
file to export Car
and User
for easier importing.
export { Car } from './car';
export { User } from './user';
We should now be ready to fix our resolve functions!
Splitting up the resolver
But our resolvers was all in 1 .js
file. I showed you how you could split up your schema into several pieces instead of keeping them all in 1 file. We are going to be doing the same thing here.
Let’s create a user.js
file inside of the resolvers
folder to hold the user resolve function. Most of the code we need is already written in the resolvers.js
file. We just add it to a constant and expose it. Oh and we also remove the mocked return value and actually fetch the data through our models layer!
import { Car } from '../models';
export const User = {
cars: (model) => {
return Car.findByOwner(model.id);
},
};
We are also gonna make a query.js file inside the resolvers folder and put the query resolve functions inside. Again we remove the mocked return value.
import { User } from '../models';
export const Query = {
testString: () => {
return 'new string!!!';
},
getUser: (_, { id }) => {
return User.findById(id);
},
};
We also wanna add 2 mutations. One to create a car and one to create a user. query are gets. They are used to fetch data. When you wanna manipulate data you wanna do a post. That’s what Mutations are. Create a mutation.js file in the resolvers folder.
import { User, Car } from '../models';
export const Mutation = {
createUser: (_, { user }) => {
return User.create(user);
},
createCar: (_, { car }) => {
return Car.create(car);
},
};
Now that it’s done we can export them in the index.js file and remove our resolver.js file because that won’t be needed anymore! Where we import the resolvers are now pointing on the resolvers folders index file instead of the resolvers.js file we removed. So everything should work exactly like before. The index.js file should look like this:
import { Query } from './query';
import { User } from './user';
import { Mutation } from './mutation';
const resolvers = {
Query,
Mutation,
User
};
export default resolvers;
But we forgot 1 thing. Our schema doesn’t expose our mutations yet! Create a mutation.graphql file inside the schema folder and paste this inside:
type Mutation {
createUser(user: InputUser!): User
createCar(car: InputCar!): Car
}
Now notice that they take inputs of type InputUser and InputCar. That is because when you want to pass custom types as function params they need to be of type input. I usually put these input types right next to their respective types. So inside of the car.graphql file add the input type
type Car {
_id: String,
model: String,
registrationNo: String,
owner: User
}
input InputCar {
model: String,
registrationNo: String,
owner: User
}
And also inside of the user.graphql file like so:
type User {
_id: String,
displayName: String,
email: String,
firstName: String,
lastName: String,
birthday: String,
cars: [ Car ]
}
input InputUser {
displayName: String!,
email: String!,
firstName: String!,
lastName: String!,
birthday: String!,
}
Alright we are almost done! we need to add the mutations to the schemaDefinition.graphql
file!
schema {
query: Query,
mutation: Mutation
}
And as the final thing we need to import it in the index.js
file and add it to the exported array. It should look like this when your done:
// Import schema definition
import SchemaDefinition from './schemaDefinition.graphql';
// Import query
import Query from './query.graphql';
import Mutation from './mutation.graphql';
// Import types
import User from './user.graphql';
import Car from './car.graphql';
export default [SchemaDefinition, Query, Mutation, User, Car];
Testing
FINALLY! Now let’s test it out! Try hitting up GraphiQL at http://localhost:8080/graphiql and copy paste the following into it:
mutation createUser {
createUser(user: {displayName: "dis", firstName: "Johnny", lastName: "boho", birthday: "2016-11-11T20:34:09Z", email: "[email protected]"}) {
_id
displayName
email
firstName
lastName
birthday
}
}
mutation createCar {
createCar(car: {model: "Ford", registrationNo:"APOLLO!", owner: ""}) {
_id
model
registrationNo
}
}
query getUser {getUser(id: "") {
_id
displayName
email
firstName
lastName
birthday
cars {
_id
model
registrationNo
}
}}
Now we got 3 functions to play with. First hit the big play button and select createUser
.
It should return the newly created user with an _id
param. Take that param and paste it inside of the createCar
and getUser
functions on the empty strings place.
Now let’s try getUser
. It should return the user you already received from createUser
, but with an empty cars
array. Try hitting createCar
now. That creates a car in our database with the owner set to the user we created (if you remembered to paste in the id).
Play around with it and try adding your own functions! 🙂
You can view the source right here: https://github.com/CaveOfCodeBlog/simple-apollo-server
That was all for this tutorial. If you liked it or have any questions feel free to leave a comment below!