From 1a8eea901b727011c0e8fce4872c18727297c480 Mon Sep 17 00:00:00 2001
From: Erik Van Kelst <4levels@gmail.com>
Date: Thu, 15 Mar 2018 13:03:49 +0100
Subject: [PATCH 1/2] Added Relay features from obsolete "feature/relay" branch
 as suggested in issue #284

---
 .../GraphQL/Relay/ConnectionEdgeType.php      |  26 +++
 .../Relay/Console/ConnectionMakeCommand.php   |  82 ++++++++
 .../Relay/Console/InputMakeCommand.php        |  82 ++++++++
 .../Relay/Console/MutationMakeCommand.php     |  82 ++++++++
 .../GraphQL/Relay/Console/NodeMakeCommand.php |  82 ++++++++
 .../Relay/Console/PayloadMakeCommand.php      |  82 ++++++++
 .../Relay/Console/stubs/connection.stub       |  20 ++
 .../GraphQL/Relay/Console/stubs/input.stub    |  22 +++
 .../GraphQL/Relay/Console/stubs/mutation.stub |  30 +++
 .../GraphQL/Relay/Console/stubs/node.stub     |  29 +++
 .../GraphQL/Relay/Console/stubs/payload.stub  |  22 +++
 src/Folklore/GraphQL/Relay/EdgeObjectType.php |  29 +++
 .../GraphQL/Relay/EdgesCollection.php         |  64 ++++++
 .../GraphQL/Relay/Exception/NodeInvalid.php   |   7 +
 .../Relay/Exception/NodeRootInvalid.php       |   7 +
 .../GraphQL/Relay/MutationResponse.php        |  35 ++++
 src/Folklore/GraphQL/Relay/NodeIdField.php    |  49 +++++
 src/Folklore/GraphQL/Relay/NodeInterface.php  |  32 +++
 src/Folklore/GraphQL/Relay/NodeQuery.php      |  61 ++++++
 src/Folklore/GraphQL/Relay/NodeResponse.php   |  33 ++++
 src/Folklore/GraphQL/Relay/PageInfoType.php   |  33 ++++
 src/Folklore/GraphQL/Relay/Relay.php          |  74 +++++++
 .../Relay/Relay/ConnectionEdgeType.php        |  26 +++
 .../Relay/Console/ConnectionMakeCommand.php   |  82 ++++++++
 .../Relay/Relay/Console/InputMakeCommand.php  |  82 ++++++++
 .../Relay/Console/MutationMakeCommand.php     |  82 ++++++++
 .../Relay/Relay/Console/NodeMakeCommand.php   |  82 ++++++++
 .../Relay/Console/PayloadMakeCommand.php      |  82 ++++++++
 .../Relay/Relay/Console/stubs/connection.stub |  20 ++
 .../Relay/Relay/Console/stubs/input.stub      |  22 +++
 .../Relay/Relay/Console/stubs/mutation.stub   |  30 +++
 .../Relay/Relay/Console/stubs/node.stub       |  29 +++
 .../Relay/Relay/Console/stubs/payload.stub    |  22 +++
 .../GraphQL/Relay/Relay/EdgeObjectType.php    |  29 +++
 .../GraphQL/Relay/Relay/EdgesCollection.php   |  64 ++++++
 .../Relay/Relay/Exception/NodeInvalid.php     |   7 +
 .../Relay/Relay/Exception/NodeRootInvalid.php |   7 +
 .../GraphQL/Relay/Relay/MutationResponse.php  |  35 ++++
 .../GraphQL/Relay/Relay/NodeIdField.php       |  49 +++++
 .../GraphQL/Relay/Relay/NodeInterface.php     |  32 +++
 .../GraphQL/Relay/Relay/NodeQuery.php         |  61 ++++++
 .../GraphQL/Relay/Relay/NodeResponse.php      |  33 ++++
 .../GraphQL/Relay/Relay/PageInfoType.php      |  33 ++++
 src/Folklore/GraphQL/Relay/Relay/Relay.php    |  74 +++++++
 .../GraphQL/Relay/Relay/ServiceProvider.php   | 187 ++++++++++++++++++
 .../Relay/Relay/Support/ConnectionField.php   |  13 ++
 .../Relay/Relay/Support/ConnectionType.php    | 165 ++++++++++++++++
 .../Relay/Relay/Support/Facades/Relay.php     |  16 ++
 .../GraphQL/Relay/Relay/Support/InputType.php |  13 ++
 .../GraphQL/Relay/Relay/Support/Mutation.php  |  64 ++++++
 .../Relay/Relay/Support/NodeContract.php      |   8 +
 .../GraphQL/Relay/Relay/Support/NodeType.php  |  11 ++
 .../Relay/Relay/Support/PayloadType.php       |  11 ++
 .../Support/Traits/FieldIsConnection.php      |  39 ++++
 .../Traits/HasClientMutationIdField.php       |  21 ++
 .../Traits/ResolvesFromQueryBuilder.php       | 151 ++++++++++++++
 .../Relay/Relay/Support/Traits/TypeIsNode.php |  59 ++++++
 .../GraphQL/Relay/ServiceProvider.php         | 187 ++++++++++++++++++
 .../GraphQL/Relay/Support/ConnectionField.php |  13 ++
 .../GraphQL/Relay/Support/ConnectionType.php  | 165 ++++++++++++++++
 .../GraphQL/Relay/Support/Facades/Relay.php   |  16 ++
 .../GraphQL/Relay/Support/InputType.php       |  13 ++
 .../GraphQL/Relay/Support/Mutation.php        |  64 ++++++
 .../GraphQL/Relay/Support/NodeContract.php    |   8 +
 .../GraphQL/Relay/Support/NodeType.php        |  11 ++
 .../GraphQL/Relay/Support/PayloadType.php     |  11 ++
 .../Support/Traits/FieldIsConnection.php      |  39 ++++
 .../Traits/HasClientMutationIdField.php       |  21 ++
 .../Traits/ResolvesFromQueryBuilder.php       | 160 +++++++++++++++
 .../Relay/Support/Traits/TypeIsNode.php       |  59 ++++++
 70 files changed, 3491 insertions(+)
 create mode 100644 src/Folklore/GraphQL/Relay/ConnectionEdgeType.php
 create mode 100644 src/Folklore/GraphQL/Relay/Console/ConnectionMakeCommand.php
 create mode 100644 src/Folklore/GraphQL/Relay/Console/InputMakeCommand.php
 create mode 100644 src/Folklore/GraphQL/Relay/Console/MutationMakeCommand.php
 create mode 100644 src/Folklore/GraphQL/Relay/Console/NodeMakeCommand.php
 create mode 100644 src/Folklore/GraphQL/Relay/Console/PayloadMakeCommand.php
 create mode 100644 src/Folklore/GraphQL/Relay/Console/stubs/connection.stub
 create mode 100644 src/Folklore/GraphQL/Relay/Console/stubs/input.stub
 create mode 100644 src/Folklore/GraphQL/Relay/Console/stubs/mutation.stub
 create mode 100644 src/Folklore/GraphQL/Relay/Console/stubs/node.stub
 create mode 100644 src/Folklore/GraphQL/Relay/Console/stubs/payload.stub
 create mode 100644 src/Folklore/GraphQL/Relay/EdgeObjectType.php
 create mode 100644 src/Folklore/GraphQL/Relay/EdgesCollection.php
 create mode 100644 src/Folklore/GraphQL/Relay/Exception/NodeInvalid.php
 create mode 100644 src/Folklore/GraphQL/Relay/Exception/NodeRootInvalid.php
 create mode 100644 src/Folklore/GraphQL/Relay/MutationResponse.php
 create mode 100644 src/Folklore/GraphQL/Relay/NodeIdField.php
 create mode 100644 src/Folklore/GraphQL/Relay/NodeInterface.php
 create mode 100644 src/Folklore/GraphQL/Relay/NodeQuery.php
 create mode 100644 src/Folklore/GraphQL/Relay/NodeResponse.php
 create mode 100644 src/Folklore/GraphQL/Relay/PageInfoType.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/ConnectionEdgeType.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/Console/ConnectionMakeCommand.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/Console/InputMakeCommand.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/Console/MutationMakeCommand.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/Console/NodeMakeCommand.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/Console/PayloadMakeCommand.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/Console/stubs/connection.stub
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/Console/stubs/input.stub
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/Console/stubs/mutation.stub
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/Console/stubs/node.stub
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/Console/stubs/payload.stub
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/EdgeObjectType.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/EdgesCollection.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/Exception/NodeInvalid.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/Exception/NodeRootInvalid.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/MutationResponse.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/NodeIdField.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/NodeInterface.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/NodeQuery.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/NodeResponse.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/PageInfoType.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/Relay.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/ServiceProvider.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/Support/ConnectionField.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/Support/ConnectionType.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/Support/Facades/Relay.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/Support/InputType.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/Support/Mutation.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/Support/NodeContract.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/Support/NodeType.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/Support/PayloadType.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/Support/Traits/FieldIsConnection.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/Support/Traits/HasClientMutationIdField.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/Support/Traits/ResolvesFromQueryBuilder.php
 create mode 100644 src/Folklore/GraphQL/Relay/Relay/Support/Traits/TypeIsNode.php
 create mode 100644 src/Folklore/GraphQL/Relay/ServiceProvider.php
 create mode 100644 src/Folklore/GraphQL/Relay/Support/ConnectionField.php
 create mode 100644 src/Folklore/GraphQL/Relay/Support/ConnectionType.php
 create mode 100644 src/Folklore/GraphQL/Relay/Support/Facades/Relay.php
 create mode 100644 src/Folklore/GraphQL/Relay/Support/InputType.php
 create mode 100644 src/Folklore/GraphQL/Relay/Support/Mutation.php
 create mode 100644 src/Folklore/GraphQL/Relay/Support/NodeContract.php
 create mode 100644 src/Folklore/GraphQL/Relay/Support/NodeType.php
 create mode 100644 src/Folklore/GraphQL/Relay/Support/PayloadType.php
 create mode 100644 src/Folklore/GraphQL/Relay/Support/Traits/FieldIsConnection.php
 create mode 100644 src/Folklore/GraphQL/Relay/Support/Traits/HasClientMutationIdField.php
 create mode 100644 src/Folklore/GraphQL/Relay/Support/Traits/ResolvesFromQueryBuilder.php
 create mode 100644 src/Folklore/GraphQL/Relay/Support/Traits/TypeIsNode.php

diff --git a/src/Folklore/GraphQL/Relay/ConnectionEdgeType.php b/src/Folklore/GraphQL/Relay/ConnectionEdgeType.php
new file mode 100644
index 00000000..70eb0bc3
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/ConnectionEdgeType.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Folklore\GraphQL\Relay;
+
+use GraphQL\Type\Definition\Type;
+use Folklore\GraphQL\Support\Type as BaseType;
+
+class ConnectionEdgeType extends BaseType
+{
+    public function fields()
+    {
+        return [
+            'cursor' => [
+                'type' => Type::nonNull(Type::id())
+            ],
+            'node' => [
+                'type' => app('graphql')->type('Node')
+            ]
+        ];
+    }
+    
+    public function toType()
+    {
+        return new EdgeObjectType($this->toArray());
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Console/ConnectionMakeCommand.php b/src/Folklore/GraphQL/Relay/Console/ConnectionMakeCommand.php
new file mode 100644
index 00000000..eacb844b
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Console/ConnectionMakeCommand.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Console;
+
+use Illuminate\Console\GeneratorCommand;
+
+class ConnectionMakeCommand extends GeneratorCommand
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'make:relay:connection {name}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Create a new GraphQL Relay connection';
+
+    /**
+     * The type of class being generated.
+     *
+     * @var string
+     */
+    protected $type = 'ConnectionType';
+
+    /**
+     * Get the stub file for the generator.
+     *
+     * @return string
+     */
+    protected function getStub()
+    {
+        return __DIR__.'/stubs/connection.stub';
+    }
+
+    /**
+     * Get the default namespace for the class.
+     *
+     * @param  string  $rootNamespace
+     * @return string
+     */
+    protected function getDefaultNamespace($rootNamespace)
+    {
+        return $rootNamespace.'\GraphQL\Type';
+    }
+
+    /**
+     * Build the class with the given name.
+     *
+     * @param  string  $name
+     * @return string
+     */
+    protected function buildClass($name)
+    {
+        $stub = parent::buildClass($name);
+
+        return $this->replaceType($stub, $name);
+    }
+
+    /**
+     * Replace the namespace for the given stub.
+     *
+     * @param  string  $stub
+     * @param  string  $name
+     * @return $this
+     */
+    protected function replaceType($stub, $name)
+    {
+        preg_match('/([^\\\]+)$/', $name, $matches);
+        $stub = str_replace(
+            'DummyConnection',
+            $matches[1],
+            $stub
+        );
+
+        return $stub;
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Console/InputMakeCommand.php b/src/Folklore/GraphQL/Relay/Console/InputMakeCommand.php
new file mode 100644
index 00000000..00ac452b
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Console/InputMakeCommand.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Console;
+
+use Illuminate\Console\GeneratorCommand;
+
+class InputMakeCommand extends GeneratorCommand
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'make:relay:input {name}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Create a new GraphQL Relay mutation input class';
+
+    /**
+     * The type of class being generated.
+     *
+     * @var string
+     */
+    protected $type = 'InputType';
+
+    /**
+     * Get the stub file for the generator.
+     *
+     * @return string
+     */
+    protected function getStub()
+    {
+        return __DIR__.'/stubs/input.stub';
+    }
+
+    /**
+     * Get the default namespace for the class.
+     *
+     * @param  string  $rootNamespace
+     * @return string
+     */
+    protected function getDefaultNamespace($rootNamespace)
+    {
+        return $rootNamespace.'\GraphQL\Type';
+    }
+
+    /**
+     * Build the class with the given name.
+     *
+     * @param  string  $name
+     * @return string
+     */
+    protected function buildClass($name)
+    {
+        $stub = parent::buildClass($name);
+
+        return $this->replaceType($stub, $name);
+    }
+
+    /**
+     * Replace the namespace for the given stub.
+     *
+     * @param  string  $stub
+     * @param  string  $name
+     * @return $this
+     */
+    protected function replaceType($stub, $name)
+    {
+        preg_match('/([^\\\]+)$/', $name, $matches);
+        $stub = str_replace(
+            'DummyInput',
+            $matches[1],
+            $stub
+        );
+
+        return $stub;
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Console/MutationMakeCommand.php b/src/Folklore/GraphQL/Relay/Console/MutationMakeCommand.php
new file mode 100644
index 00000000..8d55d822
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Console/MutationMakeCommand.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Console;
+
+use Illuminate\Console\GeneratorCommand;
+
+class MutationMakeCommand extends GeneratorCommand
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'make:relay:mutation {name}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Create a new GraphQL Relay mutation class';
+
+    /**
+     * The type of class being generated.
+     *
+     * @var string
+     */
+    protected $type = 'Mutation';
+
+    /**
+     * Get the stub file for the generator.
+     *
+     * @return string
+     */
+    protected function getStub()
+    {
+        return __DIR__.'/stubs/mutation.stub';
+    }
+
+    /**
+     * Get the default namespace for the class.
+     *
+     * @param  string  $rootNamespace
+     * @return string
+     */
+    protected function getDefaultNamespace($rootNamespace)
+    {
+        return $rootNamespace.'\GraphQL\Mutation';
+    }
+
+    /**
+     * Build the class with the given name.
+     *
+     * @param  string  $name
+     * @return string
+     */
+    protected function buildClass($name)
+    {
+        $stub = parent::buildClass($name);
+
+        return $this->replaceType($stub, $name);
+    }
+
+    /**
+     * Replace the namespace for the given stub.
+     *
+     * @param  string  $stub
+     * @param  string  $name
+     * @return $this
+     */
+    protected function replaceType($stub, $name)
+    {
+        preg_match('/([^\\\]+)$/', $name, $matches);
+        $stub = str_replace(
+            'DummyMutation',
+            $matches[1],
+            $stub
+        );
+
+        return $stub;
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Console/NodeMakeCommand.php b/src/Folklore/GraphQL/Relay/Console/NodeMakeCommand.php
new file mode 100644
index 00000000..98b14a9b
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Console/NodeMakeCommand.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Console;
+
+use Illuminate\Console\GeneratorCommand;
+
+class NodeMakeCommand extends GeneratorCommand
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'make:relay:node {name}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Create a new GraphQL Relay node class';
+
+    /**
+     * The type of class being generated.
+     *
+     * @var string
+     */
+    protected $type = 'NodeType';
+
+    /**
+     * Get the stub file for the generator.
+     *
+     * @return string
+     */
+    protected function getStub()
+    {
+        return __DIR__.'/stubs/node.stub';
+    }
+
+    /**
+     * Get the default namespace for the class.
+     *
+     * @param  string  $rootNamespace
+     * @return string
+     */
+    protected function getDefaultNamespace($rootNamespace)
+    {
+        return $rootNamespace.'\GraphQL\Type';
+    }
+
+    /**
+     * Build the class with the given name.
+     *
+     * @param  string  $name
+     * @return string
+     */
+    protected function buildClass($name)
+    {
+        $stub = parent::buildClass($name);
+
+        return $this->replaceType($stub, $name);
+    }
+
+    /**
+     * Replace the namespace for the given stub.
+     *
+     * @param  string  $stub
+     * @param  string  $name
+     * @return $this
+     */
+    protected function replaceType($stub, $name)
+    {
+        preg_match('/([^\\\]+)$/', $name, $matches);
+        $stub = str_replace(
+            'DummyType',
+            $matches[1],
+            $stub
+        );
+
+        return $stub;
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Console/PayloadMakeCommand.php b/src/Folklore/GraphQL/Relay/Console/PayloadMakeCommand.php
new file mode 100644
index 00000000..52f8fc94
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Console/PayloadMakeCommand.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Console;
+
+use Illuminate\Console\GeneratorCommand;
+
+class PayloadMakeCommand extends GeneratorCommand
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'make:relay:payload {name}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Create a new GraphQL Relay mutation payload class';
+
+    /**
+     * The type of class being generated.
+     *
+     * @var string
+     */
+    protected $type = 'PayloadType';
+
+    /**
+     * Get the stub file for the generator.
+     *
+     * @return string
+     */
+    protected function getStub()
+    {
+        return __DIR__.'/stubs/payload.stub';
+    }
+
+    /**
+     * Get the default namespace for the class.
+     *
+     * @param  string  $rootNamespace
+     * @return string
+     */
+    protected function getDefaultNamespace($rootNamespace)
+    {
+        return $rootNamespace.'\GraphQL\Type';
+    }
+
+    /**
+     * Build the class with the given name.
+     *
+     * @param  string  $name
+     * @return string
+     */
+    protected function buildClass($name)
+    {
+        $stub = parent::buildClass($name);
+
+        return $this->replaceType($stub, $name);
+    }
+
+    /**
+     * Replace the namespace for the given stub.
+     *
+     * @param  string  $stub
+     * @param  string  $name
+     * @return $this
+     */
+    protected function replaceType($stub, $name)
+    {
+        preg_match('/([^\\\]+)$/', $name, $matches);
+        $stub = str_replace(
+            'DummyPayload',
+            $matches[1],
+            $stub
+        );
+
+        return $stub;
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Console/stubs/connection.stub b/src/Folklore/GraphQL/Relay/Console/stubs/connection.stub
new file mode 100644
index 00000000..30664441
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Console/stubs/connection.stub
@@ -0,0 +1,20 @@
+<?php
+
+namespace DummyNamespace;
+
+use GraphQL\Type\Definition\Type;
+use Folklore\GraphQL\Relay\Support\ConnectionType as BaseConnectionType;
+use GraphQL;
+
+class DummyClass extends BaseConnectionType
+{
+    protected $attributes = [
+        'name' => 'DummyConnection',
+        'description' => 'A relay connection type'
+    ];
+
+    public function edgeType()
+    {
+        return null;
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Console/stubs/input.stub b/src/Folklore/GraphQL/Relay/Console/stubs/input.stub
new file mode 100644
index 00000000..0de8b90f
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Console/stubs/input.stub
@@ -0,0 +1,22 @@
+<?php
+
+namespace DummyNamespace;
+
+use GraphQL\Type\Definition\Type;
+use Folklore\GraphQL\Relay\Support\InputType as BaseInputType;
+use GraphQL;
+
+class DummyClass extends BaseInputType
+{
+    protected $attributes = [
+        'name' => 'DummyInput',
+        'description' => 'A relay mutation input type'
+    ];
+
+    public function fields()
+    {
+        return [
+        
+        ];
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Console/stubs/mutation.stub b/src/Folklore/GraphQL/Relay/Console/stubs/mutation.stub
new file mode 100644
index 00000000..a0276078
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Console/stubs/mutation.stub
@@ -0,0 +1,30 @@
+<?php
+
+namespace DummyNamespace;
+
+use GraphQL\Type\Definition\Type;
+use Folklore\GraphQL\Relay\Support\Mutation as BaseMutation;
+use GraphQL;
+
+class DummyClass extends BaseMutation
+{
+    protected $attributes = [
+        'name' => 'DummyMutation',
+        'description' => 'A relay mutation'
+    ];
+
+    protected function inputType()
+    {
+        return null;
+    }
+    
+    public function type()
+    {
+        return null;
+    }
+    
+    public function resolve($root, $args, $context, ResolveInfo $info)
+    {
+        
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Console/stubs/node.stub b/src/Folklore/GraphQL/Relay/Console/stubs/node.stub
new file mode 100644
index 00000000..1dfce5c7
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Console/stubs/node.stub
@@ -0,0 +1,29 @@
+<?php
+
+namespace DummyNamespace;
+
+use GraphQL\Type\Definition\Type;
+use Folklore\GraphQL\Relay\Support\NodeType as BaseNodeType;
+use GraphQL;
+
+class DummyClass extends BaseNodeType
+{
+    protected $attributes = [
+        'name' => 'DummyType',
+        'description' => 'A relay node type'
+    ];
+
+    public function fields()
+    {
+        return [
+            'id' => [
+                'type' => Type::nonNull(Type::id())
+            ]
+        ];
+    }
+    
+    public function resolveById($id)
+    {
+        // Get a node from an id
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Console/stubs/payload.stub b/src/Folklore/GraphQL/Relay/Console/stubs/payload.stub
new file mode 100644
index 00000000..d98d7dc2
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Console/stubs/payload.stub
@@ -0,0 +1,22 @@
+<?php
+
+namespace DummyNamespace;
+
+use GraphQL\Type\Definition\Type;
+use Folklore\GraphQL\Relay\Support\PayloadType as BasePayloadType;
+use GraphQL;
+
+class DummyClass extends BasePayloadType
+{
+    protected $attributes = [
+        'name' => 'DummyPayload',
+        'description' => 'A relay mutation payload type'
+    ];
+
+    public function fields()
+    {
+        return [
+        
+        ];
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/EdgeObjectType.php b/src/Folklore/GraphQL/Relay/EdgeObjectType.php
new file mode 100644
index 00000000..a09a079a
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/EdgeObjectType.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Folklore\GraphQL\Relay;
+
+use GraphQL\Type\Definition\ObjectType;
+use Folklore\GraphQL\Support\Type as BaseType;
+use Closure;
+
+class EdgeObjectType extends ObjectType
+{
+    public function setEdgeType($type)
+    {
+        $this->_fields = null;
+        $currentFields = array_get($this->config, 'fields');
+        $fieldsResolver = function () use ($currentFields, $type) {
+            $fields = $currentFields instanceof Closure ? $currentFields():$currentFields;
+            array_set($fields, 'node.type', $type);
+            return $fields;
+        };
+        array_set($this->config, 'fields', $fieldsResolver);
+
+        return $this;
+    }
+
+    public function getEdgeType()
+    {
+        return array_get($this->getField('node')->config, 'type');
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/EdgesCollection.php b/src/Folklore/GraphQL/Relay/EdgesCollection.php
new file mode 100644
index 00000000..ad4282b5
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/EdgesCollection.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Folklore\GraphQL\Relay;
+
+use Illuminate\Database\Eloquent\Collection;
+
+class EdgesCollection extends Collection
+{
+    protected $total = 0;
+    protected $startCursor = null;
+    protected $endCursor = null;
+    protected $hasNextPage = false;
+    protected $hasPreviousPage = false;
+
+    public function setTotal($total)
+    {
+        $this->total = $total;
+    }
+
+    public function getTotal()
+    {
+        return $this->total;
+    }
+
+    public function setHasNextPage($hasNextPage)
+    {
+        $this->hasNextPage = $hasNextPage;
+    }
+
+    public function getHasNextPage()
+    {
+        return $this->hasNextPage;
+    }
+
+    public function setHasPreviousPage($hasPreviousPage)
+    {
+        $this->hasPreviousPage = $hasPreviousPage;
+    }
+
+    public function getHasPreviousPage()
+    {
+        return $this->hasPreviousPage;
+    }
+
+    public function setStartCursor($startCursor)
+    {
+        $this->startCursor = $startCursor;
+    }
+
+    public function getStartCursor()
+    {
+        return $this->startCursor;
+    }
+
+    public function setEndCursor($endCursor)
+    {
+        $this->endCursor = $endCursor;
+    }
+
+    public function getEndCursor()
+    {
+        return $this->endCursor;
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Exception/NodeInvalid.php b/src/Folklore/GraphQL/Relay/Exception/NodeInvalid.php
new file mode 100644
index 00000000..ed6b41fa
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Exception/NodeInvalid.php
@@ -0,0 +1,7 @@
+<?php namespace Folklore\GraphQL\Relay\Exception;
+
+use Exception;
+
+class NodeInvalid extends Exception
+{
+}
diff --git a/src/Folklore/GraphQL/Relay/Exception/NodeRootInvalid.php b/src/Folklore/GraphQL/Relay/Exception/NodeRootInvalid.php
new file mode 100644
index 00000000..1f02c3c2
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Exception/NodeRootInvalid.php
@@ -0,0 +1,7 @@
+<?php namespace Folklore\GraphQL\Relay\Exception;
+
+use Exception;
+
+class NodeRootInvalid extends Exception
+{
+}
diff --git a/src/Folklore/GraphQL/Relay/MutationResponse.php b/src/Folklore/GraphQL/Relay/MutationResponse.php
new file mode 100644
index 00000000..8e2c3771
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/MutationResponse.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Folklore\GraphQL\Relay;
+
+use Illuminate\Support\Fluent;
+use Illuminate\Contracts\Support\Arrayable;
+
+class MutationResponse extends Fluent
+{
+    protected $clientMutationId;
+    protected $originalNode;
+
+    public function getOriginalNode()
+    {
+        return $this->originalNode;
+    }
+
+    public function setNode($node)
+    {
+        $this->originalNode = $node;
+        $this->attributes = $node instanceof Arrayable ? $node->toArray():(array)$node;
+        return $this;
+    }
+
+    public function setClientMutationId($clientMutationId)
+    {
+        $this->clientMutationId = $clientMutationId;
+        return $this;
+    }
+
+    public function getClientMutationId()
+    {
+        return $this->clientMutationId;
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/NodeIdField.php b/src/Folklore/GraphQL/Relay/NodeIdField.php
new file mode 100644
index 00000000..aeda542b
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/NodeIdField.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Folklore\GraphQL\Relay;
+
+use GraphQL\Type\Definition\Type;
+use Folklore\GraphQL\Support\Field as BaseField;
+
+class NodeIdField extends BaseField
+{
+    protected $idResolver;
+    protected $idType;
+
+    protected $attributes = [
+        'description' => 'A relay node id field'
+    ];
+
+    public function type()
+    {
+        return Type::nonNull(Type::id());
+    }
+
+    public function setIdResolver($idResolver)
+    {
+        $this->idResolver = $idResolver;
+        return $this;
+    }
+
+    public function getIdResolver()
+    {
+        return $this->idResolver;
+    }
+
+    public function setIdType($idType)
+    {
+        $this->idType = $idType;
+        return $this;
+    }
+
+    public function getIdType()
+    {
+        return $this->idType;
+    }
+
+    public function resolve()
+    {
+        $id = call_user_func_array($this->idResolver, func_get_args());
+        return app('graphql.relay')->toGlobalId($this->idType, $id);
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/NodeInterface.php b/src/Folklore/GraphQL/Relay/NodeInterface.php
new file mode 100644
index 00000000..f602ee16
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/NodeInterface.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Folklore\GraphQL\Relay;
+
+use GraphQL\Type\Definition\Type;
+use Folklore\GraphQL\Support\InterfaceType as BaseInterfaceType;
+use Folklore\GraphQL\Relay\Exception\NodeRootInvalid;
+
+class NodeInterface extends BaseInterfaceType
+{
+    protected $attributes = [
+        'name' => 'Node',
+        'description' => 'The relay node interface'
+    ];
+
+    public function fields()
+    {
+        return [
+            'id' => [
+                'type' => Type::nonNull(Type::id())
+            ]
+        ];
+    }
+    
+    protected function resolveType($root)
+    {
+        if (!$root instanceof NodeResponse) {
+            throw new NodeRootInvalid('$root is not a NodeResponse');
+        }
+        return $root->getType();
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/NodeQuery.php b/src/Folklore/GraphQL/Relay/NodeQuery.php
new file mode 100644
index 00000000..cc769e04
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/NodeQuery.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace Folklore\GraphQL\Relay;
+
+use Folklore\GraphQL\Support\Query;
+use GraphQL\Type\Definition\ResolveInfo;
+use GraphQL\Type\Definition\Type;
+use Folklore\GraphQL\Exception\TypeNotFound;
+use Folklore\GraphQL\Relay\Exception\NodeInvalid;
+
+use Folklore\GraphQL\Relay\Support\NodeContract;
+
+class NodeQuery extends Query
+{
+    protected $attributes = [
+        'name' => 'NodeQuery',
+        'description' => 'A query'
+    ];
+
+    public function type()
+    {
+        return app('graphql')->type('Node');
+    }
+
+    public function args()
+    {
+        return [
+            'id' => [
+                'name' => 'id',
+                'type' => Type::nonNull(Type::id())
+            ]
+        ];
+    }
+
+    public function resolve($root, $args, $context, ResolveInfo $info)
+    {
+        $globalId = app('graphql.relay')->fromGlobalId($args['id']);
+        $typeName = $globalId['type'];
+        $id = $globalId['id'];
+        $types = app('graphql')->getTypes();
+        $typeClass = array_get($types, $typeName);
+
+        if (!$typeClass) {
+            throw new TypeNotFound('Type "'.$typeName.'" not found.');
+        }
+
+        $type = app($typeClass);
+
+        if (!$type instanceof NodeContract) {
+            throw new NodeInvalid('Type "'.$typeName.'" doesn\'t implement the NodeContract interface.');
+        }
+
+        $node = $type->resolveById($id);
+
+        $response = new NodeResponse();
+        $response->setNode($node);
+        $response->setType(app('graphql')->type($typeName));
+
+        return $response;
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/NodeResponse.php b/src/Folklore/GraphQL/Relay/NodeResponse.php
new file mode 100644
index 00000000..9b1147b3
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/NodeResponse.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Folklore\GraphQL\Relay;
+
+use Illuminate\Support\Fluent;
+use Illuminate\Contracts\Support\Arrayable;
+
+class NodeResponse extends Fluent
+{
+    protected $type;
+    protected $originalNode;
+    
+    public function getOriginalNode()
+    {
+        return $this->originalNode;
+    }
+    
+    public function setNode($node)
+    {
+        $this->originalNode = $node;
+        $this->attributes = $node instanceof Arrayable ? $node->toArray():(array)$node;
+    }
+    
+    public function setType($type)
+    {
+        $this->type = $type;
+    }
+    
+    public function getType()
+    {
+        return $this->type;
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/PageInfoType.php b/src/Folklore/GraphQL/Relay/PageInfoType.php
new file mode 100644
index 00000000..9f490ed4
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/PageInfoType.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Folklore\GraphQL\Relay;
+
+use GraphQL\Type\Definition\Type;
+use Folklore\GraphQL\Support\Type as BaseType;
+use GraphQL;
+
+class PageInfoType extends BaseType
+{
+    protected $attributes = [
+        'name' => 'PageInfo',
+        'description' => 'The relay pageInfo type used by connections'
+    ];
+
+    public function fields()
+    {
+        return [
+            'hasNextPage' => [
+                'type' => Type::nonNull(Type::boolean())
+            ],
+            'hasPreviousPage' => [
+                'type' => Type::nonNull(Type::boolean())
+            ],
+            'startCursor' => [
+                'type' => Type::string()
+            ],
+            'endCursor' => [
+                'type' => Type::string()
+            ]
+        ];
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay.php b/src/Folklore/GraphQL/Relay/Relay.php
new file mode 100644
index 00000000..d720334e
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay.php
@@ -0,0 +1,74 @@
+<?php
+
+namespace Folklore\GraphQL\Relay;
+
+use Folklore\GraphQL\Relay\Support\ConnectionField;
+use Folklore\GraphQL\Relay\Support\ConnectionType;
+
+class Relay
+{
+    protected $app;
+    protected $graphql;
+
+    public function __construct($app)
+    {
+        $this->app = $app;
+        $this->graphql = $app['graphql'];
+    }
+
+    public function connectionField($config = [])
+    {
+        $field = new ConnectionField($config);
+        return $field;
+    }
+
+    public function connectionFieldFromEdgeType($edgeType, $config = [])
+    {
+        $typeName = array_get($edgeType->config, 'name');
+        $connectionName = array_get($config, 'connectionTypeName', str_plural($typeName).'Connection');
+
+        $connectionType = new ConnectionType([
+            'name' => $connectionName
+        ]);
+        $connectionType->setEdgeType($edgeType);
+        $this->graphql->addType($connectionType, $connectionName);
+
+        $fieldConfig = array_except($config, ['connectionTypeName']);
+        $field = new ConnectionField($fieldConfig);
+        $field->setType($this->graphql->type($connectionName));
+        return $field;
+    }
+
+    public function connectionFieldFromEdgeTypeAndQueryBuilder($edgeType, $queryBuilderResolver, $config = [])
+    {
+        $field = $this->connectionFieldFromEdgeType($edgeType, $config);
+        $field->setQueryBuilderResolver($queryBuilderResolver);
+        return $field;
+    }
+
+    public function toGlobalId($type, $id)
+    {
+        return base64_encode($type.':'.$id);
+    }
+
+    public function fromGlobalId($globalId)
+    {
+        $id = explode(':', base64_decode($globalId), 2);
+        return sizeof($id) === 2 ? [
+            'type' => $id[0],
+            'id' => $id[1]
+        ]:null;
+    }
+
+    public function getIdFromGlobalId($globalId)
+    {
+        $id = $this->fromGlobalId($globalId);
+        return $id ? $id['id']:null;
+    }
+
+    public function getTypeFromGlobalId($globalId)
+    {
+        $id = $this->fromGlobalId($globalId);
+        return $id ? $id['type']:null;
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/ConnectionEdgeType.php b/src/Folklore/GraphQL/Relay/Relay/ConnectionEdgeType.php
new file mode 100644
index 00000000..70eb0bc3
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/ConnectionEdgeType.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Folklore\GraphQL\Relay;
+
+use GraphQL\Type\Definition\Type;
+use Folklore\GraphQL\Support\Type as BaseType;
+
+class ConnectionEdgeType extends BaseType
+{
+    public function fields()
+    {
+        return [
+            'cursor' => [
+                'type' => Type::nonNull(Type::id())
+            ],
+            'node' => [
+                'type' => app('graphql')->type('Node')
+            ]
+        ];
+    }
+    
+    public function toType()
+    {
+        return new EdgeObjectType($this->toArray());
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/Console/ConnectionMakeCommand.php b/src/Folklore/GraphQL/Relay/Relay/Console/ConnectionMakeCommand.php
new file mode 100644
index 00000000..eacb844b
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/Console/ConnectionMakeCommand.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Console;
+
+use Illuminate\Console\GeneratorCommand;
+
+class ConnectionMakeCommand extends GeneratorCommand
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'make:relay:connection {name}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Create a new GraphQL Relay connection';
+
+    /**
+     * The type of class being generated.
+     *
+     * @var string
+     */
+    protected $type = 'ConnectionType';
+
+    /**
+     * Get the stub file for the generator.
+     *
+     * @return string
+     */
+    protected function getStub()
+    {
+        return __DIR__.'/stubs/connection.stub';
+    }
+
+    /**
+     * Get the default namespace for the class.
+     *
+     * @param  string  $rootNamespace
+     * @return string
+     */
+    protected function getDefaultNamespace($rootNamespace)
+    {
+        return $rootNamespace.'\GraphQL\Type';
+    }
+
+    /**
+     * Build the class with the given name.
+     *
+     * @param  string  $name
+     * @return string
+     */
+    protected function buildClass($name)
+    {
+        $stub = parent::buildClass($name);
+
+        return $this->replaceType($stub, $name);
+    }
+
+    /**
+     * Replace the namespace for the given stub.
+     *
+     * @param  string  $stub
+     * @param  string  $name
+     * @return $this
+     */
+    protected function replaceType($stub, $name)
+    {
+        preg_match('/([^\\\]+)$/', $name, $matches);
+        $stub = str_replace(
+            'DummyConnection',
+            $matches[1],
+            $stub
+        );
+
+        return $stub;
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/Console/InputMakeCommand.php b/src/Folklore/GraphQL/Relay/Relay/Console/InputMakeCommand.php
new file mode 100644
index 00000000..00ac452b
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/Console/InputMakeCommand.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Console;
+
+use Illuminate\Console\GeneratorCommand;
+
+class InputMakeCommand extends GeneratorCommand
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'make:relay:input {name}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Create a new GraphQL Relay mutation input class';
+
+    /**
+     * The type of class being generated.
+     *
+     * @var string
+     */
+    protected $type = 'InputType';
+
+    /**
+     * Get the stub file for the generator.
+     *
+     * @return string
+     */
+    protected function getStub()
+    {
+        return __DIR__.'/stubs/input.stub';
+    }
+
+    /**
+     * Get the default namespace for the class.
+     *
+     * @param  string  $rootNamespace
+     * @return string
+     */
+    protected function getDefaultNamespace($rootNamespace)
+    {
+        return $rootNamespace.'\GraphQL\Type';
+    }
+
+    /**
+     * Build the class with the given name.
+     *
+     * @param  string  $name
+     * @return string
+     */
+    protected function buildClass($name)
+    {
+        $stub = parent::buildClass($name);
+
+        return $this->replaceType($stub, $name);
+    }
+
+    /**
+     * Replace the namespace for the given stub.
+     *
+     * @param  string  $stub
+     * @param  string  $name
+     * @return $this
+     */
+    protected function replaceType($stub, $name)
+    {
+        preg_match('/([^\\\]+)$/', $name, $matches);
+        $stub = str_replace(
+            'DummyInput',
+            $matches[1],
+            $stub
+        );
+
+        return $stub;
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/Console/MutationMakeCommand.php b/src/Folklore/GraphQL/Relay/Relay/Console/MutationMakeCommand.php
new file mode 100644
index 00000000..8d55d822
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/Console/MutationMakeCommand.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Console;
+
+use Illuminate\Console\GeneratorCommand;
+
+class MutationMakeCommand extends GeneratorCommand
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'make:relay:mutation {name}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Create a new GraphQL Relay mutation class';
+
+    /**
+     * The type of class being generated.
+     *
+     * @var string
+     */
+    protected $type = 'Mutation';
+
+    /**
+     * Get the stub file for the generator.
+     *
+     * @return string
+     */
+    protected function getStub()
+    {
+        return __DIR__.'/stubs/mutation.stub';
+    }
+
+    /**
+     * Get the default namespace for the class.
+     *
+     * @param  string  $rootNamespace
+     * @return string
+     */
+    protected function getDefaultNamespace($rootNamespace)
+    {
+        return $rootNamespace.'\GraphQL\Mutation';
+    }
+
+    /**
+     * Build the class with the given name.
+     *
+     * @param  string  $name
+     * @return string
+     */
+    protected function buildClass($name)
+    {
+        $stub = parent::buildClass($name);
+
+        return $this->replaceType($stub, $name);
+    }
+
+    /**
+     * Replace the namespace for the given stub.
+     *
+     * @param  string  $stub
+     * @param  string  $name
+     * @return $this
+     */
+    protected function replaceType($stub, $name)
+    {
+        preg_match('/([^\\\]+)$/', $name, $matches);
+        $stub = str_replace(
+            'DummyMutation',
+            $matches[1],
+            $stub
+        );
+
+        return $stub;
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/Console/NodeMakeCommand.php b/src/Folklore/GraphQL/Relay/Relay/Console/NodeMakeCommand.php
new file mode 100644
index 00000000..98b14a9b
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/Console/NodeMakeCommand.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Console;
+
+use Illuminate\Console\GeneratorCommand;
+
+class NodeMakeCommand extends GeneratorCommand
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'make:relay:node {name}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Create a new GraphQL Relay node class';
+
+    /**
+     * The type of class being generated.
+     *
+     * @var string
+     */
+    protected $type = 'NodeType';
+
+    /**
+     * Get the stub file for the generator.
+     *
+     * @return string
+     */
+    protected function getStub()
+    {
+        return __DIR__.'/stubs/node.stub';
+    }
+
+    /**
+     * Get the default namespace for the class.
+     *
+     * @param  string  $rootNamespace
+     * @return string
+     */
+    protected function getDefaultNamespace($rootNamespace)
+    {
+        return $rootNamespace.'\GraphQL\Type';
+    }
+
+    /**
+     * Build the class with the given name.
+     *
+     * @param  string  $name
+     * @return string
+     */
+    protected function buildClass($name)
+    {
+        $stub = parent::buildClass($name);
+
+        return $this->replaceType($stub, $name);
+    }
+
+    /**
+     * Replace the namespace for the given stub.
+     *
+     * @param  string  $stub
+     * @param  string  $name
+     * @return $this
+     */
+    protected function replaceType($stub, $name)
+    {
+        preg_match('/([^\\\]+)$/', $name, $matches);
+        $stub = str_replace(
+            'DummyType',
+            $matches[1],
+            $stub
+        );
+
+        return $stub;
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/Console/PayloadMakeCommand.php b/src/Folklore/GraphQL/Relay/Relay/Console/PayloadMakeCommand.php
new file mode 100644
index 00000000..52f8fc94
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/Console/PayloadMakeCommand.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Console;
+
+use Illuminate\Console\GeneratorCommand;
+
+class PayloadMakeCommand extends GeneratorCommand
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'make:relay:payload {name}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Create a new GraphQL Relay mutation payload class';
+
+    /**
+     * The type of class being generated.
+     *
+     * @var string
+     */
+    protected $type = 'PayloadType';
+
+    /**
+     * Get the stub file for the generator.
+     *
+     * @return string
+     */
+    protected function getStub()
+    {
+        return __DIR__.'/stubs/payload.stub';
+    }
+
+    /**
+     * Get the default namespace for the class.
+     *
+     * @param  string  $rootNamespace
+     * @return string
+     */
+    protected function getDefaultNamespace($rootNamespace)
+    {
+        return $rootNamespace.'\GraphQL\Type';
+    }
+
+    /**
+     * Build the class with the given name.
+     *
+     * @param  string  $name
+     * @return string
+     */
+    protected function buildClass($name)
+    {
+        $stub = parent::buildClass($name);
+
+        return $this->replaceType($stub, $name);
+    }
+
+    /**
+     * Replace the namespace for the given stub.
+     *
+     * @param  string  $stub
+     * @param  string  $name
+     * @return $this
+     */
+    protected function replaceType($stub, $name)
+    {
+        preg_match('/([^\\\]+)$/', $name, $matches);
+        $stub = str_replace(
+            'DummyPayload',
+            $matches[1],
+            $stub
+        );
+
+        return $stub;
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/Console/stubs/connection.stub b/src/Folklore/GraphQL/Relay/Relay/Console/stubs/connection.stub
new file mode 100644
index 00000000..67c261da
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/Console/stubs/connection.stub
@@ -0,0 +1,20 @@
+<?php
+
+namespace DummyNamespace;
+
+use GraphQL\Type\Definition\Type;
+use Folklore\GraphQL\Relay\Support\ConnectionType as BaseConnectionType;
+use GraphQL;
+
+class DummyClass extends BaseConnectionType
+{
+    protected $attributes = [
+        'name' => 'DummyConnection',
+        'description' => 'A relay connection type'
+    ];
+
+    protected function edgeType()
+    {
+        return null;
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/Console/stubs/input.stub b/src/Folklore/GraphQL/Relay/Relay/Console/stubs/input.stub
new file mode 100644
index 00000000..0de8b90f
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/Console/stubs/input.stub
@@ -0,0 +1,22 @@
+<?php
+
+namespace DummyNamespace;
+
+use GraphQL\Type\Definition\Type;
+use Folklore\GraphQL\Relay\Support\InputType as BaseInputType;
+use GraphQL;
+
+class DummyClass extends BaseInputType
+{
+    protected $attributes = [
+        'name' => 'DummyInput',
+        'description' => 'A relay mutation input type'
+    ];
+
+    public function fields()
+    {
+        return [
+        
+        ];
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/Console/stubs/mutation.stub b/src/Folklore/GraphQL/Relay/Relay/Console/stubs/mutation.stub
new file mode 100644
index 00000000..a0276078
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/Console/stubs/mutation.stub
@@ -0,0 +1,30 @@
+<?php
+
+namespace DummyNamespace;
+
+use GraphQL\Type\Definition\Type;
+use Folklore\GraphQL\Relay\Support\Mutation as BaseMutation;
+use GraphQL;
+
+class DummyClass extends BaseMutation
+{
+    protected $attributes = [
+        'name' => 'DummyMutation',
+        'description' => 'A relay mutation'
+    ];
+
+    protected function inputType()
+    {
+        return null;
+    }
+    
+    public function type()
+    {
+        return null;
+    }
+    
+    public function resolve($root, $args, $context, ResolveInfo $info)
+    {
+        
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/Console/stubs/node.stub b/src/Folklore/GraphQL/Relay/Relay/Console/stubs/node.stub
new file mode 100644
index 00000000..1dfce5c7
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/Console/stubs/node.stub
@@ -0,0 +1,29 @@
+<?php
+
+namespace DummyNamespace;
+
+use GraphQL\Type\Definition\Type;
+use Folklore\GraphQL\Relay\Support\NodeType as BaseNodeType;
+use GraphQL;
+
+class DummyClass extends BaseNodeType
+{
+    protected $attributes = [
+        'name' => 'DummyType',
+        'description' => 'A relay node type'
+    ];
+
+    public function fields()
+    {
+        return [
+            'id' => [
+                'type' => Type::nonNull(Type::id())
+            ]
+        ];
+    }
+    
+    public function resolveById($id)
+    {
+        // Get a node from an id
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/Console/stubs/payload.stub b/src/Folklore/GraphQL/Relay/Relay/Console/stubs/payload.stub
new file mode 100644
index 00000000..d98d7dc2
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/Console/stubs/payload.stub
@@ -0,0 +1,22 @@
+<?php
+
+namespace DummyNamespace;
+
+use GraphQL\Type\Definition\Type;
+use Folklore\GraphQL\Relay\Support\PayloadType as BasePayloadType;
+use GraphQL;
+
+class DummyClass extends BasePayloadType
+{
+    protected $attributes = [
+        'name' => 'DummyPayload',
+        'description' => 'A relay mutation payload type'
+    ];
+
+    public function fields()
+    {
+        return [
+        
+        ];
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/EdgeObjectType.php b/src/Folklore/GraphQL/Relay/Relay/EdgeObjectType.php
new file mode 100644
index 00000000..a09a079a
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/EdgeObjectType.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Folklore\GraphQL\Relay;
+
+use GraphQL\Type\Definition\ObjectType;
+use Folklore\GraphQL\Support\Type as BaseType;
+use Closure;
+
+class EdgeObjectType extends ObjectType
+{
+    public function setEdgeType($type)
+    {
+        $this->_fields = null;
+        $currentFields = array_get($this->config, 'fields');
+        $fieldsResolver = function () use ($currentFields, $type) {
+            $fields = $currentFields instanceof Closure ? $currentFields():$currentFields;
+            array_set($fields, 'node.type', $type);
+            return $fields;
+        };
+        array_set($this->config, 'fields', $fieldsResolver);
+
+        return $this;
+    }
+
+    public function getEdgeType()
+    {
+        return array_get($this->getField('node')->config, 'type');
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/EdgesCollection.php b/src/Folklore/GraphQL/Relay/Relay/EdgesCollection.php
new file mode 100644
index 00000000..ad4282b5
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/EdgesCollection.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Folklore\GraphQL\Relay;
+
+use Illuminate\Database\Eloquent\Collection;
+
+class EdgesCollection extends Collection
+{
+    protected $total = 0;
+    protected $startCursor = null;
+    protected $endCursor = null;
+    protected $hasNextPage = false;
+    protected $hasPreviousPage = false;
+
+    public function setTotal($total)
+    {
+        $this->total = $total;
+    }
+
+    public function getTotal()
+    {
+        return $this->total;
+    }
+
+    public function setHasNextPage($hasNextPage)
+    {
+        $this->hasNextPage = $hasNextPage;
+    }
+
+    public function getHasNextPage()
+    {
+        return $this->hasNextPage;
+    }
+
+    public function setHasPreviousPage($hasPreviousPage)
+    {
+        $this->hasPreviousPage = $hasPreviousPage;
+    }
+
+    public function getHasPreviousPage()
+    {
+        return $this->hasPreviousPage;
+    }
+
+    public function setStartCursor($startCursor)
+    {
+        $this->startCursor = $startCursor;
+    }
+
+    public function getStartCursor()
+    {
+        return $this->startCursor;
+    }
+
+    public function setEndCursor($endCursor)
+    {
+        $this->endCursor = $endCursor;
+    }
+
+    public function getEndCursor()
+    {
+        return $this->endCursor;
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/Exception/NodeInvalid.php b/src/Folklore/GraphQL/Relay/Relay/Exception/NodeInvalid.php
new file mode 100644
index 00000000..ed6b41fa
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/Exception/NodeInvalid.php
@@ -0,0 +1,7 @@
+<?php namespace Folklore\GraphQL\Relay\Exception;
+
+use Exception;
+
+class NodeInvalid extends Exception
+{
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/Exception/NodeRootInvalid.php b/src/Folklore/GraphQL/Relay/Relay/Exception/NodeRootInvalid.php
new file mode 100644
index 00000000..1f02c3c2
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/Exception/NodeRootInvalid.php
@@ -0,0 +1,7 @@
+<?php namespace Folklore\GraphQL\Relay\Exception;
+
+use Exception;
+
+class NodeRootInvalid extends Exception
+{
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/MutationResponse.php b/src/Folklore/GraphQL/Relay/Relay/MutationResponse.php
new file mode 100644
index 00000000..8e2c3771
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/MutationResponse.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Folklore\GraphQL\Relay;
+
+use Illuminate\Support\Fluent;
+use Illuminate\Contracts\Support\Arrayable;
+
+class MutationResponse extends Fluent
+{
+    protected $clientMutationId;
+    protected $originalNode;
+
+    public function getOriginalNode()
+    {
+        return $this->originalNode;
+    }
+
+    public function setNode($node)
+    {
+        $this->originalNode = $node;
+        $this->attributes = $node instanceof Arrayable ? $node->toArray():(array)$node;
+        return $this;
+    }
+
+    public function setClientMutationId($clientMutationId)
+    {
+        $this->clientMutationId = $clientMutationId;
+        return $this;
+    }
+
+    public function getClientMutationId()
+    {
+        return $this->clientMutationId;
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/NodeIdField.php b/src/Folklore/GraphQL/Relay/Relay/NodeIdField.php
new file mode 100644
index 00000000..aeda542b
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/NodeIdField.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Folklore\GraphQL\Relay;
+
+use GraphQL\Type\Definition\Type;
+use Folklore\GraphQL\Support\Field as BaseField;
+
+class NodeIdField extends BaseField
+{
+    protected $idResolver;
+    protected $idType;
+
+    protected $attributes = [
+        'description' => 'A relay node id field'
+    ];
+
+    public function type()
+    {
+        return Type::nonNull(Type::id());
+    }
+
+    public function setIdResolver($idResolver)
+    {
+        $this->idResolver = $idResolver;
+        return $this;
+    }
+
+    public function getIdResolver()
+    {
+        return $this->idResolver;
+    }
+
+    public function setIdType($idType)
+    {
+        $this->idType = $idType;
+        return $this;
+    }
+
+    public function getIdType()
+    {
+        return $this->idType;
+    }
+
+    public function resolve()
+    {
+        $id = call_user_func_array($this->idResolver, func_get_args());
+        return app('graphql.relay')->toGlobalId($this->idType, $id);
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/NodeInterface.php b/src/Folklore/GraphQL/Relay/Relay/NodeInterface.php
new file mode 100644
index 00000000..f602ee16
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/NodeInterface.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Folklore\GraphQL\Relay;
+
+use GraphQL\Type\Definition\Type;
+use Folklore\GraphQL\Support\InterfaceType as BaseInterfaceType;
+use Folklore\GraphQL\Relay\Exception\NodeRootInvalid;
+
+class NodeInterface extends BaseInterfaceType
+{
+    protected $attributes = [
+        'name' => 'Node',
+        'description' => 'The relay node interface'
+    ];
+
+    public function fields()
+    {
+        return [
+            'id' => [
+                'type' => Type::nonNull(Type::id())
+            ]
+        ];
+    }
+    
+    protected function resolveType($root)
+    {
+        if (!$root instanceof NodeResponse) {
+            throw new NodeRootInvalid('$root is not a NodeResponse');
+        }
+        return $root->getType();
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/NodeQuery.php b/src/Folklore/GraphQL/Relay/Relay/NodeQuery.php
new file mode 100644
index 00000000..cc769e04
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/NodeQuery.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace Folklore\GraphQL\Relay;
+
+use Folklore\GraphQL\Support\Query;
+use GraphQL\Type\Definition\ResolveInfo;
+use GraphQL\Type\Definition\Type;
+use Folklore\GraphQL\Exception\TypeNotFound;
+use Folklore\GraphQL\Relay\Exception\NodeInvalid;
+
+use Folklore\GraphQL\Relay\Support\NodeContract;
+
+class NodeQuery extends Query
+{
+    protected $attributes = [
+        'name' => 'NodeQuery',
+        'description' => 'A query'
+    ];
+
+    public function type()
+    {
+        return app('graphql')->type('Node');
+    }
+
+    public function args()
+    {
+        return [
+            'id' => [
+                'name' => 'id',
+                'type' => Type::nonNull(Type::id())
+            ]
+        ];
+    }
+
+    public function resolve($root, $args, $context, ResolveInfo $info)
+    {
+        $globalId = app('graphql.relay')->fromGlobalId($args['id']);
+        $typeName = $globalId['type'];
+        $id = $globalId['id'];
+        $types = app('graphql')->getTypes();
+        $typeClass = array_get($types, $typeName);
+
+        if (!$typeClass) {
+            throw new TypeNotFound('Type "'.$typeName.'" not found.');
+        }
+
+        $type = app($typeClass);
+
+        if (!$type instanceof NodeContract) {
+            throw new NodeInvalid('Type "'.$typeName.'" doesn\'t implement the NodeContract interface.');
+        }
+
+        $node = $type->resolveById($id);
+
+        $response = new NodeResponse();
+        $response->setNode($node);
+        $response->setType(app('graphql')->type($typeName));
+
+        return $response;
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/NodeResponse.php b/src/Folklore/GraphQL/Relay/Relay/NodeResponse.php
new file mode 100644
index 00000000..9b1147b3
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/NodeResponse.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Folklore\GraphQL\Relay;
+
+use Illuminate\Support\Fluent;
+use Illuminate\Contracts\Support\Arrayable;
+
+class NodeResponse extends Fluent
+{
+    protected $type;
+    protected $originalNode;
+    
+    public function getOriginalNode()
+    {
+        return $this->originalNode;
+    }
+    
+    public function setNode($node)
+    {
+        $this->originalNode = $node;
+        $this->attributes = $node instanceof Arrayable ? $node->toArray():(array)$node;
+    }
+    
+    public function setType($type)
+    {
+        $this->type = $type;
+    }
+    
+    public function getType()
+    {
+        return $this->type;
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/PageInfoType.php b/src/Folklore/GraphQL/Relay/Relay/PageInfoType.php
new file mode 100644
index 00000000..9f490ed4
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/PageInfoType.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Folklore\GraphQL\Relay;
+
+use GraphQL\Type\Definition\Type;
+use Folklore\GraphQL\Support\Type as BaseType;
+use GraphQL;
+
+class PageInfoType extends BaseType
+{
+    protected $attributes = [
+        'name' => 'PageInfo',
+        'description' => 'The relay pageInfo type used by connections'
+    ];
+
+    public function fields()
+    {
+        return [
+            'hasNextPage' => [
+                'type' => Type::nonNull(Type::boolean())
+            ],
+            'hasPreviousPage' => [
+                'type' => Type::nonNull(Type::boolean())
+            ],
+            'startCursor' => [
+                'type' => Type::string()
+            ],
+            'endCursor' => [
+                'type' => Type::string()
+            ]
+        ];
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/Relay.php b/src/Folklore/GraphQL/Relay/Relay/Relay.php
new file mode 100644
index 00000000..d720334e
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/Relay.php
@@ -0,0 +1,74 @@
+<?php
+
+namespace Folklore\GraphQL\Relay;
+
+use Folklore\GraphQL\Relay\Support\ConnectionField;
+use Folklore\GraphQL\Relay\Support\ConnectionType;
+
+class Relay
+{
+    protected $app;
+    protected $graphql;
+
+    public function __construct($app)
+    {
+        $this->app = $app;
+        $this->graphql = $app['graphql'];
+    }
+
+    public function connectionField($config = [])
+    {
+        $field = new ConnectionField($config);
+        return $field;
+    }
+
+    public function connectionFieldFromEdgeType($edgeType, $config = [])
+    {
+        $typeName = array_get($edgeType->config, 'name');
+        $connectionName = array_get($config, 'connectionTypeName', str_plural($typeName).'Connection');
+
+        $connectionType = new ConnectionType([
+            'name' => $connectionName
+        ]);
+        $connectionType->setEdgeType($edgeType);
+        $this->graphql->addType($connectionType, $connectionName);
+
+        $fieldConfig = array_except($config, ['connectionTypeName']);
+        $field = new ConnectionField($fieldConfig);
+        $field->setType($this->graphql->type($connectionName));
+        return $field;
+    }
+
+    public function connectionFieldFromEdgeTypeAndQueryBuilder($edgeType, $queryBuilderResolver, $config = [])
+    {
+        $field = $this->connectionFieldFromEdgeType($edgeType, $config);
+        $field->setQueryBuilderResolver($queryBuilderResolver);
+        return $field;
+    }
+
+    public function toGlobalId($type, $id)
+    {
+        return base64_encode($type.':'.$id);
+    }
+
+    public function fromGlobalId($globalId)
+    {
+        $id = explode(':', base64_decode($globalId), 2);
+        return sizeof($id) === 2 ? [
+            'type' => $id[0],
+            'id' => $id[1]
+        ]:null;
+    }
+
+    public function getIdFromGlobalId($globalId)
+    {
+        $id = $this->fromGlobalId($globalId);
+        return $id ? $id['id']:null;
+    }
+
+    public function getTypeFromGlobalId($globalId)
+    {
+        $id = $this->fromGlobalId($globalId);
+        return $id ? $id['type']:null;
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/ServiceProvider.php b/src/Folklore/GraphQL/Relay/Relay/ServiceProvider.php
new file mode 100644
index 00000000..718314aa
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/ServiceProvider.php
@@ -0,0 +1,187 @@
+<?php namespace Folklore\GraphQL\Relay;
+
+use GraphQL\Validator\DocumentValidator;
+use Illuminate\Support\ServiceProvider as BaseServiceProvider;
+
+class ServiceProvider extends BaseServiceProvider
+{
+    /**
+     * Bootstrap any application services.
+     *
+     * @return void
+     */
+    public function boot()
+    {
+        $this->bootTypes();
+        
+        $this->bootSchemas();
+    }
+
+    /**
+     * Add schemas from config
+     *
+     * @return void
+     */
+    protected function bootSchemas()
+    {
+        $query = config('graphql.relay.query', []);
+        $schemas = config('graphql.relay.schemas');
+        if ($schemas === null) {
+            return null;
+        } elseif ($schemas === '*') {
+            $schemas = array_keys(config('graphql.schemas', []));
+        } else {
+            $schemas = (array)$schemas;
+        }
+        
+        $allSchemas = $this->app['graphql']->getSchemas();
+        foreach ($allSchemas as $name => $schema) {
+            if (!in_array($name, $schemas)) {
+                continue;
+            }
+            $schema['query'] = array_merge($schema['query'], $query);
+            $this->app['graphql']->addSchema($name, $schema);
+        }
+    }
+
+    /**
+     * Add types from config
+     *
+     * @return void
+     */
+    protected function bootTypes()
+    {
+        $types = config('graphql.relay.types');
+        if (is_array($types)) {
+            $this->app['graphql']->addTypes($types);
+        }
+    }
+
+    /**
+     * Register any application services.
+     *
+     * @return void
+     */
+    public function register()
+    {
+        $this->registerRelay();
+
+        $this->registerCommands();
+    }
+
+    /**
+     * Register Relay facade
+     *
+     * @return void
+     */
+    public function registerRelay()
+    {
+        $this->app->singleton('graphql.relay', function ($app) {
+            $relay = new Relay($app);
+            return $relay;
+        });
+    }
+
+    /**
+     * Register console commands
+     *
+     * @return void
+     */
+    public function registerCommands()
+    {
+        $commands = [
+            'MakeNode', 'MakeMutation', 'MakeInput', 'MakePayload', 'MakeConnection'
+        ];
+
+        // We'll simply spin through the list of commands that are migration related
+        // and register each one of them with an application container. They will
+        // be resolved in the Artisan start file and registered on the console.
+        foreach ($commands as $command) {
+            $this->{'register'.$command.'Command'}();
+        }
+        
+        $this->commands(
+            'command.relay.make.node',
+            'command.relay.make.mutation',
+            'command.relay.make.input',
+            'command.relay.make.payload',
+            'command.relay.make.connection'
+        );
+    }
+    
+    /**
+     * Register the "make:graphql:node" migration command.
+     *
+     * @return void
+     */
+    public function registerMakeNodeCommand()
+    {
+        $this->app->singleton('command.relay.make.node', function ($app) {
+            return new \Folklore\GraphQL\Relay\Console\NodeMakeCommand($app['files']);
+        });
+    }
+    
+    /**
+     * Register the "make:graphql:mutation" migration command.
+     *
+     * @return void
+     */
+    public function registerMakeMutationCommand()
+    {
+        $this->app->singleton('command.relay.make.mutation', function ($app) {
+            return new \Folklore\GraphQL\Relay\Console\MutationMakeCommand($app['files']);
+        });
+    }
+    
+    /**
+     * Register the "make:graphql:input" migration command.
+     *
+     * @return void
+     */
+    public function registerMakeInputCommand()
+    {
+        $this->app->singleton('command.relay.make.input', function ($app) {
+            return new \Folklore\GraphQL\Relay\Console\InputMakeCommand($app['files']);
+        });
+    }
+    
+    /**
+     * Register the "make:graphql:payload" migration command.
+     *
+     * @return void
+     */
+    public function registerMakePayloadCommand()
+    {
+        $this->app->singleton('command.relay.make.payload', function ($app) {
+            return new \Folklore\GraphQL\Relay\Console\PayloadMakeCommand($app['files']);
+        });
+    }
+    
+    /**
+     * Register the "make:graphql:payload" migration command.
+     *
+     * @return void
+     */
+    public function registerMakeConnectionCommand()
+    {
+        $this->app->singleton('command.relay.make.connection', function ($app) {
+            return new \Folklore\GraphQL\Relay\Console\ConnectionMakeCommand($app['files']);
+        });
+    }
+
+    /**
+     * Get the services provided by the provider.
+     *
+     * @return array
+     */
+    public function provides()
+    {
+        return [
+            'graphql.relay',
+            'command.relay.make.node',
+            'command.relay.make.input',
+            'command.relay.make.payload',
+            'command.relay.make.connection'
+        ];
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/Support/ConnectionField.php b/src/Folklore/GraphQL/Relay/Relay/Support/ConnectionField.php
new file mode 100644
index 00000000..9e911179
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/Support/ConnectionField.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Support;
+
+use Folklore\GraphQL\Support\Field as BaseField;
+use Folklore\GraphQL\Relay\Support\Traits\FieldIsConnection;
+use Folklore\GraphQL\Relay\Support\Traits\ResolvesFromQueryBuilder;
+use GraphQL;
+
+class ConnectionField extends BaseField
+{
+    use FieldIsConnection, ResolvesFromQueryBuilder;
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/Support/ConnectionType.php b/src/Folklore/GraphQL/Relay/Relay/Support/ConnectionType.php
new file mode 100644
index 00000000..e4362aa2
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/Support/ConnectionType.php
@@ -0,0 +1,165 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Support;
+
+use GraphQL\Type\Definition\Type;
+use GraphQL\Type\Definition\InterfaceType;
+use Folklore\GraphQL\Support\Type as BaseType;
+
+use Folklore\GraphQL\Relay\EdgesCollection;
+use Illuminate\Pagination\AbstractPaginator;
+use Illuminate\Pagination\LengthAwarePaginator;
+
+class ConnectionType extends BaseType
+{
+    protected $edgeType;
+
+    protected function edgeType()
+    {
+        return null;
+    }
+
+    public function fields()
+    {
+        return [
+            'total' => [
+                'type' => Type::int(),
+                'resolve' => function ($root) {
+                    return $this->getTotalFromRoot($root);
+                }
+            ],
+            'edges' => [
+                'type' => Type::listOf($this->getEdgeObjectType()),
+                'resolve' => function ($root) {
+                    return $this->getEdgesFromRoot($root);
+                }
+            ],
+            'pageInfo' => [
+                'type' => app('graphql')->type('PageInfo'),
+                'resolve' => function ($root) {
+                    return $this->getPageInfoFromRoot($root);
+                }
+            ]
+        ];
+    }
+
+    public function getEdgeType()
+    {
+        $edgeType = $this->edgeType();
+        return $edgeType ? $edgeType:$this->edgeType;
+    }
+
+    public function setEdgeType($edgeType)
+    {
+        $this->edgeType = $edgeType;
+        return $this;
+    }
+
+    protected function getEdgeObjectType()
+    {
+        $edgeType = $this->getEdgeType();
+        $name = $edgeType->config['name'].'Edge';
+        app('graphql')->addType(\Folklore\GraphQL\Relay\ConnectionEdgeType::class, $name);
+        $type = app('graphql')->type($name);
+        $type->setEdgeType($edgeType);
+        return $type;
+    }
+
+    protected function getCursorFromNode($edge)
+    {
+        $edgeType = $this->getEdgeType();
+        if ($edgeType instanceof InterfaceType) {
+            $edgeType = $edgeType->config['resolveType']($edge);
+        }
+        $resolveId = $edgeType->getField('id')->resolveFn;
+        return $resolveId($edge);
+    }
+
+    protected function getTotalFromRoot($root)
+    {
+        $total = 0;
+        if ($root instanceof EdgesCollection) {
+            $total = $root->getTotal();
+        }
+        return $total;
+    }
+
+
+    protected function getEdgesFromRoot($root)
+    {
+        $cursor = $this->getStartCursorFromRoot($root);
+        $edges = [];
+        foreach ($root as $item) {
+            $edges[] = [
+                'cursor' => $cursor !== null ? $cursor:$this->getCursorFromNode($item),
+                'node' => $item
+            ];
+            if ($cursor !== null) {
+                $cursor++;
+            }
+        }
+        return $edges;
+    }
+
+    protected function getHasPreviousPageFromRoot($root)
+    {
+        $hasPreviousPage = false;
+        if ($root instanceof LengthAwarePaginator) {
+            $hasPreviousPage = !$root->onFirstPage();
+        } elseif ($root instanceof AbstractPaginator) {
+            $hasPreviousPage = !$root->onFirstPage();
+        } elseif ($root instanceof EdgesCollection) {
+            $hasPreviousPage = $root->getHasPreviousPage();
+        }
+
+        return $hasPreviousPage;
+    }
+
+    protected function getHasNextPageFromRoot($root)
+    {
+        $hasNextPage = false;
+        if ($root instanceof LengthAwarePaginator) {
+            $hasNextPage = $root->hasMorePages();
+        } elseif ($root instanceof EdgesCollection) {
+            $hasNextPage = $root->getHasNextPage();
+        }
+
+        return $hasNextPage;
+    }
+
+    protected function getStartCursorFromRoot($root)
+    {
+        $startCursor = null;
+        if ($root instanceof EdgesCollection) {
+            $startCursor = $root->getStartCursor();
+        }
+
+        return $startCursor;
+    }
+
+    protected function getEndCursorFromRoot($root)
+    {
+        $endCursor = null;
+        if ($root instanceof EdgesCollection) {
+            $endCursor = $root->getEndCursor();
+        }
+
+        return $endCursor;
+    }
+
+    protected function getPageInfoFromRoot($root)
+    {
+        $hasPreviousPage = $this->getHasPreviousPageFromRoot($root);
+        $hasNextPage = $this->getHasNextPageFromRoot($root);
+        $startCursor = $this->getStartCursorFromRoot($root);
+        $endCursor = $this->getEndCursorFromRoot($root);
+        $edges = $startCursor === null || $endCursor === null ? $this->getEdgesFromRoot($root):null;
+
+        return [
+            'hasPreviousPage' => $hasPreviousPage,
+            'hasNextPage' => $hasNextPage,
+            'startCursor' => $startCursor !== null ? $startCursor:array_get($edges, '0.cursor'),
+            'endCursor' => $endCursor !== null ? $endCursor:array_get($edges, (sizeof($edges)-1).'.cursor')
+        ];
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/Support/Facades/Relay.php b/src/Folklore/GraphQL/Relay/Relay/Support/Facades/Relay.php
new file mode 100644
index 00000000..fbfdbf6a
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/Support/Facades/Relay.php
@@ -0,0 +1,16 @@
+<?php namespace Folklore\GraphQL\Relay\Support\Facades;
+
+use Illuminate\Support\Facades\Facade;
+
+class Relay extends Facade
+{
+    /**
+     * Get the registered name of the component.
+     *
+     * @return string
+     */
+    protected static function getFacadeAccessor()
+    {
+        return 'graphql.relay';
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/Support/InputType.php b/src/Folklore/GraphQL/Relay/Relay/Support/InputType.php
new file mode 100644
index 00000000..171af7dd
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/Support/InputType.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Support;
+
+use GraphQL\Type\Definition\Type;
+
+use Folklore\GraphQL\Support\InputType as BaseInputType;
+use Folklore\GraphQL\Relay\Support\Traits\HasClientMutationIdField;
+
+class InputType extends BaseInputType
+{
+    use HasClientMutationIdField;
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/Support/Mutation.php b/src/Folklore/GraphQL/Relay/Relay/Support/Mutation.php
new file mode 100644
index 00000000..2619ec81
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/Support/Mutation.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Support;
+
+use Folklore\GraphQL\Support\Mutation as BaseMutation;
+use Folklore\GraphQL\Relay\MutationResponse;
+
+class Mutation extends BaseMutation
+{
+    protected $inputType;
+
+    protected function inputType()
+    {
+        return null;
+    }
+
+    public function getInputType()
+    {
+        $inputType = $this->inputType();
+        return $inputType ? $inputType:$this->inputType;
+    }
+
+    public function setInputType($inputType)
+    {
+        $this->inputType = $inputType;
+    }
+
+    public function args()
+    {
+        return [
+            'input' => [
+                'name' => 'input',
+                'type' => $this->getInputType()
+            ]
+        ];
+    }
+
+    protected function getMutationResponse($response, $clientMutationId)
+    {
+        $mutationResponse = new MutationResponse();
+        $mutationResponse->setNode($response);
+        $mutationResponse->setClientMutationId($clientMutationId);
+
+        return $mutationResponse;
+    }
+
+    protected function resolveClientMutationId($root, $args)
+    {
+        return array_get($args, 'input.clientMutationId');
+    }
+
+    public function getResolver()
+    {
+        $resolver = parent::getResolver();
+
+        return function () use ($resolver) {
+            $args = func_get_args();
+            $response = call_user_func_array($resolver, $args);
+            $clientMutationId = call_user_func_array([$this, 'resolveClientMutationId'], $args);
+            $response = $this->getMutationResponse($response, $clientMutationId);
+            return $response;
+        };
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/Support/NodeContract.php b/src/Folklore/GraphQL/Relay/Relay/Support/NodeContract.php
new file mode 100644
index 00000000..580fcd4c
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/Support/NodeContract.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Support;
+
+interface NodeContract
+{
+    public function resolveById($id);
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/Support/NodeType.php b/src/Folklore/GraphQL/Relay/Relay/Support/NodeType.php
new file mode 100644
index 00000000..fe213894
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/Support/NodeType.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Support;
+
+use Folklore\GraphQL\Support\Type as BaseType;
+use Folklore\GraphQL\Relay\Support\Traits\TypeIsNode;
+
+abstract class NodeType extends BaseType implements NodeContract
+{
+    use TypeIsNode;
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/Support/PayloadType.php b/src/Folklore/GraphQL/Relay/Relay/Support/PayloadType.php
new file mode 100644
index 00000000..925b364f
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/Support/PayloadType.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Support;
+
+use Folklore\GraphQL\Support\Type as BaseType;
+use Folklore\GraphQL\Relay\Support\Traits\HasClientMutationIdField;
+
+class PayloadType extends BaseType
+{
+    use HasClientMutationIdField;
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/Support/Traits/FieldIsConnection.php b/src/Folklore/GraphQL/Relay/Relay/Support/Traits/FieldIsConnection.php
new file mode 100644
index 00000000..6866113e
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/Support/Traits/FieldIsConnection.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Support\Traits;
+
+use GraphQL\Type\Definition\Type;
+
+trait FieldIsConnection
+{
+    protected function connectionArgs()
+    {
+        return [
+            'first' => [
+                'name' => 'first',
+                'type' => Type::int()
+            ],
+            'last' => [
+                'name' => 'last',
+                'type' => Type::int()
+            ],
+            'after' => [
+                'name' => 'after',
+                'type' => Type::string()
+            ],
+            'before' => [
+                'name' => 'before',
+                'type' => Type::string()
+            ]
+        ];
+    }
+
+    public function getArgs()
+    {
+        $args = parent::getArgs();
+
+        $connectionArgs = $this->connectionArgs();
+
+        return array_merge($connectionArgs, $args);
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/Support/Traits/HasClientMutationIdField.php b/src/Folklore/GraphQL/Relay/Relay/Support/Traits/HasClientMutationIdField.php
new file mode 100644
index 00000000..1df513b9
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/Support/Traits/HasClientMutationIdField.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Support\Traits;
+
+use GraphQL;
+use Folklore\GraphQL\Relay\NodeIdField;
+use GraphQL\Type\Definition\Type;
+
+trait HasClientMutationIdField
+{
+    public function getFields()
+    {
+        $fields = parent::getFields();
+        
+        $fields['clientMutationId'] = [
+            'type' => Type::nonNull(Type::string())
+        ];
+        
+        return $fields;
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/Support/Traits/ResolvesFromQueryBuilder.php b/src/Folklore/GraphQL/Relay/Relay/Support/Traits/ResolvesFromQueryBuilder.php
new file mode 100644
index 00000000..2b61b5b8
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/Support/Traits/ResolvesFromQueryBuilder.php
@@ -0,0 +1,151 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Support\Traits;
+
+use GraphQL;
+use Relay;
+use Folklore\GraphQL\Relay\EdgesCollection;
+
+trait ResolvesFromQueryBuilder
+{
+    protected $queryBuilderResolver;
+
+    public function getQueryBuilderResolver()
+    {
+        return $this->queryBuilderResolver;
+    }
+
+    public function setQueryBuilderResolver($queryBuilderResolver)
+    {
+        $this->queryBuilderResolver = $queryBuilderResolver;
+        return $queryBuilderResolver;
+    }
+
+    protected function scopeAfter($query, $id)
+    {
+        $query->where('id', '>=', $id);
+    }
+
+    protected function scopeBefore($query, $id)
+    {
+        $query->where('id', '<=', $id);
+    }
+
+    protected function scopeFirst($query, $value)
+    {
+        $query->orderBy('id', 'ASC');
+        $query->take($value);
+    }
+
+    protected function scopeLast($query, $value)
+    {
+        $query->orderBy('id', 'DESC');
+        $query->take($value);
+    }
+
+    protected function getCountFromQuery($query)
+    {
+        $countQuery = clone $query;
+        if ($countQuery instanceof \Illuminate\Database\Eloquent\Relations\Relation) {
+            $countQuery->getBaseQuery()->orders = null;
+        } else if ($countQuery instanceof \Illuminate\Database\Eloquent\Builder) {
+            $countQuery->getQuery()->orders = null;
+        } else if( $countQuery instanceof \Illuminate\Database\Query\Builder) {
+            $countQuery->orders = null;
+        }
+        return $countQuery->count();
+    }
+
+    protected function resolveQueryBuilderFromRoot($root, $args)
+    {
+        if (method_exists($this, 'resolveQueryBuilder')) {
+            $queryBuilderResolver = [$this, 'resolveQueryBuilder'];
+        } else {
+            $queryBuilderResolver = $this->getQueryBuilderResolver();
+        }
+
+        if (!$queryBuilderResolver) {
+            return null;
+        }
+
+        $args = func_get_args();
+        return call_user_func_array($queryBuilderResolver, $args);
+    }
+
+    protected function resolveItemsFromQueryBuilder($query)
+    {
+        return $query->get();
+    }
+
+    protected function getCollectionFromItems($items, $offset, $limit, $total, $hasPreviousPage, $hasNextPage)
+    {
+        $collection = new EdgesCollection($items);
+        $collection->setTotal($total);
+        $collection->setStartCursor($offset);
+        $collection->setEndCursor($offset + $limit - 1);
+        $collection->setHasNextPage($hasNextPage);
+        $collection->setHasPreviousPage($hasPreviousPage);
+        return $collection;
+    }
+
+    public function resolve($root, $args)
+    {
+        // Get the query builder
+        $arguments = func_get_args();
+        $query = call_user_func_array([$this, 'resolveQueryBuilderFromRoot'], $arguments);
+
+        // If there is no query builder returned, try to use the parent resolve method.
+        if (!$query) {
+            if (method_exists('parent', 'resolve')) {
+                return call_user_func_array(['parent', 'resolve'], $arguments);
+            } else {
+                return null;
+            }
+        }
+
+        $after = array_get($args, 'after');
+        $before = array_get($args, 'before');
+        $first = array_get($args, 'first');
+        $last = array_get($args, 'last');
+
+        $count = $this->getCountFromQuery($query);
+        $offset = 0;
+        $limit = 0;
+
+        if ($first !== null) {
+            $limit = $first;
+            $offset = 0;
+            if ($after !== null) {
+                $offset = $after + 1;
+            }
+            if ($before !== null) {
+                $limit = min(max(0, $before - $offset), $limit);
+            }
+        } else if ($last !== null) {
+            $limit = $last;
+            $offset = $count - $limit;
+            if ($before !== null) {
+                $offset = max(0, $before - $limit);
+                $limit = min($before - $offset, $limit);
+            }
+            if ($after !== null) {
+                $d = max(0, $after + 1 - $offset);
+                $limit -= $d;
+                $offset += $d;
+            }
+        }
+        $offset = max(0, $offset);
+        $limit = min($count - $offset, $limit);
+
+        $query->skip($offset)->take($limit);
+
+        $hasNextPage = ($offset + $limit) < $count;
+        $hasPreviousPage = $offset > 0;
+
+        $resolveItemsArguments = array_merge([$query], $arguments);
+        $items = call_user_func_array([$this, 'resolveItemsFromQueryBuilder'], $resolveItemsArguments);
+        $collection = $this->getCollectionFromItems($items, $offset, $limit, $count, $hasPreviousPage, $hasNextPage);
+
+        return $collection;
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Relay/Support/Traits/TypeIsNode.php b/src/Folklore/GraphQL/Relay/Relay/Support/Traits/TypeIsNode.php
new file mode 100644
index 00000000..ce839e8a
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Relay/Support/Traits/TypeIsNode.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Support\Traits;
+
+use Folklore\GraphQL\Relay\NodeIdField;
+
+trait TypeIsNode
+{
+    public function getFieldsForObjectType()
+    {
+        $currentFields = parent::getFieldsForObjectType();
+        
+        $idResolver = $this->getIdResolverFromFields($currentFields);
+        $nodeIdField = $this->getNodeIdField();
+        $nodeIdField->setIdResolver($idResolver);
+        $currentFields['id'] = $nodeIdField->toArray();
+        
+        return $currentFields;
+    }
+    
+    protected function getNodeIdField()
+    {
+        $nodeIdField = new NodeIdField();
+        $nodeIdField->setIdType($this->name);
+        return $nodeIdField;
+    }
+    
+    protected function getIdResolverFromFields($fields)
+    {
+        $idResolver = null;
+        $originalResolver = array_get($fields, 'id.resolve');
+        if ($originalResolver) {
+            $idResolver = function () use ($originalResolver) {
+                $id = call_user_func_array($originalResolver, func_get_args());
+                return $id;
+            };
+        } else {
+            $idResolver = function ($root) {
+                return array_get($root, 'id');
+            };
+        }
+        
+        return $idResolver;
+    }
+    
+    protected function relayInterfaces()
+    {
+        return [
+            app('graphql')->type('Node')
+        ];
+    }
+    
+    public function getInterfaces()
+    {
+        $interfaces = parent::getInterfaces();
+        $relayInterfaces = $this->relayInterfaces();
+        return array_merge($interfaces, $relayInterfaces);
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/ServiceProvider.php b/src/Folklore/GraphQL/Relay/ServiceProvider.php
new file mode 100644
index 00000000..718314aa
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/ServiceProvider.php
@@ -0,0 +1,187 @@
+<?php namespace Folklore\GraphQL\Relay;
+
+use GraphQL\Validator\DocumentValidator;
+use Illuminate\Support\ServiceProvider as BaseServiceProvider;
+
+class ServiceProvider extends BaseServiceProvider
+{
+    /**
+     * Bootstrap any application services.
+     *
+     * @return void
+     */
+    public function boot()
+    {
+        $this->bootTypes();
+        
+        $this->bootSchemas();
+    }
+
+    /**
+     * Add schemas from config
+     *
+     * @return void
+     */
+    protected function bootSchemas()
+    {
+        $query = config('graphql.relay.query', []);
+        $schemas = config('graphql.relay.schemas');
+        if ($schemas === null) {
+            return null;
+        } elseif ($schemas === '*') {
+            $schemas = array_keys(config('graphql.schemas', []));
+        } else {
+            $schemas = (array)$schemas;
+        }
+        
+        $allSchemas = $this->app['graphql']->getSchemas();
+        foreach ($allSchemas as $name => $schema) {
+            if (!in_array($name, $schemas)) {
+                continue;
+            }
+            $schema['query'] = array_merge($schema['query'], $query);
+            $this->app['graphql']->addSchema($name, $schema);
+        }
+    }
+
+    /**
+     * Add types from config
+     *
+     * @return void
+     */
+    protected function bootTypes()
+    {
+        $types = config('graphql.relay.types');
+        if (is_array($types)) {
+            $this->app['graphql']->addTypes($types);
+        }
+    }
+
+    /**
+     * Register any application services.
+     *
+     * @return void
+     */
+    public function register()
+    {
+        $this->registerRelay();
+
+        $this->registerCommands();
+    }
+
+    /**
+     * Register Relay facade
+     *
+     * @return void
+     */
+    public function registerRelay()
+    {
+        $this->app->singleton('graphql.relay', function ($app) {
+            $relay = new Relay($app);
+            return $relay;
+        });
+    }
+
+    /**
+     * Register console commands
+     *
+     * @return void
+     */
+    public function registerCommands()
+    {
+        $commands = [
+            'MakeNode', 'MakeMutation', 'MakeInput', 'MakePayload', 'MakeConnection'
+        ];
+
+        // We'll simply spin through the list of commands that are migration related
+        // and register each one of them with an application container. They will
+        // be resolved in the Artisan start file and registered on the console.
+        foreach ($commands as $command) {
+            $this->{'register'.$command.'Command'}();
+        }
+        
+        $this->commands(
+            'command.relay.make.node',
+            'command.relay.make.mutation',
+            'command.relay.make.input',
+            'command.relay.make.payload',
+            'command.relay.make.connection'
+        );
+    }
+    
+    /**
+     * Register the "make:graphql:node" migration command.
+     *
+     * @return void
+     */
+    public function registerMakeNodeCommand()
+    {
+        $this->app->singleton('command.relay.make.node', function ($app) {
+            return new \Folklore\GraphQL\Relay\Console\NodeMakeCommand($app['files']);
+        });
+    }
+    
+    /**
+     * Register the "make:graphql:mutation" migration command.
+     *
+     * @return void
+     */
+    public function registerMakeMutationCommand()
+    {
+        $this->app->singleton('command.relay.make.mutation', function ($app) {
+            return new \Folklore\GraphQL\Relay\Console\MutationMakeCommand($app['files']);
+        });
+    }
+    
+    /**
+     * Register the "make:graphql:input" migration command.
+     *
+     * @return void
+     */
+    public function registerMakeInputCommand()
+    {
+        $this->app->singleton('command.relay.make.input', function ($app) {
+            return new \Folklore\GraphQL\Relay\Console\InputMakeCommand($app['files']);
+        });
+    }
+    
+    /**
+     * Register the "make:graphql:payload" migration command.
+     *
+     * @return void
+     */
+    public function registerMakePayloadCommand()
+    {
+        $this->app->singleton('command.relay.make.payload', function ($app) {
+            return new \Folklore\GraphQL\Relay\Console\PayloadMakeCommand($app['files']);
+        });
+    }
+    
+    /**
+     * Register the "make:graphql:payload" migration command.
+     *
+     * @return void
+     */
+    public function registerMakeConnectionCommand()
+    {
+        $this->app->singleton('command.relay.make.connection', function ($app) {
+            return new \Folklore\GraphQL\Relay\Console\ConnectionMakeCommand($app['files']);
+        });
+    }
+
+    /**
+     * Get the services provided by the provider.
+     *
+     * @return array
+     */
+    public function provides()
+    {
+        return [
+            'graphql.relay',
+            'command.relay.make.node',
+            'command.relay.make.input',
+            'command.relay.make.payload',
+            'command.relay.make.connection'
+        ];
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Support/ConnectionField.php b/src/Folklore/GraphQL/Relay/Support/ConnectionField.php
new file mode 100644
index 00000000..9e911179
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Support/ConnectionField.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Support;
+
+use Folklore\GraphQL\Support\Field as BaseField;
+use Folklore\GraphQL\Relay\Support\Traits\FieldIsConnection;
+use Folklore\GraphQL\Relay\Support\Traits\ResolvesFromQueryBuilder;
+use GraphQL;
+
+class ConnectionField extends BaseField
+{
+    use FieldIsConnection, ResolvesFromQueryBuilder;
+}
diff --git a/src/Folklore/GraphQL/Relay/Support/ConnectionType.php b/src/Folklore/GraphQL/Relay/Support/ConnectionType.php
new file mode 100644
index 00000000..e4362aa2
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Support/ConnectionType.php
@@ -0,0 +1,165 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Support;
+
+use GraphQL\Type\Definition\Type;
+use GraphQL\Type\Definition\InterfaceType;
+use Folklore\GraphQL\Support\Type as BaseType;
+
+use Folklore\GraphQL\Relay\EdgesCollection;
+use Illuminate\Pagination\AbstractPaginator;
+use Illuminate\Pagination\LengthAwarePaginator;
+
+class ConnectionType extends BaseType
+{
+    protected $edgeType;
+
+    protected function edgeType()
+    {
+        return null;
+    }
+
+    public function fields()
+    {
+        return [
+            'total' => [
+                'type' => Type::int(),
+                'resolve' => function ($root) {
+                    return $this->getTotalFromRoot($root);
+                }
+            ],
+            'edges' => [
+                'type' => Type::listOf($this->getEdgeObjectType()),
+                'resolve' => function ($root) {
+                    return $this->getEdgesFromRoot($root);
+                }
+            ],
+            'pageInfo' => [
+                'type' => app('graphql')->type('PageInfo'),
+                'resolve' => function ($root) {
+                    return $this->getPageInfoFromRoot($root);
+                }
+            ]
+        ];
+    }
+
+    public function getEdgeType()
+    {
+        $edgeType = $this->edgeType();
+        return $edgeType ? $edgeType:$this->edgeType;
+    }
+
+    public function setEdgeType($edgeType)
+    {
+        $this->edgeType = $edgeType;
+        return $this;
+    }
+
+    protected function getEdgeObjectType()
+    {
+        $edgeType = $this->getEdgeType();
+        $name = $edgeType->config['name'].'Edge';
+        app('graphql')->addType(\Folklore\GraphQL\Relay\ConnectionEdgeType::class, $name);
+        $type = app('graphql')->type($name);
+        $type->setEdgeType($edgeType);
+        return $type;
+    }
+
+    protected function getCursorFromNode($edge)
+    {
+        $edgeType = $this->getEdgeType();
+        if ($edgeType instanceof InterfaceType) {
+            $edgeType = $edgeType->config['resolveType']($edge);
+        }
+        $resolveId = $edgeType->getField('id')->resolveFn;
+        return $resolveId($edge);
+    }
+
+    protected function getTotalFromRoot($root)
+    {
+        $total = 0;
+        if ($root instanceof EdgesCollection) {
+            $total = $root->getTotal();
+        }
+        return $total;
+    }
+
+
+    protected function getEdgesFromRoot($root)
+    {
+        $cursor = $this->getStartCursorFromRoot($root);
+        $edges = [];
+        foreach ($root as $item) {
+            $edges[] = [
+                'cursor' => $cursor !== null ? $cursor:$this->getCursorFromNode($item),
+                'node' => $item
+            ];
+            if ($cursor !== null) {
+                $cursor++;
+            }
+        }
+        return $edges;
+    }
+
+    protected function getHasPreviousPageFromRoot($root)
+    {
+        $hasPreviousPage = false;
+        if ($root instanceof LengthAwarePaginator) {
+            $hasPreviousPage = !$root->onFirstPage();
+        } elseif ($root instanceof AbstractPaginator) {
+            $hasPreviousPage = !$root->onFirstPage();
+        } elseif ($root instanceof EdgesCollection) {
+            $hasPreviousPage = $root->getHasPreviousPage();
+        }
+
+        return $hasPreviousPage;
+    }
+
+    protected function getHasNextPageFromRoot($root)
+    {
+        $hasNextPage = false;
+        if ($root instanceof LengthAwarePaginator) {
+            $hasNextPage = $root->hasMorePages();
+        } elseif ($root instanceof EdgesCollection) {
+            $hasNextPage = $root->getHasNextPage();
+        }
+
+        return $hasNextPage;
+    }
+
+    protected function getStartCursorFromRoot($root)
+    {
+        $startCursor = null;
+        if ($root instanceof EdgesCollection) {
+            $startCursor = $root->getStartCursor();
+        }
+
+        return $startCursor;
+    }
+
+    protected function getEndCursorFromRoot($root)
+    {
+        $endCursor = null;
+        if ($root instanceof EdgesCollection) {
+            $endCursor = $root->getEndCursor();
+        }
+
+        return $endCursor;
+    }
+
+    protected function getPageInfoFromRoot($root)
+    {
+        $hasPreviousPage = $this->getHasPreviousPageFromRoot($root);
+        $hasNextPage = $this->getHasNextPageFromRoot($root);
+        $startCursor = $this->getStartCursorFromRoot($root);
+        $endCursor = $this->getEndCursorFromRoot($root);
+        $edges = $startCursor === null || $endCursor === null ? $this->getEdgesFromRoot($root):null;
+
+        return [
+            'hasPreviousPage' => $hasPreviousPage,
+            'hasNextPage' => $hasNextPage,
+            'startCursor' => $startCursor !== null ? $startCursor:array_get($edges, '0.cursor'),
+            'endCursor' => $endCursor !== null ? $endCursor:array_get($edges, (sizeof($edges)-1).'.cursor')
+        ];
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Support/Facades/Relay.php b/src/Folklore/GraphQL/Relay/Support/Facades/Relay.php
new file mode 100644
index 00000000..fbfdbf6a
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Support/Facades/Relay.php
@@ -0,0 +1,16 @@
+<?php namespace Folklore\GraphQL\Relay\Support\Facades;
+
+use Illuminate\Support\Facades\Facade;
+
+class Relay extends Facade
+{
+    /**
+     * Get the registered name of the component.
+     *
+     * @return string
+     */
+    protected static function getFacadeAccessor()
+    {
+        return 'graphql.relay';
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Support/InputType.php b/src/Folklore/GraphQL/Relay/Support/InputType.php
new file mode 100644
index 00000000..171af7dd
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Support/InputType.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Support;
+
+use GraphQL\Type\Definition\Type;
+
+use Folklore\GraphQL\Support\InputType as BaseInputType;
+use Folklore\GraphQL\Relay\Support\Traits\HasClientMutationIdField;
+
+class InputType extends BaseInputType
+{
+    use HasClientMutationIdField;
+}
diff --git a/src/Folklore/GraphQL/Relay/Support/Mutation.php b/src/Folklore/GraphQL/Relay/Support/Mutation.php
new file mode 100644
index 00000000..2619ec81
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Support/Mutation.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Support;
+
+use Folklore\GraphQL\Support\Mutation as BaseMutation;
+use Folklore\GraphQL\Relay\MutationResponse;
+
+class Mutation extends BaseMutation
+{
+    protected $inputType;
+
+    protected function inputType()
+    {
+        return null;
+    }
+
+    public function getInputType()
+    {
+        $inputType = $this->inputType();
+        return $inputType ? $inputType:$this->inputType;
+    }
+
+    public function setInputType($inputType)
+    {
+        $this->inputType = $inputType;
+    }
+
+    public function args()
+    {
+        return [
+            'input' => [
+                'name' => 'input',
+                'type' => $this->getInputType()
+            ]
+        ];
+    }
+
+    protected function getMutationResponse($response, $clientMutationId)
+    {
+        $mutationResponse = new MutationResponse();
+        $mutationResponse->setNode($response);
+        $mutationResponse->setClientMutationId($clientMutationId);
+
+        return $mutationResponse;
+    }
+
+    protected function resolveClientMutationId($root, $args)
+    {
+        return array_get($args, 'input.clientMutationId');
+    }
+
+    public function getResolver()
+    {
+        $resolver = parent::getResolver();
+
+        return function () use ($resolver) {
+            $args = func_get_args();
+            $response = call_user_func_array($resolver, $args);
+            $clientMutationId = call_user_func_array([$this, 'resolveClientMutationId'], $args);
+            $response = $this->getMutationResponse($response, $clientMutationId);
+            return $response;
+        };
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Support/NodeContract.php b/src/Folklore/GraphQL/Relay/Support/NodeContract.php
new file mode 100644
index 00000000..580fcd4c
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Support/NodeContract.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Support;
+
+interface NodeContract
+{
+    public function resolveById($id);
+}
diff --git a/src/Folklore/GraphQL/Relay/Support/NodeType.php b/src/Folklore/GraphQL/Relay/Support/NodeType.php
new file mode 100644
index 00000000..fe213894
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Support/NodeType.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Support;
+
+use Folklore\GraphQL\Support\Type as BaseType;
+use Folklore\GraphQL\Relay\Support\Traits\TypeIsNode;
+
+abstract class NodeType extends BaseType implements NodeContract
+{
+    use TypeIsNode;
+}
diff --git a/src/Folklore/GraphQL/Relay/Support/PayloadType.php b/src/Folklore/GraphQL/Relay/Support/PayloadType.php
new file mode 100644
index 00000000..925b364f
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Support/PayloadType.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Support;
+
+use Folklore\GraphQL\Support\Type as BaseType;
+use Folklore\GraphQL\Relay\Support\Traits\HasClientMutationIdField;
+
+class PayloadType extends BaseType
+{
+    use HasClientMutationIdField;
+}
diff --git a/src/Folklore/GraphQL/Relay/Support/Traits/FieldIsConnection.php b/src/Folklore/GraphQL/Relay/Support/Traits/FieldIsConnection.php
new file mode 100644
index 00000000..6866113e
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Support/Traits/FieldIsConnection.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Support\Traits;
+
+use GraphQL\Type\Definition\Type;
+
+trait FieldIsConnection
+{
+    protected function connectionArgs()
+    {
+        return [
+            'first' => [
+                'name' => 'first',
+                'type' => Type::int()
+            ],
+            'last' => [
+                'name' => 'last',
+                'type' => Type::int()
+            ],
+            'after' => [
+                'name' => 'after',
+                'type' => Type::string()
+            ],
+            'before' => [
+                'name' => 'before',
+                'type' => Type::string()
+            ]
+        ];
+    }
+
+    public function getArgs()
+    {
+        $args = parent::getArgs();
+
+        $connectionArgs = $this->connectionArgs();
+
+        return array_merge($connectionArgs, $args);
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Support/Traits/HasClientMutationIdField.php b/src/Folklore/GraphQL/Relay/Support/Traits/HasClientMutationIdField.php
new file mode 100644
index 00000000..1df513b9
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Support/Traits/HasClientMutationIdField.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Support\Traits;
+
+use GraphQL;
+use Folklore\GraphQL\Relay\NodeIdField;
+use GraphQL\Type\Definition\Type;
+
+trait HasClientMutationIdField
+{
+    public function getFields()
+    {
+        $fields = parent::getFields();
+        
+        $fields['clientMutationId'] = [
+            'type' => Type::nonNull(Type::string())
+        ];
+        
+        return $fields;
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Support/Traits/ResolvesFromQueryBuilder.php b/src/Folklore/GraphQL/Relay/Support/Traits/ResolvesFromQueryBuilder.php
new file mode 100644
index 00000000..78d7046c
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Support/Traits/ResolvesFromQueryBuilder.php
@@ -0,0 +1,160 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Support\Traits;
+
+use GraphQL;
+use Relay;
+use Folklore\GraphQL\Relay\EdgesCollection;
+
+trait ResolvesFromQueryBuilder
+{
+    protected $queryBuilderResolver;
+
+    public function getQueryBuilderResolver()
+    {
+        return $this->queryBuilderResolver;
+    }
+
+    public function setQueryBuilderResolver($queryBuilderResolver)
+    {
+        $this->queryBuilderResolver = $queryBuilderResolver;
+        return $queryBuilderResolver;
+    }
+
+    protected function scopeAfter($query, $id)
+    {
+        $query->where('id', '>=', $id);
+    }
+
+    protected function scopeBefore($query, $id)
+    {
+        $query->where('id', '<=', $id);
+    }
+
+    protected function scopeFirst($query, $value)
+    {
+        $query->orderBy('id', 'ASC');
+        $query->take($value);
+    }
+
+    protected function scopeLast($query, $value)
+    {
+        $query->orderBy('id', 'DESC');
+        $query->take($value);
+    }
+
+    protected function getCountFromQuery($query)
+    {
+        $countQuery = clone $query;
+        if ($countQuery instanceof \Illuminate\Database\Eloquent\Relations\Relation) {
+            $countQuery->getBaseQuery()->orders = null;
+        } else if ($countQuery instanceof \Illuminate\Database\Eloquent\Builder) {
+            $countQuery->getQuery()->orders = null;
+        } else if( $countQuery instanceof \Illuminate\Database\Query\Builder) {
+            $countQuery->orders = null;
+        }
+        return $countQuery->count();
+    }
+
+    protected function resolveQueryBuilderFromRoot($root, $args)
+    {
+        if (method_exists($this, 'resolveQueryBuilder')) {
+            $queryBuilderResolver = [$this, 'resolveQueryBuilder'];
+        } else {
+            $queryBuilderResolver = $this->getQueryBuilderResolver();
+        }
+
+        if (!$queryBuilderResolver) {
+            return null;
+        }
+
+        $args = func_get_args();
+        return call_user_func_array($queryBuilderResolver, $args);
+    }
+
+    protected function resolveItemsFromQueryBuilder($query)
+    {
+        /* @var $query \Illuminate\Database\Eloquent\Builder */
+//        throw new \Exception("Query class is " . $query->toSql());
+
+        return $query->get();
+    }
+
+    protected function getCollectionFromItems($items, $offset, $limit, $total, $hasPreviousPage, $hasNextPage)
+    {
+        $collection = new EdgesCollection($items);
+        $collection->setTotal($total);
+        $collection->setStartCursor($offset);
+        $collection->setEndCursor($offset + $limit - 1);
+        $collection->setHasNextPage($hasNextPage);
+        $collection->setHasPreviousPage($hasPreviousPage);
+        return $collection;
+    }
+
+    public function resolve($root, $args)
+    {
+        // Get the query builder
+        $arguments = func_get_args();
+        $query = call_user_func_array([$this, 'resolveQueryBuilderFromRoot'], $arguments);
+
+        // If there is no query builder returned, try to use the parent resolve method.
+        if (!$query) {
+            if (method_exists('parent', 'resolve')) {
+                return call_user_func_array(['parent', 'resolve'], $arguments);
+            } else {
+                return null;
+            }
+        }
+
+        $after = array_get($args, 'after');
+        $before = array_get($args, 'before');
+        $first = array_get($args, 'first');
+        $last = array_get($args, 'last');
+
+        $count = $this->getCountFromQuery($query);
+        $offset = 0;
+        $limit = 0;
+
+        if ($first !== null) {
+            $limit = $first;
+            $offset = 0;
+            if ($after !== null) {
+                $offset = $after + 1;
+            }
+            if ($before !== null) {
+                $limit = min(max(0, $before - $offset), $limit);
+            }
+        } else if ($last !== null) {
+            $limit = $last;
+            $offset = $count - $limit;
+            if ($before !== null) {
+                $offset = max(0, $before - $limit);
+                $limit = min($before - $offset, $limit);
+            }
+            if ($after !== null) {
+                $d = max(0, $after + 1 - $offset);
+                $limit -= $d;
+                $offset += $d;
+            }
+        }
+        $offset = max(0, $offset);
+        $limit = min($count - $offset, $limit);
+
+        if ($offset) {
+            $query->skip($offset);
+        }
+        if ($limit) {
+            $query->take($limit);
+        }
+//        $query->skip($offset)->take($limit);
+
+        $hasNextPage = ($offset + $limit) < $count;
+        $hasPreviousPage = $offset > 0;
+
+        $resolveItemsArguments = array_merge([$query], $arguments);
+        $items = call_user_func_array([$this, 'resolveItemsFromQueryBuilder'], $resolveItemsArguments);
+        $collection = $this->getCollectionFromItems($items, $offset, $limit, $count, $hasPreviousPage, $hasNextPage);
+
+        return $collection;
+    }
+}
diff --git a/src/Folklore/GraphQL/Relay/Support/Traits/TypeIsNode.php b/src/Folklore/GraphQL/Relay/Support/Traits/TypeIsNode.php
new file mode 100644
index 00000000..ce839e8a
--- /dev/null
+++ b/src/Folklore/GraphQL/Relay/Support/Traits/TypeIsNode.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Folklore\GraphQL\Relay\Support\Traits;
+
+use Folklore\GraphQL\Relay\NodeIdField;
+
+trait TypeIsNode
+{
+    public function getFieldsForObjectType()
+    {
+        $currentFields = parent::getFieldsForObjectType();
+        
+        $idResolver = $this->getIdResolverFromFields($currentFields);
+        $nodeIdField = $this->getNodeIdField();
+        $nodeIdField->setIdResolver($idResolver);
+        $currentFields['id'] = $nodeIdField->toArray();
+        
+        return $currentFields;
+    }
+    
+    protected function getNodeIdField()
+    {
+        $nodeIdField = new NodeIdField();
+        $nodeIdField->setIdType($this->name);
+        return $nodeIdField;
+    }
+    
+    protected function getIdResolverFromFields($fields)
+    {
+        $idResolver = null;
+        $originalResolver = array_get($fields, 'id.resolve');
+        if ($originalResolver) {
+            $idResolver = function () use ($originalResolver) {
+                $id = call_user_func_array($originalResolver, func_get_args());
+                return $id;
+            };
+        } else {
+            $idResolver = function ($root) {
+                return array_get($root, 'id');
+            };
+        }
+        
+        return $idResolver;
+    }
+    
+    protected function relayInterfaces()
+    {
+        return [
+            app('graphql')->type('Node')
+        ];
+    }
+    
+    public function getInterfaces()
+    {
+        $interfaces = parent::getInterfaces();
+        $relayInterfaces = $this->relayInterfaces();
+        return array_merge($interfaces, $relayInterfaces);
+    }
+}

From 21ee2cfd64b17f5046cb05c4fbb26e0d761c447a Mon Sep 17 00:00:00 2001
From: Erik Van Kelst <4levels@gmail.com>
Date: Thu, 15 Mar 2018 13:22:02 +0100
Subject: [PATCH 2/2] Minor cleanup

---
 .../GraphQL/Relay/Support/Traits/ResolvesFromQueryBuilder.php    | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/Folklore/GraphQL/Relay/Support/Traits/ResolvesFromQueryBuilder.php b/src/Folklore/GraphQL/Relay/Support/Traits/ResolvesFromQueryBuilder.php
index 78d7046c..159d011c 100644
--- a/src/Folklore/GraphQL/Relay/Support/Traits/ResolvesFromQueryBuilder.php
+++ b/src/Folklore/GraphQL/Relay/Support/Traits/ResolvesFromQueryBuilder.php
@@ -146,7 +146,6 @@ public function resolve($root, $args)
         if ($limit) {
             $query->take($limit);
         }
-//        $query->skip($offset)->take($limit);
 
         $hasNextPage = ($offset + $limit) < $count;
         $hasPreviousPage = $offset > 0;