├── .editorconfig
├── .gitignore
├── 1. What is GraphQL.md
├── 10. A Working GraphQL Server v2.md
├── 2. Basic Query Syntax.md
├── 3. Querying with Field Aliases and Fragments.md
├── 4. Querying with Directives.md
├── 5. On the Server-Side - Creating Your First Schema.md
├── 6. A Working GraphQL Server in Nodejs.md
├── 7. Deep Dive into GraphQL Type System.md
├── 8. Mutations.md
├── 9. Introspection.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
└── workshopper
├── README.md
├── exercises
├── a_working_graphql_server_in_nodejs
│ ├── exercise.js
│ ├── problem.md
│ └── solution
│ │ └── solution.js
├── a_working_graphql_server_v2
│ ├── exercise.js
│ ├── problem.md
│ └── solution
│ │ └── solution.js
├── basic_query_syntax
│ ├── exercise.js
│ ├── problem.md
│ └── solution
│ │ └── solution.js
├── deep_dive_into_graphql_type_system
│ ├── exercise.js
│ ├── problem.md
│ └── solution
│ │ └── solution.js
├── introspection
│ ├── exercise.js
│ ├── problem.md
│ └── solution
│ │ └── solution.js
├── menu.json
├── mutations
│ ├── exercise.js
│ ├── problem.md
│ └── solution
│ │ └── solution.js
├── on_the_serverside__creating_your_first_schema
│ ├── exercise.js
│ ├── problem.md
│ └── solution
│ │ └── solution.js
├── querying_with_directives
│ ├── exercise.js
│ ├── problem.md
│ └── solution
│ │ └── solution.js
├── querying_with_field_aliases_and_fragments
│ ├── exercise.js
│ ├── problem.md
│ └── solution
│ │ └── solution.js
└── what_is_graphql
│ ├── exercise.js
│ ├── problem.md
│ └── solution
│ └── solution.js
├── learning-graphql.js
├── package.json
└── workshopper.png
/.editorconfig:
--------------------------------------------------------------------------------
1 | # This file is for unifying the coding style for different editors and IDEs
2 | # editorconfig.org
3 | root = true
4 |
5 | [*]
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 | indent_style = space
11 | indent_size = 2
12 |
13 | [*.json]
14 | indent_style = space
15 | indent_size = 2
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | *.log
4 |
--------------------------------------------------------------------------------
/1. What is GraphQL.md:
--------------------------------------------------------------------------------
1 | # Part 1: What is GraphQL
2 |
3 | ```
4 | _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
5 | | |
6 | | The dirty secret: |
7 | | We’re all just building CRUD apps. |
8 | | |
9 | | @iamdevloper |
10 | | _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
11 | ```
12 | While giggling at the funny tweet, you and I both know that it is not that simple. If you are a developer and haven't been in a cave for last few years, you have seen the rise of complex single page and mobile applications in general. While developing both, sometimes I feel that we probably have entered the "client-side heavy" era without much of prior preparation. There have been a lot of confusion, experiments, thrill in how to cope up with the changes, how to structure code and the flow, how to be on the edge of performance, but with all that, we seem to have overlooked some basic things repeatedly. Data fetching is one of those things.
13 |
14 | Most of us are using REST APIs for that. While meeting our needs for most of the times, it also made our front-end developers to frequently figure out why some properties are missing in the JSON response they got from the API, what's changed in the last sprint, why some shit is not in the API documentation. Our back-end developers are constantly trying to cope up with the nitty gritty changes in dozens of client side applications and their numerous slightly different version that depend on the API. For years, we took this miserable life for granted.
15 |
16 | Don't get me wrong. REST is of course simple, there is almost no learning curve. Here is your resource, ask for it politely with the proper verb, be sure to check your status code and be done with the body. It blends perfectly with HTTP. I was a kid building native desktop applications when the world was cheering to move from SOAP to REST, XML to JSON, Configuration to Convention, so I didn't take much notice on the web. But I can feel the excitement of those times just by looking at GraphQL.
17 |
18 | I think the main problem of REST is that it tried to blend too much with HTTP instead of focusing on the nature of our data. Maybe that's because it came from a time where most complex data mingling was done on sever-side and the client was served with properly rendered pages. Things have changed. Restaurants became grocery shops, they now serve raw ingredients and assume their clients are new chefs, they know how to cook and prepare a presentable dish. Imagine a real-life communication between a chef and grocery market, that is much more chaotic than what's going on between a customer and waitress in a restaurant. Same goes for SPA and Mobile Apps. Some tooling became long due to cope up with the change.
19 |
20 | If you look close enough, most of our data is connected like graphs. You cannot just get away with an author's data, you need information about the books they have written. You cannot talk about a TV Show without talking at all about its episodes. A good dish has complex taste by requirement. The same goes for a finished product, it is never as simple as individually lived resources.
21 |
22 | The first step to solve a problem is to acknowledge that it exists. GraphQL (fortunately) does so for us. And not just that, it also agrees that product designs start with views, so front-end engineers should be able to declare what data they need instead of waiting for the backend guys to make necessary changes in API for them. The world needs to move faster. REST was failing to cope up with that, *elegantly*.
23 |
24 |
25 | There are more actually. Read [GraphQL Introduction](http://facebook.github.io/react/blog/2015/05/01/graphql-introduction.html) to feed your curious monster. Watch the [announcement on React Europe 2015 Conf](https://www.youtube.com/watch?v=WQLzZf34FJ8), and a [deep dive to it](https://www.youtube.com/watch?v=gY48GW87Feo).
26 |
27 | The official site is here: [graphql.org](http://graphql.org/)
28 |
29 |
30 | ## What GraphQL is not:
31 |
32 | * GraphQL is not for any specific language or framework. Although the [reference implementation](https://github.com/graphql/graphql-js) has been released in JavaScript, the GraphQL core is expected to be ported to all major languages.
33 |
34 | * GraphQL does not assume any specific data store (just because it has "QL" in its name, don't assume anything similar to SQL). You can use relational, non-relational or other REST style backends as your data source. The job of the GraphQL server is parsing, validating, providing data and mutating (updating) it based on clients' requests.
35 |
36 | ## What GraphQL *is*:
37 |
38 | * It is Declarative:
39 |
40 | Query responses are decided by the client rather than the server. A GraphQL query returns exactly what a client asks for and no more.
41 |
42 | * Compositional:
43 |
44 | A GraphQL query itself is a hierarchical set of fields. The query is shaped just like the data it returns. It is a natural way for product engineers to describe data requirements.
45 |
46 | * Strongly-typed
47 |
48 | A GraphQL query can be ensured to be valid within a GraphQL type system at development time allowing the server to make guarantees about the response. This makes it easier to build high-quality client tools.
49 |
50 | We'll discuss them as we go through the series.
51 |
52 | Previous
53 |
54 | [Next](2.%20Basic%20Query%20Syntax.md) - Basic Query Syntax
55 |
--------------------------------------------------------------------------------
/10. A Working GraphQL Server v2.md:
--------------------------------------------------------------------------------
1 | # Part 10: A Working GraphQL Server v2
2 |
3 | TBD
4 |
--------------------------------------------------------------------------------
/2. Basic Query Syntax.md:
--------------------------------------------------------------------------------
1 | # Part 2: Basic Query syntax
2 |
3 |
4 | GraphQL queries look a lot like JSON objects without data. I'm glad they did it this way.
5 |
6 | Consider this JSON object:
7 |
8 | ```
9 | // A JSON object
10 | {
11 | "user": {
12 | "name": "Lee Byron"
13 | }
14 | }
15 |
16 | ```
17 |
18 | A similar GraphQL query would be:
19 |
20 | ```
21 | // Request:
22 | {
23 | user {
24 | name
25 | }
26 | }
27 |
28 | ```
29 |
30 | When the server responds it just pours data into those empty properties.
31 |
32 | ```
33 | // Response:
34 | {
35 | data: {
36 | user: {
37 | name: "Lee Byron"
38 | }
39 | }
40 | }
41 |
42 | ```
43 | Could it be any simpler than that!
44 |
45 | ## Parameters
46 |
47 | You can also use parameters in GraphQL queries:
48 |
49 | ```
50 | //Request:
51 | query fetchUser {
52 | user(id: "1") {
53 | name
54 | }
55 | }
56 | ```
57 |
58 | *Now wait a minute! That looks more than the promised simple JSON object without data. What are those `query` and `fetchUser` things doing here? I hope you aren't trying to sell me OData disguised as graphshit! Are you?*
59 |
60 | I'm not. And I'm glad that you are paying attention. In my opinion, GraphQL's approach is much more elegant than that initiative of Microsoft.
61 |
62 | ## Anatomy of a GraphQL Query:
63 |
64 | Looking back again:
65 |
66 | ```
67 | //Request:
68 | query fetchUser {
69 | user(id: "1") {
70 | name
71 | }
72 | }
73 | ```
74 |
75 | * `query` is an GraphQL `Operation`. It, as you might guess, fetches the data from server. This `query` operation is read-only, you cannot modify data with it.
76 |
77 | > The other valid value for GraphQL operation is `mutation`, which we can use to create, update or delete data and fetch the updated result (*doing CRUD, remember? Need to be able to do them all!*). We'll discuss more on mutations later.
78 |
79 | * `fetchUser` is just an arbitrary name of your query. You'll write whatever you feel right here. A meaningful name will make sense later to other developers about what the query does in short. Yeah, [that's one of the hardest things to do in computer science :( )](http://martinfowler.com/bliki/TwoHardThings.html)!
80 |
81 | * `user` is called a `field`. And `name` is a sub-field, you might say.
82 |
83 | * `id: "1"` is the `argument` on the `user` field. Arguments are unordered, by the way. `picture(width: 200, height: 100)` and `picture(height: 100, width: 200)` means the same to a GraphQL server.
84 |
85 | * And this whole darn thing is called a `document`.
86 |
87 |
88 | ----------
89 |
90 |
91 | However, if a query has no `arguments` or `directives` (we will discuss directives later, for now just assume it's another piece of the whole puzzle) or `name`, the `query` keyword can be omitted. GraphQL server will default to that. We used that shortcut with our first example above:
92 |
93 | ```
94 | // Request:
95 | {
96 | user {
97 | name
98 | }
99 | }
100 |
101 | ```
102 |
103 | [Previous](1.%20What%20is%20GraphQL.md) - What is GraphQL
104 |
105 | [Next](3.%20Querying%20with%20Field%20Aliases%20and%20Fragments.md) - Querying with Field Aliases and Fragments
106 |
--------------------------------------------------------------------------------
/3. Querying with Field Aliases and Fragments.md:
--------------------------------------------------------------------------------
1 | # Part 3: Querying with Field Aliases & Fragments
2 |
3 | We have seen basic queries with parameters. Let's get into deeper water.
4 |
5 | ## Field Aliases
6 |
7 | Consider the GraphQL query from the previous section:
8 |
9 | ```
10 | // Request
11 | query FetchUser {
12 | user(id: "1") {
13 | name
14 | }
15 | }
16 |
17 | // Response:
18 | {
19 | data: {
20 | user: {
21 | name: "Lee Byron"
22 | }
23 | }
24 | }
25 | ```
26 |
27 | What if you need to fetch multiple users in a single query? You already have `user` as the property of `data` object, how would you fit another user there?
28 |
29 | `Field Aliases` can be handy in these cases. You can give your fields any valid name you want, and the response will match that query. (`user` is called a `field` here, you remember that from the previous lesson, right?)
30 |
31 | ```
32 | // Request
33 | query FetchLeeAndSam {
34 | lee: user(id: "1") {
35 | name
36 | }
37 | sam: user(id: "2") {
38 | name
39 | }
40 | }
41 | ```
42 |
43 | The `field` name `user` is still the same, you are just instructing GraphQL server to provide Lee's data and Sam's data in their separate keys.
44 |
45 | ```
46 | // Response:
47 | {
48 | data: {
49 | lee: {
50 | name: "Lee Byron"
51 | },
52 | sam: {
53 | name: "Sam"
54 | }
55 | }
56 | }
57 | ```
58 |
59 | By the way, using a comma between `field`s in a GraphQL query is optional. You can use them between `lee` and `sam` if that makes you comfortable, like this:
60 |
61 | ```
62 | // Request
63 | query FetchLeeAndSam {
64 | lee: user(id: "1") {
65 | name
66 | },
67 | sam: user(id: "2") {
68 | name
69 | }
70 | }
71 | ```
72 |
73 | ## Fragments
74 |
75 | Now imagine your product manager suddenly came and asked to include Lee and Sam's email addresses to the UI. We could ask for the new data with this query of course:
76 |
77 | ```
78 | // Request
79 | query FetchLeeAndSam {
80 | lee: user(id: "1") {
81 | name
82 | email
83 | }
84 | sam: user(id: "2") {
85 | name
86 | email
87 | }
88 | }
89 | ```
90 |
91 | We can already see that we are repeating ourselves. Since we have to add new fields (email) to both parts of the query, things are looking a bit odd. Instead, we can extract out the common fields into a `fragment`, and reuse the fragment in the query, like this:
92 |
93 | ```
94 | // Request
95 | query FetchWithFragment {
96 | lee: user(id: "1") {
97 | ...UserFragment
98 | }
99 | sam: user(id: "2") {
100 | ...UserFragment
101 | }
102 | }
103 |
104 | fragment UserFragment on User {
105 | name
106 | email
107 | }
108 |
109 | ```
110 |
111 | > This `...` is called the spread operator. If you are familiar with the new features of ES6/ES2015, you already have used similar syntax there.
112 | >
113 | > The `on User` tells us that the `UserFragment` can only be applied to the `User` type. I already gave you a hint that GraphQL is strongly-typed, we'll go through the details of the type system later.
114 |
115 | Both of those queries give this result:
116 |
117 | ```
118 | // Response:
119 | {
120 | data: {
121 | lee: {
122 | name: "Lee Byron",
123 | email: lee@example.com
124 | },
125 | sam: {
126 | name: "Sam",
127 | email: sam@example.com
128 | }
129 | }
130 | }
131 | ```
132 |
133 | The `FetchLeeAndSam` and `FetchWithFragment` queries will both get the same result, but `FetchWithFragment` is less verbose and less repetitive; if we wanted to add more fields, we could add it to the common fragment rather than copying it into multiple places.
134 |
135 | ## Inline Fragment
136 |
137 | There is another similar thing called `inline fragments`, which as you might have guessed, can be defined inline to queries. This is done to conditionally execute fields based on their runtime type.
138 |
139 | In the following example, based on the type of the show, we will get:
140 |
141 | - sequels for the "The Matrix" show, which is of the `Movie` type
142 | - episodes for the "Mr. Robot" show, which is of the `Tvshow` type.
143 |
144 | ```
145 | query inlineFragmentTyping {
146 | shows(titles: ["The Matrix", "Mr. Robot"]) {
147 | title
148 | ... on Movie {
149 | sequel {
150 | name
151 | date
152 | }
153 | }
154 | ... on Tvshow {
155 | episode {
156 | name
157 | date
158 | }
159 | }
160 | }
161 | }
162 | ```
163 |
164 |
165 | There are other ways to conditionally modify a query. `Directive` plays the key role in that and we are going to talk about them in the next part.
166 |
167 | [Previous](2.%20Basic%20Query%20Syntax.md) - Basic Query Syntax
168 |
169 | [Next](4.%20Querying%20with%20Directives.md) - Querying with Directives
170 |
--------------------------------------------------------------------------------
/4. Querying with Directives.md:
--------------------------------------------------------------------------------
1 | # Part 4: Querying with Directives
2 |
3 | In some cases you may need to provide options to alter GraphQL’s execution behavior in ways field arguments cannot, such as conditionally including or skipping a field *if* certain criteria are met. Directives enable us to do just that.
4 |
5 | Right now GraphQL comes with two directives-
6 | `@skip` and `@include`, but future extensions to it may include more, such as `@defer`, `@stream` and `@live`.
7 |
8 | ## `@skip`
9 |
10 | The `@skip` directive may be provided for fields or fragments, and allows for conditional exclusion as described by the if argument.
11 |
12 | In this example `experimentalField` will be queried only if the variable `$someTest` has a value of `false`.
13 |
14 | ```
15 | query myQuery($someTest: Boolean) {
16 | experimentalField @skip(if: $someTest)
17 | }
18 |
19 | ```
20 |
21 | ## `@include`
22 |
23 | The `@include` directive may be provided for fields or fragments, and allows for conditional inclusion during execution as described by the if argument.
24 |
25 | In this example `experimentalField` will be queried only if the variable `$someTest` has a value of `true`.
26 |
27 | ```
28 | query myQuery($someTest: Boolean) {
29 | experimentalField @include(if: $someTest)
30 | }
31 |
32 | ```
33 | Note: if both the @skip and @include directives are provided, the field must be queried only if the @skip condition is false and the @include condition is true.
34 |
35 | > If you are still with me, you have probably noticed the use of variables (`$someTest`) in GraphQL queries. *Well, does this work? How does it work? Like view templates? What's the benefit of writing our queries this way? How do you assign values to these variables? How do you send these queries to server? By assigning values in place? Or the values are passed seperately?*
36 | >
37 | > I know. I have asked all those questions the moment I saw this syntax for the first time.
38 | >
39 | > Well, for starters, the purpose of writing queries this way is to make them reusable and generic.
40 | >
41 | > We don't need anything special to place values of these variables inline to a GraphQL query before sending it to the server. Generic queries are not like view templates, we don't need to "compile" them with values. Instead, we send the values to our variables as separate JSON objects.
42 | >
43 | > The GraphQL server will take the query and parameters from the request body and parse them. The `graphql` function, that the server-side GraphQL API exposes, takes the following parameters:
44 | >
45 | > `graphql(schema, graphQuery, rootValue, variableValues, operationName)`
46 | >
47 | > We can provide our query as `graphQuery` and variable values as `variableValues`. We'll talk about the rest of the things in these parameters in successive parts of this series.
48 |
49 | As a working example, we can use this simple nodejs app to post queries to an endpoint that accepts GraphQL queries.
50 |
51 | (*We have used some ES2015 goodies here, if something doesn't feel familiar please check the new features of ES2015. We are using `Babel` to automatically transpile our ES2015 code to ES5, since nodejs doesn't have all the necessary parts in its core to natively run ES2015, yet.*)
52 |
53 | In the terminal:
54 | ```
55 | npm i babel request
56 | touch index.js server.js
57 |
58 | ```
59 |
60 | Put the following in `index.js`. **This is the entry point of our app. We'll run this file with node**:
61 | ```
62 | // By requiring `babel/register`, all of our successive requires will be transpiled by Babel.
63 | require('babel/register');
64 | require('./server.js');
65 | ```
66 |
67 | In our `server.js` file:
68 | ```js
69 | import {request} from 'request';
70 |
71 | const SERVER_URL = 'https://graphql.example.com/';
72 |
73 | let query = `
74 | query myQuery($someTest: Boolean) {
75 | experimentalField @include(if: $someTest)
76 | }
77 | `;
78 |
79 | let params = {
80 | someTest: true
81 | };
82 |
83 | //Unlike REST, GraphQL doesn't specify which HTTP Verb to use.
84 | //POST seems to be a right fit here.
85 | request.post(SERVER_URL, {
86 | query,
87 | params
88 | }, (err, res, body) => {
89 | // Handle response
90 | });
91 | ```
92 |
93 | This example is here just to show how to post a GraphQL query programmatically in nodejs. Of course the same can be achieved with `curl`, `Postman`, `httpie`, `XMLHttpRequest` or the modern `fetch` API in browsers. If you are using React (you probably are using that), Relay will take care of it.
94 |
95 | Now what about the GraphQL server that'll process the query? We haven't said anything about how to write a GraphQL server yet. Let's start that in the next part.
96 |
97 | [Previous](3.%20Querying%20with%20Field%20Aliases%20and%20Fragments.md) - Querying with Field Aliases and Fragments
98 |
99 | [Next](5.%20On%20the%20Server-Side%20-%20Creating%20Your%20First%20Schema.md) - On the Server-Side - Creating Your First Schema
100 |
--------------------------------------------------------------------------------
/5. On the Server-Side - Creating Your First Schema.md:
--------------------------------------------------------------------------------
1 | # Part 5: On the server-side - Creating your first schema
2 |
3 | We've learnt a great deal of how to prepare GraphQL queries. Let's dive into the next step- building a very simple GraphQL server.
4 |
5 | The first step to build a working GraphQL API is providing a `schema`. The GraphQL engine will parse queries, then validate and return the data based on your designed type schema.
6 |
7 | By design, GraphQL schemas are **strongly typed**. This gives the system a way to validate the query and guarantees the *shape* of the response.
8 |
9 | Here's an example of a simple GraphQL schema that returns `{hello: 'world'}` for a `{hello}` query:
10 |
11 | ```js
12 | import {
13 | graphql,
14 | GraphQLSchema,
15 | GraphQLObjectType,
16 | GraphQLString
17 | } from 'graphql';
18 |
19 | var schema = new GraphQLSchema({
20 | query: new GraphQLObjectType({
21 | name: 'RootQueryType',
22 | fields: {
23 | hello: {
24 | type: GraphQLString,
25 | resolve: () => 'world'
26 | }
27 | }
28 | })
29 | });
30 |
31 | /* Dummy GraphQL query.
32 | In real-life we'll get it from the client side.
33 | */
34 | var query = `
35 | query HelloQuery {
36 | { hello }
37 | }
38 | `;
39 |
40 | graphql(schema, query).then((result) => {
41 | // Prints
42 | // {
43 | // data: { hello: "world" }
44 | // }
45 | console.log(result);
46 | });
47 |
48 | ```
49 |
50 | If you need to accept parameters to get results, that's easy too. We need to define what type of parameter we accept via an `args` object on fields, and pass that to the `resolve` function. Here's an updated version of the above sample that takes a `greet` parameter of `GraphQLString` type:
51 |
52 | ```js
53 | import {
54 | graphql,
55 | GraphQLSchema,
56 | GraphQLObjectType,
57 | GraphQLString
58 | } from 'graphql';
59 |
60 |
61 | var schema = new GraphQLSchema({
62 | query: new GraphQLObjectType({
63 | name: 'RootQueryType',
64 | fields: {
65 | hello: {
66 | type: GraphQLString,
67 | args: {
68 | greet: { type: GraphQLString }
69 | },
70 | //Using Destructuring feature of ES2015 to assign value to greet
71 | resolve: (root, {greet}) => {
72 | greet = greet ? greet + '!' : 'world!';
73 | return greet;
74 | }
75 | }
76 | }
77 | })
78 | });
79 |
80 | var query = `
81 | query Welcome {
82 | hello (greet: "mehdi")
83 | }
84 | `;
85 |
86 | graphql(schema, query).then((result) => {
87 |
88 | // Prints
89 | // {
90 | // data: { hello: "mehdi!" }
91 | // }
92 | console.log(result);
93 | });
94 |
95 | ```
96 |
97 | > Have you noticed that we are using double-quotes in `hello (greet: "mehdi")` in our query? That's because using single-quotes in `GraphQLString` type parameters is invalid. Tada!
98 |
99 | If you are using server-side MVC frameworks like Ruby on Rails, Laravel or SailsJS, you are probably getting a hint here that the coming days are going to be a lot different with GraphQL. The `V` in MVC has already gone from the server-side in API only use cases. What's next? I can only speculate there will be rudimentary routing for our API servers, as we won't have a bunch of routes but a single endpoint. The schema will do a lot of validation for us, a lot of authentication & authorization will be on middleware layers, as they are now. The `resolve` function of the fields will probably take a lot of responsibility from controllers. The only least affected areas might be the models/ORM layer. Or maybe GraphQL will co-exists with REST and pre-dominant MVC style server-side architecture for a long time. I don't know. But I'm excited to embrace what's coming in the near future!
100 |
101 | [Previous](4.%20Querying%20with%20Directives.md) - Querying with Directives
102 |
103 | [Next](6.%20A%20Working%20GraphQL%20Server%20in%20Nodejs.md) - A Working GraphQL Server in Nodejs
104 |
--------------------------------------------------------------------------------
/6. A Working GraphQL Server in Nodejs.md:
--------------------------------------------------------------------------------
1 | # Part 6: A working GraphQL Server
2 |
3 | Let's build a real server which can parse GraphQL queries sent over HTTP.
4 |
5 | First, install `babel` (to use ES2015 awesomeness), `restify` and of course, `graphql`
6 |
7 | ```
8 | npm i babel graphql restify
9 | ```
10 |
11 | Create three files- `index.js`, `server.js` and `schema.js`.
12 |
13 | ```
14 | touch index.js server.js schema.js
15 | ```
16 |
17 | `index.js` file will be our entry point. We'll just register babel here so that the rest of our application can use ES2015 code.
18 |
19 | ```js
20 | //index.js
21 | require('babel/register');
22 | require('./server');
23 | ```
24 |
25 | Let's build our schema now. We'll use dummy data here for the sake of brevity, but as you'll see using something like `Bookshelf.js`/`Knexjs`/`Sequelize` will be trivial. We'll pass the ID parameter with our query like `{contributor (id: "1")}` to get the GraphQL contributor's github username.
26 |
27 | Here's the code in `schema.js`:
28 |
29 | ```js
30 | //schema.js
31 | import {
32 | graphql,
33 | GraphQLSchema,
34 | GraphQLObjectType,
35 | GraphQLString
36 | } from 'graphql';
37 |
38 | let dummyData = {
39 | '1': 'leebyron',
40 | '2': 'enaqx',
41 | '3': 'schrockn',
42 | '4': 'andimarek'
43 | };
44 |
45 | export default new GraphQLSchema({
46 | query: new GraphQLObjectType({
47 | name: 'RootQueryType',
48 | fields: {
49 | contributor: {
50 | type: GraphQLString,
51 | args: {
52 | id: { type: GraphQLString }
53 | },
54 | //Using Destructuring of ES2015 to assign value to id
55 | resolve: (root, {id}) => {
56 | return dummyData[id];
57 | }
58 | }
59 | }
60 | })
61 | });
62 | ```
63 | You are probably already familiar with `koa`/`express`/`restify`. This part is easiest for you in that case:
64 |
65 | ```js
66 | //server.js
67 | import restify from 'restify';
68 | import { graphql } from 'graphql';
69 | import schema from './schema.js';
70 |
71 | var server = restify.createServer({
72 | name: 'GraphQL Demo'
73 | });
74 |
75 | server.use(restify.acceptParser(server.acceptable));
76 | server.use(restify.queryParser());
77 | server.use(restify.bodyParser());
78 |
79 | server.post('/', (req, res, next) => {
80 | graphql(schema, req.body).then((result) => {
81 | res.send(result);
82 | });
83 | });
84 |
85 | server.get('/', (req, res, next) => {
86 | let instruction = 'POST GraphQL queries to' + server.url + '. Sample query: {contributor (id: "1")}';
87 | res.send(instruction);
88 | });
89 |
90 | server.listen(process.env.PORT || 8080, () => {
91 | console.log('%s listening at %s', server.name, server.url);
92 | });
93 | ```
94 |
95 | In order to run the server:
96 |
97 | ```
98 | node index.js
99 | ```
100 |
101 | The server will start at http://localhost:8080 address.
102 |
103 | Now we can use `Postman` or just simple `CURL` to test if it is working:
104 |
105 | ```
106 | curl -XPOST -d '{contributor (id: "1")}' http://localhost:8080/
107 | ```
108 |
109 | If you got `{"data":{"contributor":"leebyron"}}` in return, congratulations for making it this far!
110 |
111 | [Previous](5.%20On%20the%20Server-Side%20-%20Creating%20Your%20First%20Schema.md) - On the Server-Side - Creating Your First Schema
112 |
113 | [Next](7.%20Deep%20Dive%20into%20GraphQL%20Type%20System.md) - Deep Dive into GraphQL Type System
114 |
--------------------------------------------------------------------------------
/7. Deep Dive into GraphQL Type System.md:
--------------------------------------------------------------------------------
1 | # Part 7: Deep Dive into GraphQL Type System
2 |
3 | This part may feel more like a manual than a tutorial. I wrote it to save myself from jumping between GraphQL RFC Spec and the test suit of the reference JavaScript implementation.
4 |
5 | At the heart of any GraphQL implementation, there is a description of what types of objects it can return, described with a GraphQL type system.
6 |
7 | The JavaScript implementation has the following named types implemented in it:
8 |
9 | ## `GraphQLScalarType`:
10 |
11 | Scalar types hold a single value. GraphQL provides a basic set of well‐defined Scalar types.
12 |
13 | `GraphQLInt` (example: 2)
14 |
15 | `GraphQLFloat` (example: 2.0)
16 |
17 | `GraphQLString` (example: "Hello World")
18 |
19 | `GraphQLBoolean` (example: true)
20 |
21 | `GraphQLID` (integer or string of any format)
22 |
23 | ## Other types:
24 |
25 | ## `GraphQLObjectType`
26 |
27 | GraphQL Objects represent a list of named fields, each of which also yields a value of their own type. Example:
28 |
29 | ```js
30 | var PersonType = new GraphQLObjectType({
31 | name: 'Person',
32 | fields: () => ({
33 | name: {
34 | type: GraphQLString,
35 | description: 'The name of the person.',
36 | },
37 | age: {
38 | type: GraphQLInt,
39 | description: 'The age of the person.',
40 | }
41 | })
42 | });
43 |
44 | ```
45 |
46 | ## `GraphQLInterfaceType`
47 |
48 | GraphQL Interfaces represent a reusable collection of fields and their arguments. GraphQL object can then implement an interface, which guarantees that they will contain the specified fields.
49 |
50 | ```js
51 | class Dog {
52 | constructor(name, barks) {
53 | this.name = name;
54 | this.barks = barks;
55 | }
56 | }
57 |
58 | class Cat {
59 | constructor(name, meows) {
60 | this.name = name;
61 | this.meows = meows;
62 | }
63 | }
64 |
65 | var NamedType = new GraphQLInterfaceType({
66 | name: 'Named',
67 | fields: {
68 | name: { type: GraphQLString }
69 | }
70 | });
71 |
72 | var DogType = new GraphQLObjectType({
73 | name: 'Dog',
74 | interfaces: [ NamedType ],
75 | fields: {
76 | name: { type: GraphQLString },
77 | barks: { type: GraphQLBoolean }
78 | },
79 | isTypeOf: (value) => value instanceof Dog
80 | });
81 |
82 | var CatType = new GraphQLObjectType({
83 | name: 'Cat',
84 | interfaces: [ NamedType ],
85 | fields: {
86 | name: { type: GraphQLString },
87 | meows: { type: GraphQLBoolean }
88 | },
89 | isTypeOf: (value) => value instanceof Cat
90 | });
91 |
92 | ```
93 |
94 | ## `GraphQLList`
95 |
96 | A GraphQL list is a collection type which declares the type of each item in the List.
97 |
98 | ```js
99 | var BlogArticle = new GraphQLObjectType({
100 | name: 'Article',
101 | fields: {
102 | id: { type: GraphQLString },
103 | isPublished: { type: GraphQLBoolean },
104 | author: { type: GraphQLString },
105 | title: { type: GraphQLString },
106 | body: { type: GraphQLString }
107 | }
108 | });
109 |
110 | var BlogQuery = new GraphQLObjectType({
111 | name: 'Query',
112 | fields: {
113 | article: {
114 | args: { id: { type: GraphQLString } },
115 | type: BlogArticle
116 | },
117 | feed: {
118 | type: new GraphQLList(BlogArticle)
119 | }
120 | }
121 | });
122 |
123 | ```
124 |
125 | ## `GraphQLNonNull`
126 |
127 | By default, all types in GraphQL are nullable; the null value is a valid response for all of the above types. To declare a type that disallows null, the GraphQL Non‐Null type can be used.
128 |
129 | `GraphQLNonNull` acts more like a wrapper on other types. Remember to instantiate it with `new` when disallowing null on some typed field.
130 |
131 | ```js
132 | var BlogArticle = new GraphQLObjectType({
133 | name: 'Article',
134 | fields: {
135 | id: { type: new GraphQLNonNull(GraphQLString) },
136 | isPublished: { type: new GraphQLNonNull(GraphQLBoolean) },
137 | author: { type: new GraphQLNonNull(GraphQLString) },
138 | title: { type: GraphQLString },
139 | body: { type: GraphQLString }
140 | }
141 | });
142 | ```
143 |
144 |
145 | ## `GraphQLEnumType`
146 |
147 | GraphQL Enum represents one the of possible values:
148 |
149 | ```js
150 | var episodeEnum = new GraphQLEnumType({
151 | name: 'Episode',
152 | description: 'One of the films in the Star Wars Trilogy',
153 | values: {
154 | NEWHOPE: {
155 | value: 4,
156 | description: 'Released in 1977.',
157 | },
158 | EMPIRE: {
159 | value: 5,
160 | description: 'Released in 1980.',
161 | },
162 | JEDI: {
163 | value: 6,
164 | description: 'Released in 1983.',
165 | },
166 | }
167 | });
168 |
169 | ```
170 |
171 |
172 | ## `GraphQLUnionType`
173 |
174 | GraphQL Unions represent an object that could be one of items in a list of GraphQL Object types. They also differ from interfaces in that Object types declare what interfaces they implement, but are not aware of what unions contain them.
175 |
176 | ```js
177 | class Dog {
178 | constructor(name, barks) {
179 | this.name = name;
180 | this.barks = barks;
181 | }
182 | }
183 |
184 | class Cat {
185 | constructor(name, meows) {
186 | this.name = name;
187 | this.meows = meows;
188 | }
189 | }
190 |
191 | var NamedType = new GraphQLInterfaceType({
192 | name: 'Named',
193 | fields: {
194 | name: { type: GraphQLString }
195 | }
196 | });
197 |
198 | var DogType = new GraphQLObjectType({
199 | name: 'Dog',
200 | interfaces: [ NamedType ],
201 | fields: {
202 | name: { type: GraphQLString },
203 | barks: { type: GraphQLBoolean }
204 | },
205 | isTypeOf: value => value instanceof Dog
206 | });
207 |
208 | var CatType = new GraphQLObjectType({
209 | name: 'Cat',
210 | interfaces: [ NamedType ],
211 | fields: {
212 | name: { type: GraphQLString },
213 | meows: { type: GraphQLBoolean }
214 | },
215 | isTypeOf: value => value instanceof Cat
216 | });
217 |
218 | var PetType = new GraphQLUnionType({
219 | name: 'Pet',
220 | types: [ DogType, CatType ],
221 | resolveType(value) {
222 | if (value instanceof Dog) {
223 | return DogType;
224 | }
225 | if (value instanceof Cat) {
226 | return CatType;
227 | }
228 | }
229 | });
230 |
231 | ```
232 |
233 | ## `GraphQLInputObjectType`
234 |
235 | Fields can define arguments that the client passes up with the query, to configure their behavior. These inputs can be Strings or Enums, but they sometimes need to be more complex than that.
236 |
237 | The Object type has not been used here intentionally, because Objects can contain fields that express circular references or references to interfaces and unions, neither of which is appropriate for use as an input argument. For this reason, input objects have a separate type in the system.
238 |
239 | An Input Object defines input fields; the input fields are either scalars, enums, or other input objects. This allows arguments to accept arbitrarily complex structures.
240 |
241 | ```js
242 | var ComplexInput = new GraphQLInputObjectType({
243 | name: 'ComplexInput',
244 | fields: {
245 | requiredField: { type: new GraphQLNonNull(GraphQLBoolean) },
246 | intField: { type: GraphQLInt },
247 | stringField: { type: GraphQLString },
248 | booleanField: { type: GraphQLBoolean },
249 | stringListField: { type: new GraphQLList(GraphQLString) },
250 | }
251 | });
252 |
253 | var ComplicatedArgs = new GraphQLObjectType({
254 | name: 'ComplicatedArgs',
255 | fields: () => ({
256 | complexArgField: {
257 | type: GraphQLString,
258 | args: {
259 | complexArg: { type: ComplexInput }
260 | },
261 | }
262 | }),
263 | });
264 |
265 | ```
266 |
267 |
268 | ## Bonus: Creating custom scalar types
269 |
270 | In addition to built-in scalars, you can define your own custom scalar types. It is mostly helpful for doing fine-grained validations. In a lot of cases you might want to check if an email, date-time or url format is valid. It is easily doable by defining your custom email/datetime/url scalars.
271 |
272 | Here's a complete example of how to define a custom Email type with validation:
273 |
274 | ```js
275 |
276 | import {
277 | graphql,
278 | GraphQLSchema,
279 | GraphQLObjectType,
280 | GraphQLString,
281 | GraphQLScalarType
282 | } from 'graphql';
283 |
284 | import { GraphQLError } from 'graphql/error';
285 | import { Kind } from 'graphql/language';
286 |
287 | var EmailType = new GraphQLScalarType({
288 | name: 'Email',
289 | serialize: value => {
290 | return value;
291 | },
292 | parseValue: value => {
293 | return value;
294 | },
295 | parseLiteral: ast => {
296 | if (ast.kind !== Kind.STRING) {
297 | throw new GraphQLError('Query error: Can only parse strings got a: ' + ast.kind, [ast]);
298 | }
299 |
300 | // Regex taken from: http://stackoverflow.com/a/46181/761555
301 | var re = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;
302 | if(!re.test(ast.value)) {
303 | throw new GraphQLError('Query error: Not a valid Email', [ast]);
304 | }
305 |
306 | return ast.value;
307 | }
308 | });
309 |
310 | var schema = new GraphQLSchema({
311 | query: new GraphQLObjectType({
312 | name: 'RootQueryType',
313 | fields: {
314 | echo: {
315 | type: GraphQLString,
316 | args: {
317 | email: { type: EmailType }
318 | },
319 | resolve: (root, {email}) => {
320 | return email;
321 | }
322 | }
323 | }
324 | })
325 | });
326 |
327 | var query = `
328 | query Welcome {
329 | echo (email: "hi@example.com")
330 | }
331 | `;
332 |
333 | graphql(schema, query).then((result) => {
334 |
335 | // Prints
336 | // {
337 | // echo: { 'hi@example.com' }
338 | // }
339 | console.log(result);
340 | });
341 |
342 |
343 | ```
344 |
345 | You'll get an error if you provide a malformed email parameter to our echo query:
346 |
347 | ```js
348 | // ... truncated
349 |
350 | var query = `
351 | query Welcome {
352 | echo (email: "hi")
353 | }
354 | `;
355 |
356 | graphql(schema, query).then((result) => {
357 |
358 | // Prints
359 | // { errors: [ { [Error: Query error: Not a valid Email] message: 'Query error: Not a valid Email' } ] }
360 |
361 | console.log(result);
362 | });
363 |
364 | ```
365 |
366 | If you need to define similar custom scalar types for date, time, date-time, url etc., defining all of them separately is going to be a lot of boilerplate code, right?
367 |
368 | We can refactor the above code like this to avoid that:
369 |
370 | ```js
371 | import {
372 | graphql,
373 | GraphQLSchema,
374 | GraphQLObjectType,
375 | GraphQLString,
376 | GraphQLScalarType
377 | } from 'graphql';
378 |
379 | import { GraphQLError } from 'graphql/error';
380 | import { Kind } from 'graphql/language';
381 |
382 | var ValidateStringType = (params) => {
383 | return new GraphQLScalarType({
384 | name: params.name,
385 | serialize: value => {
386 | return value;
387 | },
388 | parseValue: value => {
389 | return value;
390 | },
391 | parseLiteral: ast => {
392 | if (ast.kind !== Kind.STRING) {
393 | throw new GraphQLError("Query error: Can only parse strings got a: " + ast.kind, [ast]);
394 | }
395 | if (ast.value.length < params.min) {
396 | throw new GraphQLError(`Query error: minimum length of ${params.min} required: `, [ast]);
397 | }
398 | if (ast.value.length > params.max){
399 | throw new GraphQLError(`Query error: maximum length is ${params.max}: `, [ast]);
400 | }
401 | if(params.regex !== null) {
402 | if(!params.regex.test(ast.value)) {
403 | throw new GraphQLError(`Query error: Not a valid ${params.name}: `, [ast]);
404 | }
405 | }
406 | return ast.value;
407 | }
408 | })
409 | };
410 |
411 | var EmailType = ValidateStringType({
412 | name: 'Email',
413 | min: 4,
414 | max: 254,
415 | regex: /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i
416 | });
417 |
418 | var schema = new GraphQLSchema({
419 | query: new GraphQLObjectType({
420 | name: 'RootQueryType',
421 | fields: {
422 | echo: {
423 | type: GraphQLString,
424 | args: {
425 | email: { type: EmailType }
426 | },
427 | resolve: (root, {email}) => {
428 | return email;
429 | }
430 | }
431 | }
432 | })
433 | });
434 |
435 | var query = `
436 | query Welcome {
437 | echo (email: "hi@example.com")
438 | }
439 | `;
440 |
441 | graphql(schema, query).then((result) => {
442 |
443 | // Prints
444 | // {
445 | // echo: { 'hi@example.com' }
446 | // }
447 | console.log(result);
448 | });
449 |
450 | ```
451 |
452 | Now you can define more custom scalar types using `ValidateStringType` function. *Thanks [pyros2097](https://github.com/pyros2097) for the suggestion and `ValidateStringType` snippet!*
453 |
454 | [Previous](6.%20A%20Working%20GraphQL%20Server%20in%20Nodejs.md) - A Working GraphQL Server in Nodejs
455 |
456 | [Next](8.%20Mutations.md) - Mutations
457 |
--------------------------------------------------------------------------------
/8. Mutations.md:
--------------------------------------------------------------------------------
1 | # Part 8: Mutations
2 |
3 | So far we have only talked about the `query` operation, which is read-only. What about modifying data?
4 |
5 | Unlike REST, which uses different HTTP verbs for Creating, Updating, Deleting resources, GraphQL treats all operations with side-effects similarly, and calls them `mutation`.
6 |
7 | By GraphQL definitions mutations are **"writes followed by a fetch"**. You'll return the modified data after mutating.
8 |
9 | Adding mutation to a GraphQL schema is very similar to adding query operations. Let's add one.
10 |
11 | Here's our schema from part 6 of the series:
12 |
13 | ```js
14 | //schema.js
15 | import {
16 | graphql,
17 | GraphQLSchema,
18 | GraphQLObjectType,
19 | GraphQLString
20 | } from 'graphql';
21 |
22 | let dummyData = {
23 | '1': 'leebyron',
24 | '2': 'enaqx',
25 | '3': 'schrockn',
26 | '4': 'andimarek'
27 | };
28 |
29 | export var schema = new GraphQLSchema({
30 | query: new GraphQLObjectType({
31 | name: 'RootQueryType',
32 | fields: {
33 | contributor: {
34 | type: GraphQLString,
35 | args: {
36 | id: { type: GraphQLString }
37 | },
38 | //Using Destructuring of ES2015 to assign value to id
39 | resolve: (root, {id}) => {
40 | return dummyData[id];
41 | }
42 | }
43 | }
44 | })
45 | });
46 | ```
47 |
48 | We need to start by adding a top level key `mutation` to it, rest of the things are similar.
49 |
50 | ```js
51 | var schema = new GraphQLSchema({
52 | query: new GraphQLObjectType({
53 | name: 'RootQueryType',
54 | fields: {
55 | contributor: {
56 | type: GraphQLString,
57 | args: {
58 | id: { type: GraphQLString }
59 | },
60 | //Using Destructuring of ES2015 to assign value to id
61 | resolve: (root, {id}) => {
62 | return dummyData[id];
63 | }
64 | }
65 | }
66 | }),
67 |
68 | mutation: new GraphQLObjectType({
69 | name: 'RootMutationType',
70 | fields: {
71 | updateContributor: {
72 | type: GraphQLString,
73 | args: {
74 | id: { type: GraphQLString },
75 | username: { type: GraphQLString },
76 | },
77 | resolve: (root, {id, username}) => {
78 | dummyData[id] = username;
79 | return dummyData[id];
80 | }
81 | }
82 | }
83 | })
84 | });
85 | ```
86 | Now you should get `{"data":{"updateContributor":"dschafer"}}` if you post the following `UpdateContributorUsername` mutation:
87 |
88 | ```
89 | curl -XPOST -d 'mutation UpdateContributorUsername {updateContributor (id: "5", username: "dschafer")}' http://localhost:8080/
90 | ```
91 |
92 | One important difference between mutation and query operations is, if you post multiple mutations together, they are processed serially in order to ensure data integrity, whereas independent queries are processed concurrently by the GraphQL executor.
93 |
94 | [Previous](7.%20Deep%20Dive%20into%20GraphQL%20Type%20System.md) - Deep Dive into GraphQL Type System
95 |
96 | [Next](9.%20Introspection.md) - Instrospection
97 |
--------------------------------------------------------------------------------
/9. Introspection.md:
--------------------------------------------------------------------------------
1 | # Part 9: Introspection
2 |
3 | It's often useful to ask a GraphQL schema for information about what queries it supports. GraphQL allows us to do so using the introspection system.
4 |
5 | TBD.
6 |
7 | [Previous](8.%20Mutations.md) - Mutations
8 |
9 | Next
10 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | #Contributing to this repository:
2 | Any contributions are welcome. Feel free to send pull request with improvements in spelling and grammar to better examples and descriptions.
3 |
4 | ##Priority:
5 | If you are looking for what would help most, please start with the workshopper, add tasks and tests to it. If you are new to workshopper, you can get a nice guideline [here](http://lin-clark.com/blog/2014/07/01/authoring-nodejs-workshopper-lessons/).
6 |
7 | ##Styling:
8 | I've added `.editorconfig` file to this project to help with consistency. Your favorite text editor probably already has a plugin for it [here](http://editorconfig.org/), if not already installed.
9 |
10 | I'd prefer examples in ES2015 syntax.
11 |
12 | ##Credits:
13 | I'm willing mention all contributors in the root README file. Your name will be mentioned in the `learning-graphql` npm package if you have directly contributed there.
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Mehdi Hasan Khan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Learning GraphQL
2 |
3 | This repository is my attempt to learn GraphQL by going through the [official RFC specification](http://facebook.github.io/graphql/) and the [sample JavaScript implementation](https://github.com/graphql/graphql-js).
4 |
5 | The Official Specification is meant for anyone who wants to implement GraphQL core in any language. I felt the need of a simpler version that speaks only JavaScript in its APIs and examples instead of being generic, so I wrote it down myself and shared with hope that others might find it useful too.
6 |
7 | **Update:** [graphql.org](http://graphql.org/) site came out later, which took more straightforward approach to documenting it. I've updated the contents of this series taking a lot from this new dedicated site for GraphQL.
8 |
9 | ## Table of Content:
10 |
11 | 1. [What is GraphQL](https://github.com/mugli/learning-graphql/blob/master/1.%20What%20is%20GraphQL.md)
12 | 2. [Basic Query Syntax](https://github.com/mugli/learning-graphql/blob/master/2.%20Basic%20Query%20Syntax.md)
13 | 3. [Querying with Field Aliases and Fragments](https://github.com/mugli/learning-graphql/blob/master/3.%20Querying%20with%20Field%20Aliases%20and%20Fragments.md)
14 | 4. [Querying with Directives](https://github.com/mugli/learning-graphql/blob/master/4.%20Querying%20with%20Directives.md)
15 | 5. [On the Server-Side - Creating Your First Schema](https://github.com/mugli/learning-graphql/blob/master/5.%20On%20the%20Server-Side%20-%20Creating%20Your%20First%20Schema.md)
16 | 6. [A Working GraphQL Server in Nodejs](https://github.com/mugli/learning-graphql/blob/master/6.%20A%20Working%20GraphQL%20Server%20in%20Nodejs.md)
17 | 7. [Deep Dive into GraphQL Type System](https://github.com/mugli/learning-graphql/blob/master/7.%20Deep%20Dive%20into%20GraphQL%20Type%20System.md)
18 | 8. [Mutations](https://github.com/mugli/learning-graphql/blob/master/8.%20Mutations.md)
19 | 9. Introspection (TBD)
20 | 10. A Working GraphQL Server v2 (TBD)
21 |
22 |
23 | ## Plan
24 |
25 | I intend to use these documents to make workshopper lessons. Any kind of contributions are welcome, more if you feel like adding tasks and test suits in the workshopper.
26 |
27 | ## License
28 |
29 | MIT
30 |
31 | ## More Resources
32 |
33 | - [How to GraphQL](https://www.howtographql.com): Fullstack Tutorial Website to Learn GraphQL
34 | - [Explore GraphQL](https://www.graphql.com): Great collection of articles, case studies and real-world use cases all around GraphQL
35 | - [GraphQL Radio](https://www.graphqlradio.com): Podcast all around the GraphQL eco-system
36 | - [GraphQL Weekly](https://graphqlweekly.com): Newsletter all around GraphQL
37 |
--------------------------------------------------------------------------------
/workshopper/README.md:
--------------------------------------------------------------------------------
1 | # Learning GraphQL through a terminal workshop
2 |
3 | 
4 |
5 | TBD
6 |
--------------------------------------------------------------------------------
/workshopper/exercises/a_working_graphql_server_in_nodejs/exercise.js:
--------------------------------------------------------------------------------
1 | const Exercise = require('workshopper-exercise'); module.exports = new Exercise()
--------------------------------------------------------------------------------
/workshopper/exercises/a_working_graphql_server_in_nodejs/problem.md:
--------------------------------------------------------------------------------
1 | # Part 6: A working GraphQL Server
2 |
3 | Let's build a real server which can parse GraphQL queries sent over HTTP.
4 |
5 | First, install `babel` (to use ES2015 awesomeness), `restify` and of course, `graphql`
6 |
7 | ```
8 | npm i babel graphql restify
9 | ```
10 |
11 | Create three files- `index.js`, `server.js` and `schema.js`.
12 |
13 | ```
14 | touch index.js server.js schema.js
15 | ```
16 |
17 | `index.js` file will be our entry point. We'll just register babel here so that the rest of our application can use ES2015 code.
18 |
19 | ```
20 | //index.js
21 | require('babel/register');
22 | require('./server');
23 | ```
24 |
25 | Let's build our schema now. We'll use dummy data here for the sake of brevity, but as you'll see using something like `Bookshelf.js`/`Knexjs`/`Sequelize` will be trivial. We'll pass the ID parameter with our query like `{contributor (id: "1")}` to get the GraphQL contributor's github username.
26 |
27 | Here's the code in `schema.js`:
28 |
29 | ```
30 | //schema.js
31 | import {
32 | graphql,
33 | GraphQLSchema,
34 | GraphQLObjectType,
35 | GraphQLString
36 | } from 'graphql';
37 |
38 | let dummyData = {
39 | '1': 'leebyron',
40 | '2': 'enaqx',
41 | '3': 'schrockn',
42 | '4': 'andimarek'
43 | };
44 |
45 | export default new GraphQLSchema({
46 | query: new GraphQLObjectType({
47 | name: 'RootQueryType',
48 | fields: {
49 | contributor: {
50 | type: GraphQLString,
51 | args: {
52 | id: { type: GraphQLString }
53 | },
54 | //Using Destructuring of ES2015 to assign value to id
55 | resolve: (root, {id}) => {
56 | return dummyData[id];
57 | }
58 | }
59 | }
60 | })
61 | });
62 | ```
63 | You are probably already familiar with `koa`/`express`/`restify`. This part is easiest for you in that case:
64 |
65 | ```
66 | //server.js
67 | import restify from 'restify';
68 | import { graphql } from 'graphql';
69 | import schema from './schema.js';
70 |
71 | var server = restify.createServer({
72 | name: 'GraphQL Demo'
73 | });
74 |
75 | server.use(restify.acceptParser(server.acceptable));
76 | server.use(restify.queryParser());
77 | server.use(restify.bodyParser());
78 |
79 | server.post('/', (req, res, next) => {
80 | graphql(schema, req.body).then((result) => {
81 | res.send(result);
82 | });
83 | });
84 |
85 | server.get('/', (req, res, next) => {
86 | let instruction = 'POST GraphQL queries to' + server.url + '. Sample query: {contributor (id: "1")}';
87 | res.send(instruction);
88 | });
89 |
90 | server.listen(process.env.PORT || 8080, () => {
91 | console.log('%s listening at %s', server.name, server.url);
92 | });
93 | ```
94 |
95 | In order to run the server:
96 |
97 | ```
98 | node index.js
99 | ```
100 |
101 | The server will start at http://localhost:8080 address.
102 |
103 | Now we can use `Postman` or just simple `CURL` to test if it is working:
104 |
105 | ```
106 | curl -XPOST -d '{contributor (id: "1")}' http://localhost:8080/
107 | ```
108 |
109 | If you got `{"data":{"contributor":"leebyron"}}` in return, congratulations for making it this far!
110 |
--------------------------------------------------------------------------------
/workshopper/exercises/a_working_graphql_server_in_nodejs/solution/solution.js:
--------------------------------------------------------------------------------
1 | // solution stuff here
--------------------------------------------------------------------------------
/workshopper/exercises/a_working_graphql_server_v2/exercise.js:
--------------------------------------------------------------------------------
1 | const Exercise = require('workshopper-exercise'); module.exports = new Exercise()
--------------------------------------------------------------------------------
/workshopper/exercises/a_working_graphql_server_v2/problem.md:
--------------------------------------------------------------------------------
1 | # Part 10: A Working GraphQL Server v2
2 |
3 | TBD
4 |
--------------------------------------------------------------------------------
/workshopper/exercises/a_working_graphql_server_v2/solution/solution.js:
--------------------------------------------------------------------------------
1 | // solution stuff here
--------------------------------------------------------------------------------
/workshopper/exercises/basic_query_syntax/exercise.js:
--------------------------------------------------------------------------------
1 | const Exercise = require('workshopper-exercise'); module.exports = new Exercise()
--------------------------------------------------------------------------------
/workshopper/exercises/basic_query_syntax/problem.md:
--------------------------------------------------------------------------------
1 | # Part 2: Basic Query syntax
2 |
3 |
4 | GraphQL queries look a lot like JSON objects without data. I'm glad they did it this way.
5 |
6 | Consider this JSON object:
7 |
8 | ```
9 | // A JSON object
10 | {
11 | "user": {
12 | "name": "Lee Byron"
13 | }
14 | }
15 |
16 | ```
17 |
18 | A similar GraphQL query would be:
19 |
20 | ```
21 | // Request:
22 | {
23 | user {
24 | name
25 | }
26 | }
27 |
28 | ```
29 |
30 | When the server responds it just pours data into those empty properties.
31 |
32 | ```
33 | // Response:
34 | {
35 | data: {
36 | user: {
37 | name: "Lee Byron"
38 | }
39 | }
40 | }
41 |
42 | ```
43 | Could it be any simpler than that!
44 |
45 | ## Parameters
46 |
47 | You can also use parameters in GraphQL queries:
48 |
49 | ```
50 | //Request:
51 | query fetchUser {
52 | user(id: "1") {
53 | name
54 | }
55 | }
56 | ```
57 |
58 | *Now wait a minute! That looks more than the promised simple JSON object without data. What are those `query` and `fetchUser` things doing here? I hope you aren't trying to sell me OData disguised as graphshit! Are you?*
59 |
60 | I'm not. And I'm glad that you are paying attention. In my opinion, GraphQL's approach is much more elegant than that initiative of Microsoft.
61 |
62 | ## Anatomy of a GraphQL Query:
63 |
64 | Looking back again:
65 |
66 | ```
67 | //Request:
68 | query fetchUser {
69 | user(id: "1") {
70 | name
71 | }
72 | }
73 | ```
74 |
75 | * `query` is an GraphQL `Operation`. It, as you might guess, fetches the data from server. This `query` operation is read-only, you cannot modify data with it.
76 |
77 | > The other valid value for GraphQL operation is `mutation`, which we can use to create, update or delete data and fetch the updated result (*doing CRUD, remember? Need to be able to do them all!*). We'll discuss more on mutations later.
78 |
79 | * `fetchUser` is just an arbitrary name of your query. You'll write whatever you feel right here. A meaningful name will make sense later to other developers about what the query does in short. Yeah, [that's one of the hardest things to do in computer science :( )](http://martinfowler.com/bliki/TwoHardThings.html)!
80 |
81 | * `user` is called a `field`. And `name` is a sub-field, you might say.
82 |
83 | * `id: "1"` is the `argument` on the `user` field. Arguments are unordered, by the way. `picture(width: 200, height: 100)` and `picture(height: 100, width: 200)` means the same to a GraphQL server.
84 |
85 | * And this whole darn thing is called a `document`.
86 |
87 |
88 | ----------
89 |
90 |
91 | However, if a query has no `arguments` or `directives` (we will discuss directives later, for now just assume it's another piece of the whole puzzle) or `name`, the `query` keyword can be omitted. GraphQL server will default to that. We used that shortcut with our first example above:
92 |
93 | ```
94 | // Request:
95 | {
96 | user {
97 | name
98 | }
99 | }
100 |
101 | ```
102 |
--------------------------------------------------------------------------------
/workshopper/exercises/basic_query_syntax/solution/solution.js:
--------------------------------------------------------------------------------
1 | // solution stuff here
--------------------------------------------------------------------------------
/workshopper/exercises/deep_dive_into_graphql_type_system/exercise.js:
--------------------------------------------------------------------------------
1 | const Exercise = require('workshopper-exercise'); module.exports = new Exercise()
--------------------------------------------------------------------------------
/workshopper/exercises/deep_dive_into_graphql_type_system/problem.md:
--------------------------------------------------------------------------------
1 | # Part 7: Deep Dive into GraphQL Type System
2 |
3 | This part may feel more like a manual than a tutorial. I wrote it to save myself from jumping between GraphQL RFC Spec and the test suit of the reference JavaScript implementation.
4 |
5 | At the heart of any GraphQL implementation, there is a description of what types of objects it can return, described with a GraphQL type system.
6 |
7 | The JavaScript implementation has the following named types implemented in it:
8 |
9 | ## `GraphQLScalarType`:
10 |
11 | Scalar types hold a single value. GraphQL provides a basic set of well‐defined Scalar types.
12 |
13 | `GraphQLInt` (example: 2)
14 |
15 | `GraphQLFloat` (example: 2.0)
16 |
17 | `GraphQLString` (example: "Hello World")
18 |
19 | `GraphQLBoolean` (example: true)
20 |
21 | `GraphQLID` (integer or string of any format)
22 |
23 | ## Other types:
24 |
25 | ## `GraphQLObjectType`
26 |
27 | GraphQL Objects represent a list of named fields, each of which also yields a value of their own type. Example:
28 |
29 | ```
30 | var PersonType = new GraphQLObjectType({
31 | name: 'Person',
32 | fields: () => ({
33 | name: {
34 | type: GraphQLString,
35 | description: 'The name of the person.',
36 | },
37 | age: {
38 | type: GraphQLInt,
39 | description: 'The age of the person.',
40 | }
41 | })
42 | });
43 |
44 | ```
45 |
46 | ## `GraphQLInterfaceType`
47 |
48 | GraphQL Interfaces represent a reusable collection of fields and their arguments. GraphQL object can then implement an interface, which guarantees that they will contain the specified fields.
49 |
50 | ```
51 | class Dog {
52 | constructor(name, barks) {
53 | this.name = name;
54 | this.barks = barks;
55 | }
56 | }
57 |
58 | class Cat {
59 | constructor(name, meows) {
60 | this.name = name;
61 | this.meows = meows;
62 | }
63 | }
64 |
65 | var NamedType = new GraphQLInterfaceType({
66 | name: 'Named',
67 | fields: {
68 | name: { type: GraphQLString }
69 | }
70 | });
71 |
72 | var DogType = new GraphQLObjectType({
73 | name: 'Dog',
74 | interfaces: [ NamedType ],
75 | fields: {
76 | name: { type: GraphQLString },
77 | barks: { type: GraphQLBoolean }
78 | },
79 | isTypeOf: (value) => value instanceof Dog
80 | });
81 |
82 | var CatType = new GraphQLObjectType({
83 | name: 'Cat',
84 | interfaces: [ NamedType ],
85 | fields: {
86 | name: { type: GraphQLString },
87 | meows: { type: GraphQLBoolean }
88 | },
89 | isTypeOf: (value) => value instanceof Cat
90 | });
91 |
92 | ```
93 |
94 | ## `GraphQLList`
95 |
96 | A GraphQL list is a collection type which declares the type of each item in the List.
97 |
98 | ```
99 | var BlogArticle = new GraphQLObjectType({
100 | name: 'Article',
101 | fields: {
102 | id: { type: GraphQLString },
103 | isPublished: { type: GraphQLBoolean },
104 | author: { type: GraphQLString },
105 | title: { type: GraphQLString },
106 | body: { type: GraphQLString }
107 | }
108 | });
109 |
110 | var BlogQuery = new GraphQLObjectType({
111 | name: 'Query',
112 | fields: {
113 | article: {
114 | args: { id: { type: GraphQLString } },
115 | type: BlogArticle
116 | },
117 | feed: {
118 | type: new GraphQLList(BlogArticle)
119 | }
120 | }
121 | });
122 |
123 | ```
124 |
125 | ## `GraphQLNonNull`
126 |
127 | By default, all types in GraphQL are nullable; the null value is a valid response for all of the above types. To declare a type that disallows null, the GraphQL Non‐Null type can be used.
128 |
129 | `GraphQLNonNull` acts more like a wrapper on other types. Remember to instantiate it with `new` when disallowing null on some typed field.
130 |
131 | ```
132 | var BlogArticle = new GraphQLObjectType({
133 | name: 'Article',
134 | fields: {
135 | id: { type: new GraphQLNonNull(GraphQLString) },
136 | isPublished: { type: new GraphQLNonNull(GraphQLBoolean) },
137 | author: { type: new GraphQLNonNull(GraphQLString) },
138 | title: { type: GraphQLString },
139 | body: { type: GraphQLString }
140 | }
141 | });
142 | ```
143 |
144 |
145 | ## `GraphQLEnumType`
146 |
147 | GraphQL Enum represents one the of possible values:
148 |
149 | ```
150 | var episodeEnum = new GraphQLEnumType({
151 | name: 'Episode',
152 | description: 'One of the films in the Star Wars Trilogy',
153 | values: {
154 | NEWHOPE: {
155 | value: 4,
156 | description: 'Released in 1977.',
157 | },
158 | EMPIRE: {
159 | value: 5,
160 | description: 'Released in 1980.',
161 | },
162 | JEDI: {
163 | value: 6,
164 | description: 'Released in 1983.',
165 | },
166 | }
167 | });
168 |
169 | ```
170 |
171 |
172 | ## `GraphQLUnionType`
173 |
174 | GraphQL Unions represent an object that could be one of items in a list of GraphQL Object types. They also differ from interfaces in that Object types declare what interfaces they implement, but are not aware of what unions contain them.
175 |
176 | ```
177 | class Dog {
178 | constructor(name, barks) {
179 | this.name = name;
180 | this.barks = barks;
181 | }
182 | }
183 |
184 | class Cat {
185 | constructor(name, meows) {
186 | this.name = name;
187 | this.meows = meows;
188 | }
189 | }
190 |
191 | var NamedType = new GraphQLInterfaceType({
192 | name: 'Named',
193 | fields: {
194 | name: { type: GraphQLString }
195 | }
196 | });
197 |
198 | var DogType = new GraphQLObjectType({
199 | name: 'Dog',
200 | interfaces: [ NamedType ],
201 | fields: {
202 | name: { type: GraphQLString },
203 | barks: { type: GraphQLBoolean }
204 | },
205 | isTypeOf: value => value instanceof Dog
206 | });
207 |
208 | var CatType = new GraphQLObjectType({
209 | name: 'Cat',
210 | interfaces: [ NamedType ],
211 | fields: {
212 | name: { type: GraphQLString },
213 | meows: { type: GraphQLBoolean }
214 | },
215 | isTypeOf: value => value instanceof Cat
216 | });
217 |
218 | var PetType = new GraphQLUnionType({
219 | name: 'Pet',
220 | types: [ DogType, CatType ],
221 | resolveType(value) {
222 | if (value instanceof Dog) {
223 | return DogType;
224 | }
225 | if (value instanceof Cat) {
226 | return CatType;
227 | }
228 | }
229 | });
230 |
231 | ```
232 |
233 | ## `GraphQLInputObjectType`
234 |
235 | Fields can define arguments that the client passes up with the query, to configure their behavior. These inputs can be Strings or Enums, but they sometimes need to be more complex than that.
236 |
237 | The Object type has not been used here intentionally, because Objects can contain fields that express circular references or references to interfaces and unions, neither of which is appropriate for use as an input argument. For this reason, input objects have a separate type in the system.
238 |
239 | An Input Object defines input fields; the input fields are either scalars, enums, or other input objects. This allows arguments to accept arbitrarily complex structures.
240 |
241 | ```
242 | var ComplexInput = new GraphQLInputObjectType({
243 | name: 'ComplexInput',
244 | fields: {
245 | requiredField: { type: new GraphQLNonNull(GraphQLBoolean) },
246 | intField: { type: GraphQLInt },
247 | stringField: { type: GraphQLString },
248 | booleanField: { type: GraphQLBoolean },
249 | stringListField: { type: new GraphQLList(GraphQLString) },
250 | }
251 | });
252 |
253 | var ComplicatedArgs = new GraphQLObjectType({
254 | name: 'ComplicatedArgs',
255 | fields: () => ({
256 | complexArgField: {
257 | type: GraphQLString,
258 | args: {
259 | complexArg: { type: ComplexInput }
260 | },
261 | }
262 | }),
263 | });
264 |
265 | ```
266 |
267 |
268 | ## Bonus: Creating custom scalar types
269 |
270 | In addition to built-in scalars, you can define your own custom scalar types. It is mostly helpful for doing fine-grained validations. In a lot of cases you might want to check if an email, date-time or url format is valid. It is easily doable by defining your custom email/datetime/url scalars.
271 |
272 | Here's a complete example of how to define a custom Email type with validation:
273 |
274 | ```
275 |
276 | import {
277 | graphql,
278 | GraphQLSchema,
279 | GraphQLObjectType,
280 | GraphQLString,
281 | GraphQLScalarType
282 | } from 'graphql';
283 |
284 | import { GraphQLError } from 'graphql/error';
285 | import { Kind } from 'graphql/language';
286 |
287 | var EmailType = new GraphQLScalarType({
288 | name: 'Email',
289 | serialize: value => {
290 | return value;
291 | },
292 | parseValue: value => {
293 | return value;
294 | },
295 | parseLiteral: ast => {
296 | if (ast.kind !== Kind.STRING) {
297 | throw new GraphQLError('Query error: Can only parse strings got a: ' + ast.kind, [ast]);
298 | }
299 |
300 | // Regex taken from: http://stackoverflow.com/a/46181/761555
301 | var re = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;
302 | if(!re.test(ast.value)) {
303 | throw new GraphQLError('Query error: Not a valid Email', [ast]);
304 | }
305 |
306 | return ast.value;
307 | }
308 | });
309 |
310 | var schema = new GraphQLSchema({
311 | query: new GraphQLObjectType({
312 | name: 'RootQueryType',
313 | fields: {
314 | echo: {
315 | type: GraphQLString,
316 | args: {
317 | email: { type: EmailType }
318 | },
319 | resolve: (root, {email}) => {
320 | return email;
321 | }
322 | }
323 | }
324 | })
325 | });
326 |
327 | var query = `
328 | query Welcome {
329 | echo (email: "hi@example.com")
330 | }
331 | `;
332 |
333 | graphql(schema, query).then((result) => {
334 |
335 | // Prints
336 | // {
337 | // echo: { 'hi@example.com' }
338 | // }
339 | console.log(result);
340 | });
341 |
342 |
343 | ```
344 |
345 | You'll get an error if you provide a malformed email parameter to our echo query:
346 |
347 | ```
348 | // ... truncated
349 |
350 | var query = `
351 | query Welcome {
352 | echo (email: "hi")
353 | }
354 | `;
355 |
356 | graphql(schema, query).then((result) => {
357 |
358 | // Prints
359 | // { errors: [ { [Error: Query error: Not a valid Email] message: 'Query error: Not a valid Email' } ] }
360 |
361 | console.log(result);
362 | });
363 |
364 | ```
365 |
366 | If you need to define similar custom scalar types for date, time, date-time, url etc., defining all of them separately is going to be a lot of boilerplate code, right?
367 |
368 | We can refactor the above code like this to avoid that:
369 |
370 | ```
371 | import {
372 | graphql,
373 | GraphQLSchema,
374 | GraphQLObjectType,
375 | GraphQLString,
376 | GraphQLScalarType
377 | } from 'graphql';
378 |
379 | import { GraphQLError } from 'graphql/error';
380 | import { Kind } from 'graphql/language';
381 |
382 | var ValidateStringType = (params) => {
383 | return new GraphQLScalarType({
384 | name: params.name,
385 | serialize: value => {
386 | return value;
387 | },
388 | parseValue: value => {
389 | return value;
390 | },
391 | parseLiteral: ast => {
392 | if (ast.kind !== Kind.STRING) {
393 | throw new GraphQLError("Query error: Can only parse strings got a: " + ast.kind, [ast]);
394 | }
395 | if (ast.value.length < params.min) {
396 | throw new GraphQLError(`Query error: minimum length of ${params.min} required: `, [ast]);
397 | }
398 | if (ast.value.length > params.max){
399 | throw new GraphQLError(`Query error: maximum length is ${params.max}: `, [ast]);
400 | }
401 | if(params.regex !== null) {
402 | if(!params.regex.test(ast.value)) {
403 | throw new GraphQLError(`Query error: Not a valid ${params.name}: `, [ast]);
404 | }
405 | }
406 | return ast.value;
407 | }
408 | })
409 | };
410 |
411 | var EmailType = ValidateStringType({
412 | name: 'Email',
413 | min: 4,
414 | max: 254,
415 | regex: /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i
416 | });
417 |
418 | var schema = new GraphQLSchema({
419 | query: new GraphQLObjectType({
420 | name: 'RootQueryType',
421 | fields: {
422 | echo: {
423 | type: GraphQLString,
424 | args: {
425 | email: { type: EmailType }
426 | },
427 | resolve: (root, {email}) => {
428 | return email;
429 | }
430 | }
431 | }
432 | })
433 | });
434 |
435 | var query = `
436 | query Welcome {
437 | echo (email: "hi@example.com")
438 | }
439 | `;
440 |
441 | graphql(schema, query).then((result) => {
442 |
443 | // Prints
444 | // {
445 | // echo: { 'hi@example.com' }
446 | // }
447 | console.log(result);
448 | });
449 |
450 | ```
451 |
452 | Now you can define more custom scalar types using `ValidateStringType` function. *Thanks [pyros2097](https://github.com/pyros2097) for the suggestion and `ValidateStringType` snippet!*
453 |
--------------------------------------------------------------------------------
/workshopper/exercises/deep_dive_into_graphql_type_system/solution/solution.js:
--------------------------------------------------------------------------------
1 | // solution stuff here
--------------------------------------------------------------------------------
/workshopper/exercises/introspection/exercise.js:
--------------------------------------------------------------------------------
1 | const Exercise = require('workshopper-exercise'); module.exports = new Exercise()
--------------------------------------------------------------------------------
/workshopper/exercises/introspection/problem.md:
--------------------------------------------------------------------------------
1 | # Part 9: Introspection
2 |
3 | It's often useful to ask a GraphQL schema for information about what queries it supports. GraphQL allows us to do so using the introspection system.
4 |
5 | TBD.
6 |
--------------------------------------------------------------------------------
/workshopper/exercises/introspection/solution/solution.js:
--------------------------------------------------------------------------------
1 | // solution stuff here
--------------------------------------------------------------------------------
/workshopper/exercises/menu.json:
--------------------------------------------------------------------------------
1 | [
2 | "What is GraphQL",
3 | "Basic Query Syntax",
4 | "Querying with Field Aliases and Fragments",
5 | "Querying with Directives",
6 | "On the Server-Side - Creating Your First Schema",
7 | "A Working GraphQL Server in Nodejs",
8 | "Deep Dive into GraphQL Type System",
9 | "Mutations",
10 | "Introspection",
11 | "A Working GraphQL Server v2"
12 | ]
13 |
--------------------------------------------------------------------------------
/workshopper/exercises/mutations/exercise.js:
--------------------------------------------------------------------------------
1 | const Exercise = require('workshopper-exercise'); module.exports = new Exercise()
--------------------------------------------------------------------------------
/workshopper/exercises/mutations/problem.md:
--------------------------------------------------------------------------------
1 | # Part 8: Mutations
2 |
3 | So far we have only talked about the `query` operation, which is read-only. What about modifying data?
4 |
5 | Unlike REST, which uses different HTTP verbs for Creating, Updating, Deleting resources, GraphQL treats all operations with side-effects similarly, and calls them `mutation`.
6 |
7 | By GraphQL definitions mutations are **"writes followed by a fetch"**. You'll return the modified data after mutating.
8 |
9 | Adding mutation to a GraphQL schema is very similar to adding query operations. Let's add one.
10 |
11 | Here's our schema from part 6 of the series:
12 |
13 | ```
14 | //schema.js
15 | import {
16 | graphql,
17 | GraphQLSchema,
18 | GraphQLObjectType,
19 | GraphQLString
20 | } from 'graphql';
21 |
22 | let dummyData = {
23 | '1': 'leebyron',
24 | '2': 'enaqx',
25 | '3': 'schrockn',
26 | '4': 'andimarek'
27 | };
28 |
29 | export var schema = new GraphQLSchema({
30 | query: new GraphQLObjectType({
31 | name: 'RootQueryType',
32 | fields: {
33 | contributor: {
34 | type: GraphQLString,
35 | args: {
36 | id: { type: GraphQLString }
37 | },
38 | //Using Destructuring of ES2015 to assign value to id
39 | resolve: (root, {id}) => {
40 | return dummyData[id];
41 | }
42 | }
43 | }
44 | })
45 | });
46 | ```
47 |
48 | We need to start by adding a top level key `mutation` to it, rest of the things are similar.
49 |
50 | ```
51 | var schema = new GraphQLSchema({
52 | query: new GraphQLObjectType({
53 | name: 'RootQueryType',
54 | fields: {
55 | contributor: {
56 | type: GraphQLString,
57 | args: {
58 | id: { type: GraphQLString }
59 | },
60 | //Using Destructuring of ES2015 to assign value to id
61 | resolve: (root, {id}) => {
62 | return dummyData[id];
63 | }
64 | }
65 | }
66 | }),
67 |
68 | mutation: new GraphQLObjectType({
69 | name: 'RootMutationType',
70 | fields: {
71 | updateContributor: {
72 | type: GraphQLString,
73 | args: {
74 | id: { type: GraphQLString },
75 | username: { type: GraphQLString },
76 | },
77 | resolve: (root, {id, username}) => {
78 | dummyData[id] = username;
79 | return dummyData[id];
80 | }
81 | }
82 | }
83 | })
84 | });
85 | ```
86 | Now you should get `{"data":{"updateContributor":"dschafer"}}` if you post the following `UpdateContributorUsername` mutation:
87 |
88 | ```
89 | curl -XPOST -d 'mutation UpdateContributorUsername {updateContributor (id: "5", username: "dschafer")}' http://localhost:8080/
90 | ```
91 |
92 | One important difference between mutation and query operations is, if you post multiple mutations together, they are processed serially in order to ensure data integrity, whereas independent queries are processed concurrently by the GraphQL executor.
93 |
--------------------------------------------------------------------------------
/workshopper/exercises/mutations/solution/solution.js:
--------------------------------------------------------------------------------
1 | // solution stuff here
--------------------------------------------------------------------------------
/workshopper/exercises/on_the_serverside__creating_your_first_schema/exercise.js:
--------------------------------------------------------------------------------
1 | const Exercise = require('workshopper-exercise'); module.exports = new Exercise()
--------------------------------------------------------------------------------
/workshopper/exercises/on_the_serverside__creating_your_first_schema/problem.md:
--------------------------------------------------------------------------------
1 | # Part 5: On the server-side - Creating your first schema
2 |
3 | We've learnt a great deal of how to prepare GraphQL queries. Let's dive into the next step- building a very simple GraphQL server.
4 |
5 | The first step to build a working GraphQL API is providing a `schema`. The GraphQL engine will parse queries, then validate and return the data based on your designed type schema.
6 |
7 | By design, GraphQL schemas are **strongly typed**. This gives the system a way to validate the query and guarantees the *shape* of the response.
8 |
9 | Here's an example of a simple GraphQL schema that returns `{hello: 'world'}` for a `{hello}` query:
10 |
11 | ```
12 | import {
13 | graphql,
14 | GraphQLSchema,
15 | GraphQLObjectType,
16 | GraphQLString
17 | } from 'graphql';
18 |
19 | var schema = new GraphQLSchema({
20 | query: new GraphQLObjectType({
21 | name: 'RootQueryType',
22 | fields: {
23 | hello: {
24 | type: GraphQLString,
25 | resolve: () => 'world'
26 | }
27 | }
28 | })
29 | });
30 |
31 | /* Dummy GraphQL query.
32 | In real-life we'll get it from the client side.
33 | */
34 | var query = `
35 | query HelloQuery {
36 | { hello }
37 | }
38 | `;
39 |
40 | graphql(schema, query).then((result) => {
41 | // Prints
42 | // {
43 | // data: { hello: "world" }
44 | // }
45 | console.log(result);
46 | });
47 |
48 | ```
49 |
50 | If you need to accept parameters to get results, that's easy too. We need to define what type of parameter we accept via an `args` object on fields, and pass that to the `resolve` function. Here's an updated version of the above sample that takes a `greet` parameter of `GraphQLString` type:
51 |
52 | ```
53 | import {
54 | graphql,
55 | GraphQLSchema,
56 | GraphQLObjectType,
57 | GraphQLString
58 | } from 'graphql';
59 |
60 |
61 | var schema = new GraphQLSchema({
62 | query: new GraphQLObjectType({
63 | name: 'RootQueryType',
64 | fields: {
65 | hello: {
66 | type: GraphQLString,
67 | args: {
68 | greet: { type: GraphQLString }
69 | },
70 | //Using Destructuring feature of ES2015 to assign value to greet
71 | resolve: (root, {greet}) => {
72 | greet = greet ? greet + '!' : 'world!';
73 | return greet;
74 | }
75 | }
76 | }
77 | })
78 | });
79 |
80 | var query = `
81 | query Welcome {
82 | hello (greet: "mehdi")
83 | }
84 | `;
85 |
86 | graphql(schema, query).then((result) => {
87 |
88 | // Prints
89 | // {
90 | // data: { hello: "mehdi" }
91 | // }
92 | console.log(result);
93 | });
94 |
95 | ```
96 |
97 | > Have you noticed that we are using double-quotes in `hello (greet: "mehdi")` in our query? That's because using single-quotes in `GraphQLString` type parameters is invalid. Tada!
98 |
99 | If you are using server-side MVC frameworks like Ruby on Rails, Laravel or SailsJS, you are probably getting a hint here that the coming days are going to be a lot different with GraphQL. The `V` in MVC has already gone from the server-side in API only use cases. What's next? I can only speculate there will be rudimentary routing for our API servers, as we won't have a bunch of routes but a single endpoint. The schema will do a lot of validation for us, a lot of authentication & authorization will be on middleware layers, as they are now. The `resolve` function of the fields will probably take a lot of responsibility from controllers. The only least affected areas might be the models/ORM layer. Or maybe GraphQL will co-exists with REST and pre-dominant MVC style server-side architecture for a long time. I don't know. But I'm excited to embrace what's coming in the near future!
100 |
--------------------------------------------------------------------------------
/workshopper/exercises/on_the_serverside__creating_your_first_schema/solution/solution.js:
--------------------------------------------------------------------------------
1 | // solution stuff here
--------------------------------------------------------------------------------
/workshopper/exercises/querying_with_directives/exercise.js:
--------------------------------------------------------------------------------
1 | const Exercise = require('workshopper-exercise'); module.exports = new Exercise()
--------------------------------------------------------------------------------
/workshopper/exercises/querying_with_directives/problem.md:
--------------------------------------------------------------------------------
1 | # Part 4: Querying with Directives
2 |
3 | In some cases you may need to provide options to alter GraphQL’s execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field *if* some criteria meets or not. Directives enable us to do just that.
4 |
5 | Right now GraphQL comes with two directives-
6 | `@skip` and `@include`, but future extensions to it may include more.
7 |
8 | ## `@skip`
9 |
10 | The `@skip` directive may be provided for fields or fragments, and allows for conditional exclusion as described by the if argument.
11 |
12 | In this example `experimentalField` will be queried only if the `$someTest` is provided a `false` value.
13 |
14 | ```
15 | query myQuery($someTest: Boolean) {
16 | experimentalField @skip(if: $someTest)
17 | }
18 |
19 | ```
20 |
21 | ## `@include`
22 |
23 | The `@include` directive may be provided for fields or fragments, and allows for conditional inclusion during execution as described by the if argument.
24 |
25 | In this example `experimentalField` will be queried only if the `$someTest` is provided a `true` value.
26 |
27 | ```
28 | query myQuery($someTest: Boolean) {
29 | experimentalField @include(if: $someTest)
30 | }
31 |
32 | ```
33 |
34 | Just a note, the `@skip` directive has precedence over the `@include` directive should both be provided in the same context.
35 |
36 | > If you are still with me, you have probably noticed the use of variables (`$someTest`) in GraphQL queries. *Well, does this work? How does it work? Like view templates? What's the benefit of writing our queries this way? How do you assign values to these variables? How do you send these queries to server? By assigning values in place? Or the values are passed seperately?*
37 | >
38 | > I know. I have asked all those questions the moment I saw this syntax for the first time.
39 | >
40 | > Well, for starters, the purpose of writing queries this way is to make them reusable and generic.
41 | >
42 | > We don't need anything special to place values of these variables inline to a GraphQL query before sending it to the server. Generic queries are not like view templates, we don't need to "compile" them with values. Instead, we send the values to our variables as separate JSON objects.
43 | >
44 | > The GraphQL server will take the query and parameters from the request body and parse them. The `graphql` function, that the server-side GraphQL API exposes, takes the following parameters:
45 | >
46 | > `graphql(schema, graphQuery, rootValue, variableValues, operationName)`
47 | >
48 | > We can provide our query as `graphQuery` and variable values as `variableValues`. We'll talk about the rest of the things in these parameters in successive parts of this series.
49 |
50 | As a working example, we can use this simple nodejs app to post queries to an endpoint that accepts GraphQL queries.
51 |
52 | (*We have used some ES2015 goodies here, if something doesn't feel familiar please check the new features of ES2015. We are using `Babel` to automatically transpile our ES2015 code to ES5, since nodejs doesn't have all the necessary parts in its core to natively run ES2015, yet.*)
53 |
54 | In the terminal:
55 | ```
56 | npm i babel request
57 | touch index.js server.js
58 |
59 | ```
60 |
61 | Put the following in `index.js`. **This is the entry point of our app. We'll run this file with node**:
62 | ```
63 | // By requiring `babel/register`, all of our successive requires will be transpiled by Babel.
64 | require('babel/register');
65 | require('./server.js');
66 | ```
67 |
68 | In our `server.js` file:
69 | ```
70 | import {request} from 'request';
71 |
72 | const SERVER_URL = 'https://graphql.example.com/';
73 |
74 | let query = `
75 | query myQuery($someTest: Boolean) {
76 | experimentalField @include(if: $someTest)
77 | }
78 | `;
79 |
80 | let params = {
81 | someTest: true
82 | };
83 |
84 | //Unlike REST, GraphQL doesn't specify which HTTP Verb to use.
85 | //POST seems to be a right fit here.
86 | request.post(SERVER_URL, {
87 | query,
88 | params
89 | }, (err, res, body) => {
90 | // Handle response
91 | });
92 | ```
93 |
94 | This example is here just to show how to post a GraphQL query programmatically in nodejs. Of course the same can be achieved with `curl`, `Postman`, `httpie`, `XMLHttpRequest` or the modern `fetch` API in browsers. If you are using React (you probably are using that), Relay will take care of it.
95 |
96 | Now what about the GraphQL server that'll process the query? We haven't said anything about how to write a GraphQL server yet. Let's start that in the next part.
97 |
--------------------------------------------------------------------------------
/workshopper/exercises/querying_with_directives/solution/solution.js:
--------------------------------------------------------------------------------
1 | // solution stuff here
--------------------------------------------------------------------------------
/workshopper/exercises/querying_with_field_aliases_and_fragments/exercise.js:
--------------------------------------------------------------------------------
1 | const Exercise = require('workshopper-exercise'); module.exports = new Exercise()
--------------------------------------------------------------------------------
/workshopper/exercises/querying_with_field_aliases_and_fragments/problem.md:
--------------------------------------------------------------------------------
1 | # Part 3: Querying with Field Aliases & Fragments
2 |
3 | We have seen basic queries with parameters. Let's get into deeper water.
4 |
5 | ## Field Aliases
6 |
7 | Consider the GraphQL query from the previous section:
8 |
9 | ```
10 | // Request
11 | query FetchUser {
12 | user(id: "1") {
13 | name
14 | }
15 | }
16 |
17 | // Response:
18 | {
19 | data: {
20 | user: {
21 | name: "Lee Byron"
22 | }
23 | }
24 | }
25 | ```
26 |
27 | What if you need to fetch multiple users in a single query? You already have `user` as the property of `data` object, how would you fit another user there?
28 |
29 | `Field Aliases` can be handy in these cases. You can give your fields any valid name you want, and the response will match that query. (`user` is called a `field` here, you remember that from the previous lesson, right?)
30 |
31 | ```
32 | // Request
33 | query FetchLeeAndSam {
34 | lee: user(id: "1") {
35 | name
36 | }
37 | sam: user(id: "2") {
38 | name
39 | }
40 | }
41 | ```
42 |
43 | The `field` name `user` is still the same, you are just instructing GraphQL server to provide Lee's data and Sam's data in their separate keys.
44 |
45 | ```
46 | // Response:
47 | {
48 | data: {
49 | lee: {
50 | name: "Lee Byron"
51 | },
52 | sam: {
53 | name: "Sam"
54 | }
55 | }
56 | }
57 | ```
58 |
59 | By the way, using a comma between `field`s in a GraphQL query is optional. You can use them between `lee` and `sam` if that makes you comfortable, like this:
60 |
61 | ```
62 | // Request
63 | query FetchLeeAndSam {
64 | lee: user(id: "1") {
65 | name
66 | },
67 | sam: user(id: "2") {
68 | name
69 | }
70 | }
71 | ```
72 |
73 | ## Fragments
74 |
75 | Now imagine your product manager suddenly came and asked to include Lee and Sam's email addresses to the UI. We could ask for the new data with this query of course:
76 |
77 | ```
78 | // Request
79 | query FetchLeeAndSam {
80 | lee: user(id: "1") {
81 | name
82 | email
83 | }
84 | sam: user(id: "2") {
85 | name
86 | email
87 | }
88 | }
89 | ```
90 |
91 | We can already see that we are repeating ourselves. Since we have to add new fields (email) to both parts of the query, things are looking a bit odd. Instead, we can extract out the common fields into a `fragment`, and reuse the fragment in the query, like this:
92 |
93 | ```
94 | // Request
95 | query FetchWithFragment {
96 | lee: user(id: "1") {
97 | ...UserFragment
98 | }
99 | sam: user(id: "2") {
100 | ...UserFragment
101 | }
102 | }
103 |
104 | fragment UserFragment on User {
105 | name
106 | email
107 | }
108 |
109 | ```
110 |
111 | > This `...` is called the spread operator. If you are familiar with the new features of ES6/ES2015, you already have used similar syntax there.
112 | >
113 | > The `on User` tells us that the `UserFragment` can only be applied to the `User` type. I already gave you a hint that GraphQL is strongly-typed, we'll go through the details of the type system later.
114 |
115 | Both of those queries give this result:
116 |
117 | ```
118 | // Response:
119 | {
120 | data: {
121 | lee: {
122 | name: "Lee Byron",
123 | email: lee@example.com
124 | },
125 | sam: {
126 | name: "Sam",
127 | email: sam@example.com
128 | }
129 | }
130 | }
131 | ```
132 |
133 | The `FetchLeeAndSam` and `FetchWithFragment` queries will both get the same result, but `FetchWithFragment` is less verbose and less repetitive; if we wanted to add more fields, we could add it to the common fragment rather than copying it into multiple places.
134 |
135 | ##Inline Fragment
136 |
137 | There is another similar thing called `inline fragments`, which as you might have guessed, can be defined inline to queries. This is done to conditionally execute fields based on their runtime type.
138 |
139 | In the following example, based on the type of the show, we will get:
140 |
141 | - sequels for the "The Matrix" show, which is of the `Movie` type
142 | - episodes for the "Mr. Robot" show, which is of the `Tvshow` type.
143 |
144 | ```
145 | query inlineFragmentTyping {
146 | shows(titles: ["The Matrix", "Mr. Robot"]) {
147 | title
148 | ... on Movie {
149 | sequel {
150 | name
151 | date
152 | }
153 | }
154 | ... on Tvshow {
155 | episode {
156 | name
157 | date
158 | }
159 | }
160 | }
161 | }
162 | ```
163 |
164 |
165 | There are other ways to conditionally modify a query. `Directive` plays the key role in that and we are going to talk about them in the next part.
166 |
--------------------------------------------------------------------------------
/workshopper/exercises/querying_with_field_aliases_and_fragments/solution/solution.js:
--------------------------------------------------------------------------------
1 | // solution stuff here
--------------------------------------------------------------------------------
/workshopper/exercises/what_is_graphql/exercise.js:
--------------------------------------------------------------------------------
1 | const Exercise = require('workshopper-exercise'); module.exports = new Exercise()
--------------------------------------------------------------------------------
/workshopper/exercises/what_is_graphql/problem.md:
--------------------------------------------------------------------------------
1 | # Part 1: What is GraphQL
2 |
3 | ```
4 | _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
5 | | |
6 | | The dirty secret: |
7 | | We’re all just building CRUD apps. |
8 | | |
9 | | @iamdevloper |
10 | | _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
11 | ```
12 | While giggling at the funny tweet, you and I both know that it is not that simple. If you are a developer and haven't been in a cave for last few years, you have seen the rise of complex single page and mobile applications in general. While developing both, sometimes I feel that we probably have entered the "client-side heavy" era without much of prior preparation. There have been a lot of confusion, experiments, thrill in how to cope up with the changes, how to structure code and the flow, how to be on the edge of performance, but with all that, we seem to have overlooked some basic things repeatedly. Data fetching is one of those things.
13 |
14 | Most of us are using REST APIs for that. While meeting our needs for most of the times, it also made our front-end developers to frequently figure out why some properties are missing in the JSON response they got from the API, what's changed in the last sprint, why some shit is not in the API documentation. Our back-end developers are constantly trying to cope up with the nitty gritty changes in dozens of client side applications and their numerous slightly different version that depend on the API. For years, we took this miserable life for granted.
15 |
16 | Don't get me wrong. REST is of course simple, there is almost no learning curve. Here is your resource, ask for it politely with the proper verb, be sure to check your status code and be done with the body. It blends perfectly with HTTP. I was a kid building native desktop applications when the world was cheering to move from SOAP to REST, XML to JSON, Configuration to Convention, so I didn't take much notice on the web. But I can feel the excitement of those times just by looking at GraphQL.
17 |
18 | I think the main problem of REST is that it tried to blend too much with HTTP instead of focusing on the nature of our data. Maybe that's because it came from a time where most complex data mingling was done on sever-side and the client was served with properly rendered pages. Things have changed. Restaurants became grocery shops, they now serve raw ingredients and assume their clients are new chefs, they know how to cook and prepare a presentable dish. Imagine a real-life communication between a chef and grocery market, that is much more chaotic than what's going on between a customer and waitress in a restaurant. Same goes for SPA and Mobile Apps. Some tooling became long due to cope up with the change.
19 |
20 | If you look close enough, most of our data is connected like graphs. You cannot just get away with an author's data, you need information about the books they have written. You cannot talk about a TV Show without talking at all about its episodes. A good dish has complex taste by requirement. The same goes for a finished product, it is never as simple as individually lived resources.
21 |
22 | The first step to solve a problem is to acknowledge that it exists. GraphQL (fortunately) does so for us. And not just that, it also agrees that product designs start with views, so front-end engineers should be able to declare what data they need instead of waiting for the backend guys to make necessary changes in API for them. The world needs to move faster. REST was failing to cope up with that, *elegantly*.
23 |
24 |
25 | There are more actually. Read [GraphQL Introduction](http://facebook.github.io/react/blog/2015/05/01/graphql-introduction.html) to feed your curious monster. Watch the [announcement on React Europe 2015 Conf](https://www.youtube.com/watch?v=WQLzZf34FJ8), and a [deep dive to it](https://www.youtube.com/watch?v=gY48GW87Feo).
26 |
27 | The official site is here: [graphql.org](http://graphql.org/)
28 |
29 |
30 | ## What GraphQL is not:
31 |
32 | * GraphQL is not for any specific language or framework. Although the [reference implementation](https://github.com/graphql/graphql-js) has been released in JavaScript, the GraphQL core is expected to be ported to all major languages.
33 |
34 | * GraphQL does not assume any specific data store (just because it has "QL" in its name, don't assume anything similar to SQL). You can use relational, non-relational or other REST style backends as your data source. The job of the GraphQL server is parsing, validating, providing data and mutating (updating) it based on clients' requests.
35 |
36 | ## What GraphQL *is*:
37 |
38 | * It is Declarative:
39 |
40 | Query responses are decided by the client rather than the server. A GraphQL query returns exactly what a client asks for and no more.
41 |
42 | * Compositional:
43 |
44 | A GraphQL query itself is a hierarchical set of fields. The query is shaped just like the data it returns. It is a natural way for product engineers to describe data requirements.
45 |
46 | * Strongly-typed
47 |
48 | A GraphQL query can be ensured to be valid within a GraphQL type system at development time allowing the server to make guarantees about the response. This makes it easier to build high-quality client tools.
49 |
50 | We'll discuss them as we go through the series.
51 |
--------------------------------------------------------------------------------
/workshopper/exercises/what_is_graphql/solution/solution.js:
--------------------------------------------------------------------------------
1 | // solution stuff here
--------------------------------------------------------------------------------
/workshopper/learning-graphql.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const workshopper = require('workshopper'),
4 | path = require('path')
5 |
6 | function fpath (f) {
7 | return path.join(__dirname, f)
8 | }
9 |
10 | workshopper({
11 | name : 'learning-graphql',
12 | title : 'Learning GraphQL',
13 | subtitle : 'Learning GraphQL through a terminal workshop.',
14 | appDir : __dirname,
15 | menuItems : [],
16 | exerciseDir : fpath('./exercises/')
17 | });
18 |
--------------------------------------------------------------------------------
/workshopper/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "learning-graphql",
3 | "version": "0.0.1",
4 | "description": "Learning GraphQL through a terminal workshop.",
5 | "author": "Mehdi Hasan Khan ",
6 | "contributors": [],
7 | "scripts": {
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "license": "MIT",
11 | "repository" : {
12 | "type" : "git",
13 | "url" : "https://github.com/mugli/learning-graphql"
14 | },
15 | "dependencies": {
16 | "workshopper": "^2.7.0",
17 | "workshopper-exercise": "^2.4.0"
18 | },
19 | "bin": {
20 | "learning-graphql": "./learning-graphql.js"
21 | },
22 | "preferGlobal": true
23 | }
24 |
--------------------------------------------------------------------------------
/workshopper/workshopper.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mugli/learning-graphql/56cf19e3475ad402f4c372497e4dc894c1158238/workshopper/workshopper.png
--------------------------------------------------------------------------------