Jesper O. Christensen

Cloud Architect @ Brandheroes

Apollo server: using the GraphQL Schema Languages

2016-10-31 jesper o. christensenGraphQL...

unsplash.com

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.

unsplash.com

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: 'myEmail@gmail.com',
        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!