GraphQL and Apollo are two technologies I’m very excited about. In this post I’ll show you how to setup an Apollo Server using the GraphQL schema language. There are other approaches on how to implement your schema. In another tutorial I’ll show you how to do so using a GraphQL-JS Schema.
Getting started
git clone https://github.com/CaveOfCodeBlog/apollo-tutorial-kit.git
cd apollo-starter-kit
npm install
npm start
When the server is up and running you should be able to view GraphiQL at http://localhost:8080/graphiql
Copy paste the below text into the upper left box in GraphiQL.
{
testString
}
Hit the big play button and if everything works you should receive the answer below.
{
"data": {
"testString": "It works!"
}
}
So what we have right now is a very simple schema that exposes a testString. There is no resolve function for this testString, so how come we get “It works!” back? Apollo have the ability to use mocks. Which means that we can mock return types. If you look at the mocks.js file you’ll see this:
const mocks = {
String: () => 'It works!',
};
export default mocks;
This means that all Strings that cannot be resolved, will be returned as “It works!”.
What makes it work is the addMockFunctionsToSchema() in the server.js file:
import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools';
import Mocks from './data/mocks';
addMockFunctionsToSchema({
schema: executableSchema,
mocks: Mocks,
preserveResolvers: true,
});
Now let’s get started writing a GraphQL Schema for your Apollo Server!
The basics
Right now our schema lies in the schema.graphql
file. GraphQL files are imported as strings using the babel-plugin-inline-import
package.
The schema.graphql
file could be replaced by a schema.js
file. I’m going to continue using the .graphql
file extension, but if you’r more happy with .js files you can see how that is done below:
const schema = `
type Query {
testString: String
}
type schema {
query: Query
}
`;
export default schema;
type Query {
testString: String
}
type schema {
query: Query
}
Right now we are mocking all String
return values with “it works!” when there is no resolver.
Now, that is not really exciting. So let’s add a resolver to change that!
As you can see in the server.js
file. Our resolvers are defined as an empty function. The resolvers are the ones responsible for resolving queries to Apollo by doing whatever is nessecary to “resolve” the query and deliver data if requested. Let’s start by creating a new file called resolvers.js
and input the following code.
const resolvers = {
Query: {
testString: () => {
return 'new string!';
},
},
};
export default resolvers;
Now we need to import it into our server.js
file and change the resolvers used in the makeExecutableSchema()
body
import resolvers from './data/resolvers.js';
import Mocks from './data/mocks';
const GRAPHQL_PORT = 8080;
const graphQLServer = express();
const executableSchema = makeExecutableSchema({
typeDefs: schema,
resolvers,
});
Now refresh GraphiQL and try querying for testString again. We should now get the following response.
{
"data": {
"testString": "new string!"
}
}
So we just created a resolve function for testString. That was pretty easy right? Later we’ll add some models that make more sense.
Splitting up the schema
We still have a pretty simple schema so splitting it up is not really necessary but it’s nice to know that it can be done.
Inside of our data folder, let’s create a new folder called schema
.
Inside the schema
folder, create an index.js
, schemaDefinition.graphql
, and query.graphql
file. It should look something like this.

Let’s start with the query.graphql file. In this file we only want what our query should contain. So let’s take the query from our schema.graphql file and insert that into our query.graphql file like so:
type Query {
testString: String
}
Now to the schemaDefiition.graphql
file. This one contains the rest of the string from the schema.js file. It should look like this.
schema {
query: Query
}
The last and most important of the files is our index.js
file. This is the one that puts them together and exports them.
// Import schema definition
import SchemaDefinition from "./schemaDefinition.graphql";
// Import query
import Query from "./query.graphql";
export default [SchemaDefinition, Query];
Modularizing might seem premature when our schema is this simple, and it is. But as we progress it should make more sense. But by no means do you need to do this. You can keep it all in one file if you want to, like we did in the section prior to this one.
Adding custom models
More often than not your endpoints want to return some kind of model. So let’s make a couple of models. Our GraphQL schema doesn’t need to represent our data layer schema. I’ll get to that later in this section. Let’s start of by making a User
model. Create a user.graphql
file in our schema
folder and paste in the following User
model.
type User {
_id: String,
displayName: String,
email: String,
firstName: String,
lastName: String,
birthday: String,
cars: [ Car ]
}
Our User
has a property called cars which is an array of the model Car. Since we don’t have a car model yet I say we create one right now. Add a car.graphql file to the schema folder and paste the following model into it.
type Car {
_id: String,
model: String,
registrationNo: String,
owner: User
}
Now to the fun part. Like I said earlier, our GraphQL schema doesn’t represent our database schema. I might not wanna taint my User
model by putting an array of cars on it in the database. So how does this work? We setup a resolve function for the cars
parameter on the User
model. Since every car has an owner, we can search the database for a car with this queried user
as owner. The database model might have navigation properties or some other way of navigating to his cars. But we have to remember that this shouldn’t matter to our GraphQL client. In this tutorial we are not really gonna touch the database side of things, I’ll get to that in another tutorial.
Let’s create the resolve functions related to the user so we can see how this could work.
const resolvers = {
Query: {
testString: () => {
return 'new string!!!';
},
getUser: (_, { id }) => {
// return db.findUserWithId(id);
return {
_id: 'myId',
displayName: 'Karnich',
email: '[email protected]',
firstName: 'Jesper',
secondName: 'Christensen',
birthday: 'old',
};
},
},
User: {
cars(user) {
// return db.findCarWithOwner(user._id);
return [
{ _id: '230cx', model: 'Speedster', registrationNo: 'X32C211', owner: 'myId' },
{ _id: '54pc3', model: 'Slowster', registrationNo: 'X212X45', owner: 'myId' }
];
},
},
};
export default resolvers;
Now I’ve out-commented db.findUserWithId(id)
and db.findCarWithOwner(user._id)
because those won’t work right now when we haven’t made a connector/model layer for us to fetch models in the database. So I’ve “mocked” them. You should get the picture 😉
The bottom resolver on User is the resolver that is run when you query for something that returns a User where you want the cars array as well. Fields that are not a scalar type are not resolved by default. You need to specify that you want them when you query.
Now for this to actually work we still need to update our query schema. So head into our query.graphql file and add getUser
like so.
type Query {
getUser(id: String!): User,
testString: String
}
And as the last thing we need to add our models to the schema as well. We do so in the index.js
file inside the schema
folder.
// Import schema definition
import SchemaDefinition from './schemaDefinition.graphql';
// Import query
import Query from './query.graphql';
// Import types
import User from './user.graphql';
import Car from './car.graphql';
export default [SchemaDefinition, Query, User, Car];
Now we should be able to query after a User. Now the parameters we query with obviously doesn’t mean anything since we return the same model no matter what.
So let’s just query with the following.
{
getUser(id: "test")
}
Our query should change and include all the scalar types of the User
model. This means it returns all fields except for the cars
property. Try adding the cars
to the query and see what happens.
{
getUser(id: "test") {
_id
displayName
email
firstName
lastName
birthday
cars
}
}
Again our query will expand our cars so we get _id
, model
and registrationNo
returned. But not the owner. Again this is because it’s not returned by default. We need to query after it specifically. But since we haven’t made a resolver for it we won’t do that 🙂
That’s it for this tutorial! Check out part 2 where we add a model and connectors layer so we can get some data persistence!