You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -13,14 +13,15 @@ Full API docs are available at https://ef-eng.github.io/graphql-query-rewriter
13
13
14
14
GraphQL is great at enforcing a strict schema for APIs, but its lack of versioning makes it extremely difficult to make changes to GraphQL schemas without breaking existing clients. For example, take the following query:
15
15
16
-
```
16
+
```graphql
17
17
querygetUserById($id: String!) {
18
18
userById(id: $id) {
19
19
...
20
20
}
21
21
}
22
22
```
23
-
Oh no! We should have used `ID!` as the type for `userById(id)` instead of `String!`, but it's already in production! Now if we change our schema to use `ID!` instead of `String!` then our old clients will start getting the error `Variable "$id" of type "String!" used in position expecting type "ID!"`. Currently your only options are to continue using the incorrect `String!` type forever (*eeew*), or make a new query with a new name, like `userByIdNew(id: ID!)` (*gross*)!
23
+
24
+
Oh no! We should have used `ID!` as the type for `userById(id)` instead of `String!`, but it's already in production! Now if we change our schema to use `ID!` instead of `String!` then our old clients will start getting the error `Variable "$id" of type "String!" used in position expecting type "ID!"`. Currently your only options are to continue using the incorrect `String!` type forever (_eeew_), or make a new query with a new name, like `userByIdNew(id: ID!)` (_gross_)!
24
25
25
26
Wouldn't it be great if you could change the schema to use `ID!`, but just silently replace `String!` in old queries with `ID!` in your middleware so the old queries will continue to work just like they had been?
Now your schema is clean and up to date, and deprecated clients keep working! GraphQL Schema Rewriter can rewrite much more complex queries than just changing a single input type as well.
77
80
78
-
79
81
## Installation
80
82
81
83
Installation requires the base package `graphql-query-rewriter` and a middleware adapter for the web framework you use. Currently works with `express-graphql` and `apollo-server`.
Apollo server works with `express-graphql-query-rewriter` via [Apollo server middleware](https://www.apollographql.com/docs/apollo-server/migration-two-dot/#adding-additional-middleware-to-apollo-server-2).
93
+
Apollo server works with `express-graphql-query-rewriter` via [Apollo server middleware](https://www.apollographql.com/docs/apollo-server/migration-two-dot/#adding-additional-middleware-to-apollo-server-2).
@@ -154,10 +156,10 @@ const rewriter = new FieldArgTypeRewriter({
154
156
argName:'arg1',
155
157
oldType:'Int',
156
158
newType:'Int!'
157
-
})
159
+
});
158
160
```
159
161
160
-
Sometimes, you'll need to do some preprocessing on the variables submitted to the rewritten argument to make them into the type needed by the new schema. You can do this by passing in a `coerceVariable` function which returns a new value of the variable. For example, the following changes the value of `arg1` from `Int!` to `String!`, and also changes the value of `arg1` to a string as well:
162
+
Sometimes, you'll need to do some preprocessing on the variables submitted to the rewritten argument to make them into the type needed by the new schema. You can do this by passing in a `coerceVariable` function which returns a new value of the variable. For example, the following changes the value of `arg1` from `Int!` to `String!`, and also changes the value of `arg1` to a string as well:
@@ -184,13 +186,14 @@ const rewriter = new FieldArgNameRewriter({
184
186
fieldName:'createUser',
185
187
oldArgName:'userID',
186
188
newArgName:'userId'
187
-
})
189
+
});
188
190
```
189
191
190
192
### FieldArgsToInputTypeRewriter
191
193
192
194
`FieldArgsToInputTypeRewriter` can be used to move mutation parameters into a single input object, by default named `input`. It's a best-practice to use a single input type for mutations in GraphQL, and it's required by the [Relay GraphQL Spec](https://facebook.github.io/relay/docs/en/graphql-server-specification.html#mutations). For example, to migrate the mutation `createUser(username:String!, password:String!)` to a mutation with a proper input type like:
`ScalarFieldToObjectFieldRewriter` can be used to rewrite a scalar field into an object selecing a single scalar field. For example, imagine there's a `User` type with a `full_name` field that's of type `String!`. But to internationalize, that `full_name` field needs to support different names in different languges, something like `full_name: { default:'Jackie Chan', 'cn':'成龙', ... }`. We could use the `ScalarFieldToObjectFieldRewriter` to rewriter `full_name` to instead select the `default` name. Specifically, given we have the schema below:
238
241
239
-
```
242
+
```graphql
240
243
type User {
241
244
id:ID!
242
245
full_name:String!
@@ -246,7 +249,7 @@ type User {
246
249
247
250
and we want to change it to
248
251
249
-
```
252
+
```graphql
250
253
type User {
251
254
id:ID!
252
255
full_name: {
@@ -267,13 +270,13 @@ import { ScalarFieldToObjectFieldRewriter } from 'graphql-query-rewriter';
267
270
// add this to the rewriters array in graphqlRewriterMiddleware(...)
For example, This would rewrite the following query:
275
278
276
-
```
279
+
```graphql
277
280
query getUser(id:ID!) {
278
281
user {
279
282
id
@@ -284,7 +287,7 @@ query getUser(id: ID!) {
284
287
285
288
and turn it into:
286
289
287
-
```
290
+
```graphql
288
291
query getUser(id:ID!) {
289
292
user {
290
293
id
@@ -299,7 +302,7 @@ query getUser(id: ID!) {
299
302
300
303
`NestFieldOutputsRewriter` can be used to move mutation outputs into a nested payload object. It's a best-practice for each mutation in GraphQL to have its own output type, and it's required by the [Relay GraphQL Spec](https://facebook.github.io/relay/docs/en/graphql-server-specification.html#mutations). For example, to migrate the mutation `createUser(input: CreateUserInput!): User!` to a mutation with a proper output payload type like:
Sometimes you need more control over which fields get rewritten to avoid accidentally rewriting fields which happen to have the same name in an unrelated query. This can be accomplished by providing a list of `matchConditions` to the `RewriteHandler`. There are 3 built-in match condition helpers you can use to make this easier, specifically `fragmentMatchCondition`, `queryMatchCondition`, and `mutationMatchCondition`. If any of the conditions passed in to `matchConditions` match, then the rewriter will proceed as normal.
358
+
359
+
For example, to restrict matches to only to the `title` field of fragments named `thingFragment`, on type `Thing`, we could use the following `matchConditions`:
Then, this will rewrite the following query as follows:
377
+
378
+
```graphql
379
+
query {
380
+
articles {
381
+
title # <- This will not get rewritten, it doesn't match the matchConditions
382
+
things {
383
+
...thingFragment
384
+
}
385
+
}
386
+
}
387
+
388
+
fragment thingFragment on Thing {
389
+
id
390
+
title # <- This will be rewritten, because it matches the matchConditions
391
+
}
392
+
```
393
+
394
+
You can also pass a `pathRegexes` array of regexes to `fragmentMatchCondition` if you'd like to restrict the path to the object field within the fragment that you'd like to rewrite. For example:
395
+
396
+
```js
397
+
const rewriter = new ScalarFieldToObjectFieldRewriter({
398
+
fieldName: 'title',
399
+
objectFieldName: 'text',
400
+
matchConditions: [
401
+
fragmentMatchCondition({
402
+
// rewrite only at exatly path innerThing.title
403
+
pathRegexes: [/^innerThing.title$/]
404
+
})
405
+
]
406
+
});
407
+
```
408
+
409
+
Then, this will rewrite the query below as follows:
410
+
411
+
```graphql
412
+
query {
413
+
things {
414
+
...parentThingFragment
415
+
}
416
+
}
417
+
418
+
fragment parentThingFragment on Thing {
419
+
id
420
+
title # <- not rewritten, it's not at the correct path
421
+
innerThing {
422
+
title # <- This will be rewritten, it's at path innerThing.title
423
+
}
424
+
}
425
+
```
426
+
427
+
There are also `queryMatchCondition` and `mutationMatchCondition`. These work similarly to `fragmentMatchCondition`, except they match only fields directly inside of a query or a mutation, respectively.
428
+
All of these matches take `pathRegexes` to search for matching paths, but `queryMatchCondition` can also take `queryNames`, to match only named queries, and likewise `mutationMatchCondition` can take `mutationNames` to match named mutations.
429
+
430
+
If there are multiple `matchConditions` provided, then if any of the conditions match then the rewriter will continue as normal. For example:
431
+
432
+
```js
433
+
const rewriter = new ScalarFieldToObjectFieldRewriter({
434
+
fieldName: 'title',
435
+
objectFieldName: 'text',
436
+
matchConditions: [
437
+
fragmentMatchCondition({
438
+
fragmentNames: ['thingFragment']
439
+
}),
440
+
queryMatchCondition({
441
+
queryNames: ['getThing', 'getOtherThing']
442
+
})
443
+
]
444
+
});
445
+
```
446
+
447
+
The above rewriter will only match on fragments named `thingFragment`, or queries named `getThing` or `getOtherThing`.
448
+
352
449
## Current Limitations
353
450
354
451
Currently GraphQL Query Rewriter can only work with a single operation per query, and cannot properly handle aliased fields. These limitations should hopefully be fixed soon. Contributions are welcome!
0 commit comments