├── .gitignore
├── client.html
├── package.json
├── server.js
├── queries.gql
├── resolvers.js
├── data.js
├── typeDefs.js
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/client.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | GraphQL Fantasy API Client
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "graphql-fantasy-api",
3 | "dependencies": {
4 | "@graphql-tools/schema": "^8.1.2",
5 | "express": "^4.17.1",
6 | "express-graphql": "^0.12.0",
7 | "graphql": "^15.5.1",
8 | "nodemon": "^2.0.12"
9 | },
10 | "main": "server.js",
11 | "scripts": {
12 | "dev": "nodemon server.js"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const graphQL = require('graphql')
3 | const { graphqlHTTP } = require('express-graphql')
4 | const { makeExecutableSchema } = require('@graphql-tools/schema')
5 |
6 | const data = require('./data.js')
7 | const { typeDefs } = require('./typeDefs.js')
8 | const { resolvers } = require('./resolvers.js')
9 |
10 | const executableSchema = makeExecutableSchema({
11 | typeDefs,
12 | resolvers,
13 | })
14 |
15 | const app = express()
16 | app.use(express.json())
17 | app.use(express.urlencoded({ extended: true }))
18 | app.use(
19 | '/graphql',
20 | graphqlHTTP({
21 | schema: executableSchema,
22 | context: data,
23 | graphiql: true,
24 | })
25 | )
26 |
27 | app.listen(4000, () => {
28 | console.log('Running a GraphQL API server at http://localhost:4000/graphql')
29 | })
30 |
--------------------------------------------------------------------------------
/queries.gql:
--------------------------------------------------------------------------------
1 | query FetchWizards {
2 | wizards {
3 | name
4 | level
5 | race
6 | job
7 | spells {
8 | name
9 | attack
10 | range
11 | }
12 | }
13 | }
14 |
15 | query FetchSpecificWizardQuery {
16 | wizard(id: "1") {
17 | name
18 | }
19 | }
20 |
21 | # Pass data in Query Variables
22 | query FetchSomeWizard($wizardId: ID!) {
23 | wizard(id: $wizardId) {
24 | id
25 | name
26 | }
27 | }
28 |
29 | query FetchAllCharacters {
30 | characters {
31 | ... on Fighter {
32 | id
33 | name
34 | }
35 | ... on Wizard {
36 | id
37 | name
38 | }
39 | }
40 | }
41 |
42 | mutation AddCharacter($input: CharacterInput!) {
43 | addCharacter(input: $input) {
44 | __typename
45 | ... on Fighter {
46 | id
47 | name
48 | race
49 | weapon {
50 | name
51 | }
52 | }
53 | ... on Wizard {
54 | id
55 | name
56 | race
57 | spells {
58 | name
59 | }
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/resolvers.js:
--------------------------------------------------------------------------------
1 | const resolvers = {
2 | Query: {
3 | fighter: (obj, { id }, context) => context.fighters.find(fighter => fighter.id === id),
4 | fighters: (obj, args, context) => context.fighters,
5 | wizard: (obj, { id }, context) => context.wizards.find(wizard => wizard.id === id),
6 | wizards: (obj, args, context) => context.wizards,
7 | characters: (obj, args, context) => context.characters,
8 | },
9 |
10 | Mutation: {
11 | addCharacter: (obj, { input }, context) => {
12 | const newCharacter = {
13 | id: Math.floor(Math.random() * 100).toString(),
14 | ...input,
15 | level: 1,
16 | }
17 |
18 | if (input.job === 'FIGHTER') {
19 | context.fighters.push(newCharacter)
20 | }
21 |
22 | if (input.job === 'WIZARD') {
23 | context.wizards.push({ ...newCharacter, spells: [] })
24 | }
25 |
26 | return newCharacter
27 | },
28 | },
29 |
30 | CharacterType: {
31 | __resolveType: (obj, context, info) => {
32 | if (obj.job === 'FIGHTER') {
33 | return 'Fighter'
34 | }
35 |
36 | if (obj.job === 'WIZARD') {
37 | return 'Wizard'
38 | }
39 |
40 | return null
41 | },
42 | },
43 | }
44 |
45 | module.exports = { resolvers }
46 |
--------------------------------------------------------------------------------
/data.js:
--------------------------------------------------------------------------------
1 | // Enums
2 | const races = {
3 | HUMAN: 'HUMAN',
4 | DWARF: 'DWARF',
5 | ELF: 'ELF',
6 | }
7 |
8 | const jobs = {
9 | FIGHTER: 'FIGHTER',
10 | WIZARD: 'WIZARD',
11 | }
12 |
13 | // Equipment and Abilities
14 | const weapons = [
15 | { name: 'Sword', attack: 3, range: 1 },
16 | { name: 'Bow', attack: 2, range: 3 },
17 | { name: 'Axe', attack: 4, range: 1 },
18 | ]
19 |
20 | const spells = [
21 | { name: 'Lightning Bolt', attack: 2, range: 2 },
22 | { name: 'Ice Storm', attack: 2, range: 2, effect: 'Frozen' },
23 | { name: 'Fireball', attack: 2, range: 2 },
24 | ]
25 |
26 | // Individual heroes
27 | const fighters = [
28 | {
29 | id: '1',
30 | name: 'Arthur',
31 | level: 1,
32 | weapon: weapons.find(weapon => weapon.name === 'Sword'),
33 | race: races.HUMAN,
34 | job: jobs.FIGHTER,
35 | __typename: 'Fighter',
36 | },
37 | {
38 | id: '2',
39 | name: 'Gimli',
40 | level: 1,
41 | weapon: weapons.find(weapon => weapon.name === 'Axe'),
42 | race: races.DWARF,
43 | job: jobs.FIGHTER,
44 | __typename: 'Fighter',
45 | },
46 | ]
47 |
48 | const wizards = [
49 | {
50 | id: '1',
51 | name: 'Merlin',
52 | level: 1,
53 | spells,
54 | race: races.HUMAN,
55 | job: jobs.WIZARD,
56 | __typename: 'Wizard',
57 | },
58 | {
59 | id: '2',
60 | name: 'Gandalf',
61 | level: 1,
62 | spells,
63 | race: races.HUMAN,
64 | job: jobs.WIZARD,
65 | __typename: 'Wizard',
66 | },
67 | ]
68 |
69 | const characters = [...fighters, ...wizards]
70 |
71 | module.exports = {
72 | races,
73 | jobs,
74 | weapons,
75 | spells,
76 | fighters,
77 | wizards,
78 | characters,
79 | }
80 |
--------------------------------------------------------------------------------
/typeDefs.js:
--------------------------------------------------------------------------------
1 | const typeDefs = `
2 | "The job class of the character."
3 | enum Job {
4 | FIGHTER
5 | WIZARD
6 | }
7 |
8 | "The species or ancestry of the character."
9 | enum Race {
10 | HUMAN
11 | ELF
12 | DWARF
13 | }
14 |
15 | "A valiant weapon wielded by a fighter."
16 | type Weapon {
17 | name: String!
18 | attack: Int
19 | range: Int
20 | }
21 |
22 | "A powerful spell that a wizard can read from a scroll."
23 | type Spell {
24 | name: String!
25 | attack: Int
26 | range: Int
27 | effect: String
28 | }
29 |
30 | "A hero on a quest."
31 | interface Character {
32 | id: ID!
33 | name: String!
34 | level: Int!
35 | race: Race
36 | job: Job
37 | }
38 |
39 | "A hero with direct combat ability and strength."
40 | type Fighter implements Character {
41 | id: ID!
42 | name: String!
43 | level: Int!
44 | race: Race
45 | job: Job!
46 | weapon: Weapon
47 | }
48 |
49 | "A hero with a variety of magical powers."
50 | type Wizard implements Character {
51 | id: ID!
52 | name: String!
53 | level: Int!
54 | race: Race
55 | job: Job!
56 | spells: [Spell]
57 | }
58 |
59 | input CharacterInput {
60 | name: String!
61 | race: Race
62 | job: Job
63 | }
64 |
65 | union CharacterType = Wizard | Fighter
66 |
67 | type Mutation {
68 | addCharacter(input: CharacterInput): CharacterType
69 | }
70 |
71 | type Query {
72 | fighters: [Fighter]
73 | fighter(id: ID!): Fighter
74 | wizards: [Wizard]
75 | wizard(id: ID!): Wizard
76 | characters: [CharacterType]
77 | }
78 | `
79 |
80 | module.exports = { typeDefs }
81 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Outline
2 |
3 | GraphQL Fantasy API
4 |
5 | ## An Overview of GraphQL
6 |
7 | - What is GraphQL?
8 | - GraphQL vs. REST
9 | - GraphQL server and client
10 | - Important Concepts and Terminology
11 | - Schema
12 | - Resolvers
13 | - Types
14 | - Queries
15 | - Mutations
16 |
17 | ## Setting up a GraphQL API Server and Client (Node)
18 |
19 | Tutorial walkthrough.
20 |
21 | - Server
22 | - Create a simple back end (Node/Express) that returns a single `/graphql` endpoint
23 | - Using "in memory" data store to avoid overhead of databases
24 | - Using Node 16/module type to avoid overhead of webpack
25 | - Client
26 | - Create a simple front end (vanilla JS? React?) that queries the `/graphql` endpoint
27 | - GraphiQL
28 | -Using GraphiQL for local development testing
29 |
30 | ## Creating a GraphQL Schema
31 |
32 | Using Fantasy/RPG data for example schema. Build it into the graphQL server above.
33 |
34 | - Type System
35 | - GraphQL Object Type (`type`, fields (properties))
36 | - Scalar Type (aka primitives, `String`, `Int`, `Boolean`, `Float`)
37 | - Enumeration (`enum`)
38 | - Interface/Union Type (`interface`, `|` (union))
39 | - Type Modifiers (`!` (non-nullable) and `[]` (list))
40 | - Entrypoint (Operation) Types (`Query` and `Mutation`)
41 | - Comments/description (`""`)
42 |
43 | ## Using GraphQL Queries, Mutations, and Resolvers
44 |
45 | - Queries (read)
46 | - Simple query (no name)
47 | - Operation names (`query`)
48 | - Aliases
49 | - Arguments
50 | - hard-coded
51 | - query parameter
52 | - directives
53 | - Fragments (`fragment` and `...`)
54 | - Mutations (write)
55 | - Input Types (`input`)
56 | - Resolvers
57 | - Resolver functions
58 | - `resolveType`
59 | - Context
60 |
61 | ## Additional Information
62 |
63 | - Pagination: Connections Pattern - Edges and Nodes
64 | - Introspection (`__schema`, `__type`, `name`, `kind`, `ofType`)
65 |
--------------------------------------------------------------------------------