-
Notifications
You must be signed in to change notification settings - Fork 0
/
07 - On Request.js
160 lines (142 loc) · 3.49 KB
/
07 - On Request.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
/**
* Lesson 07 - On Request
*
* Right now we're creating "userLoader" when the server starts
* and we don't have any code to purge the cache between requests
*
* So a second query for the same item is still served from the cache
*
* Network caches like this are a good idea, but this is not
* the place for them. Server RAM is expensive and this implementation
* poses major security risks
*
* We can eliminate all of that by using a cache that only exists for
* the duration of a single network request
*
* We're going to do that by making new DataLoaders on every request
* using Apollo's context method
*
* Copy the userLoader directly from its declaration into the Apollo context
*
* And it will be available as the third argument to every single resolver
*
* Now we can see the caching and batching working flawlessly on a per-request level
*
query {
posts {
id
title
author {
id
name
}
}
}
*
*/
const { ApolloServer, gql } = require('apollo-server')
const sql = require('knex')({
client: 'pg',
connection: {
host : '127.0.0.1',
port : 5432,
user : 'postgres',
password : 'password',
database : 'postgres'
}
});
// A schema is a collection of type definitions (hence "typeDefs")
// that together define the "shape" of queries that are executed against
// your data.
const typeDefs = gql`
type Query {
posts: [Post]
}
type Post {
id: String
title: String
author: User
}
type User {
id: String
name: String
}
`
class DataLoader {
constructor(batchFn, options = {}) {
this.batchFn = batchFn
if(options.schedulingFn) {
this.schedulingFn = options.schedulingFn
}
this.promises = {}
this.activeQuery = null
}
batchFn() {
throw new Error('Not implemented')
}
schedulingFn() {
// Wait until the batch is ready
// Increase the timeout to make larger, slower batches
return new Promise((resolve) => {
setTimeout(() => {
resolve()
})
})
}
async load(key) {
if (this.promises[key]) {
// We've queried this before, return the cached promise
return this.promises[key]
} else {
// Just a placeholder until the batch is ready to go
this.promises[key] = null
}
await this.schedulingFn()
if (!this.activeQuery) {
// Query all IDs at once
const ids = Object.keys(this.promises)
console.log(`SELECT * from users WHERE id in (${ids.join(',')})`)
this.activeQuery = this.batchFn(ids)
}
// Cache a promise that waits on the active query
this.promises[
key
] = this.activeQuery.then((items) => {
// And selects the item with this id
return items.find((item) => item.id === Number(key))
})
return this.promises[key]
}
}
const resolvers = {
Query: {
posts() {
// Executes once per query
console.log('SELECT * from posts')
return sql('posts').select('*')
},
},
Post: {
async author(post, args, { userLoader }) {
// Executes once per post per query
return userLoader.load(post.author_id)
},
},
}
const server = new ApolloServer({
typeDefs,
resolvers,
async context() {
return {
userLoader: new DataLoader(keys => sql
.select('*')
.from('users')
.whereIn('id', keys)
)
}
}
})
// The `listen` method launches a web server.
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
})