1 | # A neat GraphQL server in JavaScript
|
2 |
|
3 | MVP for a GraphQL server that makes the developers' life easier.
|
4 |
|
5 | This GraphQL server is built on
|
6 | * graphql-js
|
7 |
|
8 | ## How to use:
|
9 |
|
10 | Apollo-server takes three inputs:
|
11 |
|
12 | 1. A GraphQL schema in shorthand format
|
13 | 1. One or more javascript code files that defines resolve functions
|
14 | 1. One or more javascript code files that define data loaders
|
15 |
|
16 | An example of a valid GraphQL shorthand input:
|
17 | ```
|
18 | type Book {
|
19 | id: ID!
|
20 | title: String!
|
21 | genre: Genre
|
22 | author: Author
|
23 | }
|
24 |
|
25 | interface Person {
|
26 | firstName: String!
|
27 | lastName: String
|
28 | middleName: String
|
29 | }
|
30 |
|
31 | type Author: Person {
|
32 | id: ID!
|
33 | firstName: String!
|
34 | lastName: String
|
35 | middleName: String
|
36 | booksPublished: [Book]
|
37 | }
|
38 |
|
39 | enum Genre {
|
40 | FICTION
|
41 | NONFICTION
|
42 | }
|
43 |
|
44 | type RootQuery {
|
45 | author(id: ID!): Author,
|
46 | book(title: String!): Book
|
47 | }
|
48 |
|
49 | type RootMutation {
|
50 | addBook(title: String!, genre: Genre!, author_id: ID): Book
|
51 | }
|
52 | ```
|
53 |
|
54 | The corresponding file that defines how fields are resolved. If a field does not
|
55 | have a resolve function, it is assumed to be the standard resolve function.
|
56 | Resolve functions should be stateless.
|
57 | ```
|
58 | const resolveFunctions = {
|
59 | Book: {
|
60 | author(book, _, ctx){ return ctx.loaders.author.get(book.author_id) },
|
61 |
|
62 | // fields without resolve function just return book.<fieldName>
|
63 | },
|
64 |
|
65 | Author: {
|
66 | booksPublished(author, _, _){ return ctx.loaders.book.list( {author_id: author.id }) },
|
67 | },
|
68 |
|
69 | RootQuery: {
|
70 | author(_, { id }, ctx){ return ctx.loaders.author.get(id) },
|
71 | book(_, { title }, ctx){ return ctx.loaders.book.getByTitle(title) },
|
72 | },
|
73 |
|
74 | RootMutation: {
|
75 | addBook(_, { title, genre, author_id }, ctx){
|
76 | return ctx.loaders.book.createBook({ title: title, genre: genre, author_id: author_id });
|
77 | },
|
78 | },
|
79 | };
|
80 | export default resolveFunctions;
|
81 | ```
|
82 |
|
83 | And in a file that defines the data loaders:
|
84 | ```
|
85 | // imports ...
|
86 |
|
87 | const loaders = {
|
88 | book: {
|
89 | createBook(args){
|
90 | return knex('books').insert({ ...args });
|
91 | },
|
92 | },
|
93 | // etc.
|
94 | };
|
95 |
|
96 | export default loaders;
|
97 | ```
|
98 |
|
99 |
|
100 | Providing a schema in this format has the advantage of staying relatively close
|
101 | to graphql-js while at the same time separating the type system from the
|
102 | resolve rules for clarity.
|
103 |
|
104 | Separating data loaders from resolve functions makes them easier to reason about and to test. It
|
105 | also means that the only stateful part is clearly separated from the rest of the system, which
|
106 | has benefits for scaling.
|
107 |
|
108 | It is still possible to dynamically generate the resolve functions, as they just
|
109 | have to be exported from the file that Apollo Proxy imports for the schema.
|
110 |
|
111 |
|
112 | It is also possible to not use the shorthand with resolve definitions and simply
|
113 | upload a schema for graphql-js in the standard format.
|