This repository contains full walkthrough creating GraphQL Server.
Technologies used:
Proceed with steps below.
You can clone repo and checkout to commit with particular step
$ git clone https://github.com/G3F4/express-graphql-workshop.git
$ cd express-graphql-workshop
$ git reset --hard $changeId
... or do everything step by step :)
-
Create project folder.
$ touch express-graphql-workshop $ cd express-graphql-workshop -
Initialize npm.
$ npm init -
Install
babel-registerandbabel-preset-es2015with npm saving dependencies(to use es2015+ features).$ npm i babel-register --save $ npm i babel-preset-es2015 --save -
Create the entry file
index.jsin root folder and within:$ touch index.js -
and within use require to import
babel-register(creates hook):require('babel-register')({ "presets": ["es2015"] });
-
Install
expresswith npm and save flag and createserver.jsfile ...$ npm i express --save $ touch server.js -
... and within:
-
import
expressimport express from 'express';
-
define port number
const PORT = 30001;
-
create
expressapplication and start to listeningexpress() .listen(PORT, () => console.log(`Server listening on localhost:${PORT}`));
-
-
In
index.jsrequireserver.js:require('./server.js'); -
Add script starting server to
package.json."dev": "node index.js" -
Start server.
$ npm run dev
-
Using npm install
graphqlandexpress-graphql(Lee Byron Express app setup for graphql server).$ npm i graphql express-graphql --save
-
Create
graphqlfolder andschema.jsfile in it.$ mkdir graphql && cd graphql $ touch schema.js
-
In
schema.js:- import basic types needed to create test schema
import { GraphQLSchema, GraphQLObjectType, GraphQLString } from 'graphql';
- create new
schemawith single fieldtestof string type, resolving static literal
const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'query', fields: { test: { type: GraphQLString, resolve: () => 'test' } } }) });
- export
schema
export default schema;
-
In
server.js:- import
express-graphqlandschema
import graphqlHTTP from 'express-graphql'; import schema from './graphql/schema';
- use
express-graphqlto process query document on/graphqlroute
express() .use('/graphql', graphqlHTTP({ schema, graphiql: true })) .listen(PORT, () => console.log(`Server listening on localhost:${PORT}`));
- import
-
Open browser on localhost:30001 and query for test field!
-
Create
query.jsfile ingraphqlfolder.$ cd graphql $ touch query.js -
In
query.js:- import necessary
import { GraphQLObjectType, GraphQLString } from 'graphql';
- create
schemawith two fields:participantandeventof string type resolving static string for now.
const query = new GraphQLObjectType({ name: 'Query', fields: { participant: { resolve: () => 'participant', type: GraphQLString, }, event: { resolve: () => 'event', type: GraphQLString, } } });
- export
query
export default query;
-
In
schema.js:- reduce imports from
graphqland importquery
import { GraphQLSchema } from 'graphql'; import query from './query';
- change
schemadefinition to use importedquery
const schema = new GraphQLSchema({ query });
- reduce imports from
-
Create
typessubfolder ingraphqlfolder. Then createevent-type.jsandparticipant-type.jsfiles in it.$ cd graphql $ mkdir types && cd touch $ touch event-type.js participant-type.js
-
In
event-type.js"- import necessary
GraphQLtypes andParticipantType(we will use it to resolve event participants)
import { GraphQLObjectType, GraphQLString, GraphQLID, GraphQLList } from 'graphql'; import ParticipantType from './participant-type';
- create new type with 3 fields:
idof ID type, resolving static literal for nownameof string type, resolving static literal for nowparticipantstype of list ofParticipantType, resolving array with empty string for now
const EventType = new GraphQLObjectType({ name: 'event', fields: () => ({ id: { type: GraphQLID, resolve: () => 'id' }, name: { type: GraphQLString, resolve: () => 'name' }, participants: { type: new GraphQLList(ParticipantType), resolve: () => [''] } }) });
- export
EventType
export default EventType;
- import necessary
-
Do the same for
ParticipantTypewith fields(importEventTypeinstead ofParticipantType):idof ID type, resolving static literal for nownameof string type, resolving static literal for nowfriendstype of list ofParticipantType, resolving array with empty string for noweventstype of list ofEventType, resolving array with empty string for now
import { GraphQLObjectType, GraphQLString, GraphQLID, GraphQLList } from 'graphql'; import EventType from './EventType'; const ParticipantType = new GraphQLObjectType({ name: 'participant', fields: () => ({ id: { type: GraphQLID, resolve: () => 'id' }, name: { type: GraphQLString, resolve: () => 'name' }, friends: { type: new GraphQLList(ParticipantType), resolve: () => [''] }, events: { type: new GraphQLList(EventType), resolve: () => [''] } }) }); export default `ParticipantType`;
-
In
query.js:- import created types
import EventType from './types/EventType'; import ParticipantType from './types/ParticipantType';
- use them in
queryas fields types
const query = new GraphQLObjectType({ name: 'Query', fields: { participant: { resolve: () => 'participant', type: ParticipantType, }, event: { resolve: () => 'event', type: EventType, } } });
-
In
query.js:- import neseccary types from
graphql
import { GraphQLObjectType, GraphQLNonNull, GraphQLID, GraphQLString } from 'graphql';
- add arguments to fields(the same for both
participantandevent)
args: { id: { type: new GraphQLNonNull(GraphQLID) }, name: { type: GraphQLString } }
- add
rootandargsarguments to resolve methods and returnargs
resolve: (root, args) => args,
- import neseccary types from
-
Next in
EventType.js:- add "root" and "args" arguments to resolve methods
- the "root" argument has value returned from parent in schema in this case the "event" field which is returning it's args for scalar(primitive) fields return corresponding value from root for list return table with root value
id: { type: GraphQLID, resolve: (root) => root.id }, name: { type: GraphQLString, resolve: (root) => root.name }, participants: { type: new GraphQLList(ParticipantType), resolve: (root) => [root] }
-
Do the same thing for
ParticipantType.id: { type: GraphQLID, resolve: (root) => root.id }, name: { type: GraphQLString, resolve: (root) => root.name }, friends: { type: new GraphQLList(ParticipantType), resolve: (root) => [root] }, events: { type: new GraphQLList(EventType), resolve: (root) => [root] }
-
Create new file in root directory
fake-api.js.$ touch fake-api.js
-
We need two lists: one for events and one for participants.
- event item in events list should be structured like below:
{ id: String // event id name: String // event name participantsIds: List(String) // list of participants ids }
- participant item in participants list should be structured like below:
{ id: String // event id name: String // event name eventsIds: List(String) // list of events ids friendsIds: List(String) // list of friends ids }
-
Inside
fake-api.js:- declaring dummy data
const DATA = { participants: [{ id: '1', name: 'Bolek', friendsIds: ['2', '3'], eventsIds: ['1'] }, { id: '2', name: 'Franek', friendsIds: ['1'], eventsIds: ['1'] }, { id: '3', name: 'Zenek', friendsIds: [], eventsIds: ['1', '2'] }], events: [{ id: '1', name: 'WarsawJS', participantsIds: ['1', '2', '3'] }, { id: '2', name: 'ReactWarsaw', participantsIds: ['2'] }, { id: '3', name: 'AngularWarsaw', participantsIds: [] }] };
- declare functions returning proper
eventandparticipantbyid
const getEvent = (id) => DATA.events.find(participant => participant.id === id); const getParticipant = (id) => DATA.participants.find(event => event.id === id);
- export functions
export { getEvent, getParticipant }
-
Use
fake-apiinquery.js:- import
fake-apifunctions
import { getEvent, getParticipant } from '../fake-api';
- use them to resolve value for
eventandparticipantfields, passingidfromargsargument
participant: { ... resolve: (root, args) => getParticipant(args.id), ... }, event: { ... resolve: (root, args) => getEvent(args.id), ... }
- import
-
In
event-type.js:- import getParticipant
import { getParticipant } from '../../fake-api';
- in
participantsfield resolve method usegetParticipantwhile mapping throughparticipantIdsfrom root value
resolve: (root) => root.participantsIds.map((id) => getParticipant(id))
-
In
participant-type.js:- import
getParticipantandgetEvent
import { getParticipant, getEvent } from '../../fake-api';
- in
eventfield resolve method usegetEventwhile mapping througheventIdsfrom root value
resolve: (root) => root.eventsIds.map(id => getEvent(id))
- in
friendsfield resolve method usegetParticipantwhile mapping throughfriendsIdsfrom root value
resolve: (root) => root.friendsIds.map(id => getParticipant(id))
- import
-
Add new functions to
fake-api.jsand update exportconst addEvent = ({ id, name }) => { DATA.events.push({ id, name, participantsIds: [] }); return getEvent(id); }; const addParticipant = ({ id, name }) => { DATA.participants.push({ id, name, eventsIds: [], friendsIds: [] }); return getParticipant(id); }; const addParticipantToEvent = ({ participantId, eventId }) => { const participant = getParticipant(participantId); participant.eventsIds.push(eventId); getEvent(eventId).participantsIds.push(participantId); return participant; }; const addParticipantFriend = ({ participantId, friendId }) => { const participant = getParticipant(participantId); participant.friendsIds.push(friendId); getParticipant(friendId).friendsIds.push(participantId); return participant; }; export { addEvent, addParticipant, addParticipantToEvent, addParticipantFriend, getEvent, getParticipant }
-
Create
mutation.jsfile ingraphqlfolder.$ cd graphql $ touch mutation.js -
Inside:
- import necessary
graphqltypes, functions fromfake-api,EventTypeandParticipantType
import { GraphQLObjectType, GraphQLID, GraphQLString, GraphQLNonNull } from 'graphql'; import { addEvent, addParticipant, addParticipantToEvent, addParticipantFriend } from '../fakeApi'; import EventType from './types/EventType'; import ParticipantType from './types/ParticipantType';
- declaring
mutationis similar to declaringquery. Mutation have fields with types after mutating data, GrapqhQL will return mutated data, that's why mutation fields have similar types to query fields we will add 4 mutations:addEvent,addParticipant,addParticipantToEvent,addParticipantFriend
const mutation = new GraphQLObjectType({ name: 'Mutation', description: 'Mutate data', fields: { addEvent: { type: EventType, args: { id: { type: new GraphQLNonNull(GraphQLID) }, name: { type: new GraphQLNonNull(GraphQLString) } }, resolve: (root, { id, name }) => addEvent({ id, name }) }, addParticipant: { type: ParticipantType, args: { id: { type: new GraphQLNonNull(GraphQLID) }, name: { type: new GraphQLNonNull(GraphQLString) } }, resolve: (root, { id, name }) => addParticipant({ id, name }) }, addParticipantToEvent: { type: ParticipantType, args: { participantId: { type: new GraphQLNonNull(GraphQLID) }, eventId: { type: new GraphQLNonNull(GraphQLID) } }, resolve: (root, { participantId, eventId }) => addParticipantToEvent({ participantId, eventId }) }, addParticipantFriend: { type: ParticipantType, args: { participantId: { type: new GraphQLNonNull(GraphQLID) }, friendId: { type: new GraphQLNonNull(GraphQLID) } }, resolve: (root, { participantId, friendId }) => addParticipantFriend({ participantId, friendId }) } } }); export default mutation;
- import necessary
-
Import and use mutation in
schema.js.import mutation from './mutation'; const schema = new GraphQLSchema({ query, mutation });
