0
# Relay Integration
1
2
Graphene provides complete GraphQL Relay specification support, enabling standardized patterns for global object identification, cursor-based pagination, and mutations with client IDs. This includes the Node interface for globally unique objects, connection-based pagination for efficient data fetching, and mutation patterns that support optimistic updates.
3
4
## Capabilities
5
6
### Node Interface and Global IDs
7
8
Implements the Relay Node interface for global object identification across your GraphQL schema.
9
10
```python { .api }
11
class Node(Interface):
12
"""
13
Relay Node interface implementation with global ID support.
14
15
Features:
16
- Automatic ID field injection
17
- Global ID encoding/decoding
18
- Type validation for node objects
19
- Integration with Relay client libraries
20
21
Class Methods:
22
Field(*args, **kwargs): Creates NodeField for single node queries
23
get_node_from_global_id(info, global_id, only_type=None): Node retrieval
24
to_global_id(type_, id): Global ID creation
25
resolve_global_id(info, global_id): Global ID parsing
26
27
Usage:
28
class User(graphene.ObjectType):
29
class Meta:
30
interfaces = (Node,)
31
32
name = graphene.String()
33
email = graphene.String()
34
35
@classmethod
36
def get_node(cls, info, id):
37
return get_user_by_id(id)
38
"""
39
40
class GlobalID(Field):
41
"""
42
Automatic global ID field generation for Node types.
43
44
Parameters:
45
node: Node type this ID represents
46
parent_type: Parent type for ID resolution
47
required: Whether field is NonNull
48
global_id_type: Custom GlobalIDType implementation
49
50
Features:
51
- Automatic ID resolution and encoding
52
- Configurable ID type implementations
53
- Integration with Relay pagination
54
55
Usage:
56
class User(graphene.ObjectType):
57
class Meta:
58
interfaces = (Node,)
59
60
id = GlobalID()
61
name = graphene.String()
62
"""
63
64
def is_node(objecttype):
65
"""
66
Check if ObjectType implements Node interface.
67
68
Args:
69
objecttype: GraphQL ObjectType to check
70
71
Returns:
72
bool: True if type implements Node interface
73
74
Usage:
75
if graphene.is_node(User):
76
print("User implements Node interface")
77
"""
78
```
79
80
### Global ID Type System
81
82
Flexible global ID implementations supporting different encoding strategies.
83
84
```python { .api }
85
class BaseGlobalIDType:
86
"""
87
Base class for global ID type implementations.
88
89
Attributes:
90
graphene_type: GraphQL scalar type used (default: ID)
91
92
Abstract Methods:
93
resolve_global_id(global_id): Parse global ID to (type, id) tuple
94
to_global_id(type_, id): Encode type and ID to global ID string
95
96
Usage:
97
class CustomGlobalIDType(BaseGlobalIDType):
98
def resolve_global_id(self, global_id):
99
parts = global_id.split('.')
100
return (parts[0], parts[1])
101
102
def to_global_id(self, type_, id):
103
return f"{type_}.{id}"
104
"""
105
106
class DefaultGlobalIDType(BaseGlobalIDType):
107
"""
108
Base64-encoded "TypeName:id" format (Relay standard).
109
110
Features:
111
- Standard Relay specification compliance
112
- Base64 encoding for opacity
113
- Type safety with parsing validation
114
- Compatible with Relay client libraries
115
116
Methods:
117
Uses graphql-relay's to_global_id/from_global_id functions
118
119
Format: base64("TypeName:id")
120
Example: "VXNlcjox" -> "User:1"
121
"""
122
123
class SimpleGlobalIDType(BaseGlobalIDType):
124
"""
125
Pass-through ID type that uses raw ID values.
126
127
Warning: User responsible for ensuring global uniqueness
128
Features:
129
- No encoding/decoding overhead
130
- Direct ID passthrough
131
- Requires globally unique IDs
132
133
Use Case: When IDs are already globally unique (UUIDs, etc.)
134
"""
135
136
class UUIDGlobalIDType(BaseGlobalIDType):
137
"""
138
UUID-based global IDs with native UUID support.
139
140
Features:
141
- Uses UUID GraphQL type instead of ID
142
- Inherently global uniqueness
143
- No type prefix needed
144
- Automatic UUID validation
145
146
Attributes:
147
graphene_type: UUID (instead of ID)
148
"""
149
```
150
151
### Connection System
152
153
Cursor-based pagination following the Relay Connection specification.
154
155
```python { .api }
156
class Connection(ObjectType):
157
"""
158
Relay cursor-based pagination container.
159
160
Features:
161
- Automatic Edge class generation
162
- PageInfo integration for pagination metadata
163
- Configurable strict typing
164
- Cursor-based navigation
165
166
Meta Options:
167
node: Required ObjectType that this connection contains
168
name: Custom connection name (defaults to {Node}Connection)
169
strict_types: Enforce strict edge/connection typing
170
171
Generated Fields:
172
edges: List of Edge objects containing nodes and cursors
173
page_info: PageInfo object with pagination metadata
174
175
Usage:
176
class UserConnection(Connection):
177
class Meta:
178
node = User
179
180
# Automatically generates:
181
# - UserEdge with node and cursor fields
182
# - edges field returning [UserEdge]
183
# - page_info field returning PageInfo
184
"""
185
186
class ConnectionField(Field):
187
"""
188
Field that automatically handles Relay-style pagination.
189
190
Features:
191
- Automatic Relay pagination arguments (before, after, first, last)
192
- Iterable to connection conversion
193
- Resolver wrapping for pagination
194
- Integration with Connection types
195
196
Auto Arguments:
197
before: Cursor for backward pagination
198
after: Cursor for forward pagination
199
first: Number of items from start
200
last: Number of items from end
201
202
Methods:
203
resolve_connection(): Converts iterables to connections
204
connection_resolver(): Wraps resolvers for pagination
205
206
Usage:
207
class Query(graphene.ObjectType):
208
users = ConnectionField(UserConnection)
209
210
def resolve_users(self, info, **args):
211
# Return iterable - automatically converted to connection
212
return User.objects.all()
213
"""
214
215
class PageInfo(ObjectType):
216
"""
217
Relay pagination metadata following the specification.
218
219
Fields:
220
has_next_page: Boolean indicating if more items exist forward
221
has_previous_page: Boolean indicating if more items exist backward
222
start_cursor: Cursor of first item in current page
223
end_cursor: Cursor of last item in current page
224
225
Features:
226
- Relay specification compliant
227
- Automatic cursor generation
228
- Pagination state management
229
- Client navigation support
230
231
Usage:
232
# Automatically included in Connection types
233
# Used by Relay clients for pagination UI
234
query {
235
users(first: 10) {
236
edges {
237
node {
238
name
239
}
240
cursor
241
}
242
pageInfo {
243
hasNextPage
244
hasPreviousPage
245
startCursor
246
endCursor
247
}
248
}
249
}
250
"""
251
```
252
253
### Relay Mutations
254
255
Standardized mutation patterns with client IDs for optimistic updates.
256
257
```python { .api }
258
class ClientIDMutation(Mutation):
259
"""
260
Relay-style mutations with client mutation ID support.
261
262
Features:
263
- Automatic Input class generation
264
- client_mutation_id field injection
265
- Payload naming convention ({MutationName}Payload)
266
- Support for optimistic updates
267
268
Required Method:
269
mutate_and_get_payload(): Implementation method with client ID
270
271
Generated Features:
272
- Input class with client_mutation_id field
273
- Payload class with client_mutation_id field
274
- Automatic argument extraction
275
276
Usage:
277
class CreateUser(ClientIDMutation):
278
class Input:
279
name = graphene.String(required=True)
280
email = graphene.String()
281
282
user = graphene.Field(User)
283
284
@classmethod
285
def mutate_and_get_payload(cls, root, info, **input):
286
# client_mutation_id automatically handled
287
user = create_user(
288
name=input['name'],
289
email=input.get('email')
290
)
291
return CreateUser(user=user)
292
293
# Generates mutation field that accepts:
294
# input {
295
# name: String!
296
# email: String
297
# clientMutationId: String
298
# }
299
#
300
# Returns payload:
301
# {
302
# user: User
303
# clientMutationId: String
304
# }
305
"""
306
```
307
308
## Usage Examples
309
310
### Implementing Node Interface
311
312
```python
313
import graphene
314
from graphene import relay
315
316
class User(graphene.ObjectType):
317
class Meta:
318
interfaces = (relay.Node,)
319
320
name = graphene.String()
321
email = graphene.String()
322
posts = relay.ConnectionField('PostConnection')
323
324
@classmethod
325
def get_node(cls, info, id):
326
"""Required method for Node interface."""
327
return get_user_by_id(id)
328
329
def resolve_posts(self, info, **args):
330
return get_posts_for_user(self.id)
331
332
class Post(graphene.ObjectType):
333
class Meta:
334
interfaces = (relay.Node,)
335
336
title = graphene.String()
337
content = graphene.String()
338
author = graphene.Field(User)
339
340
@classmethod
341
def get_node(cls, info, id):
342
return get_post_by_id(id)
343
344
# Connection classes
345
class PostConnection(relay.Connection):
346
class Meta:
347
node = Post
348
349
class UserConnection(relay.Connection):
350
class Meta:
351
node = User
352
353
# Query with Node field
354
class Query(graphene.ObjectType):
355
node = relay.Node.Field()
356
users = relay.ConnectionField(UserConnection)
357
posts = relay.ConnectionField(PostConnection)
358
359
def resolve_users(self, info, **args):
360
return User.objects.all()
361
362
def resolve_posts(self, info, **args):
363
return Post.objects.all()
364
```
365
366
### Custom Global ID Types
367
368
```python
369
import graphene
370
from graphene import relay
371
import uuid
372
373
class UUIDNode(relay.Node):
374
"""Node interface using UUID global IDs."""
375
376
class Meta:
377
# Use UUID-based global ID type
378
global_id_type = relay.UUIDGlobalIDType()
379
380
class User(graphene.ObjectType):
381
class Meta:
382
interfaces = (UUIDNode,)
383
384
# ID field will use UUID type instead of ID
385
name = graphene.String()
386
387
@classmethod
388
def get_node(cls, info, id):
389
# id is already a UUID object
390
return get_user_by_uuid(id)
391
392
# Custom global ID type
393
class PrefixedGlobalIDType(relay.BaseGlobalIDType):
394
"""Custom global ID with prefix."""
395
396
def to_global_id(self, type_, id):
397
return f"app_{type_}_{id}"
398
399
def resolve_global_id(self, global_id):
400
parts = global_id.split('_')
401
if len(parts) >= 3 and parts[0] == 'app':
402
return (parts[1], '_'.join(parts[2:]))
403
raise ValueError(f"Invalid global ID format: {global_id}")
404
405
class CustomNode(relay.Node):
406
class Meta:
407
global_id_type = PrefixedGlobalIDType()
408
```
409
410
### Relay Mutations with Input Validation
411
412
```python
413
import graphene
414
from graphene import relay
415
416
class CreateUserInput(graphene.InputObjectType):
417
name = graphene.String(required=True)
418
email = graphene.String(required=True)
419
age = graphene.Int()
420
421
class CreateUser(relay.ClientIDMutation):
422
class Input:
423
data = graphene.Field(CreateUserInput, required=True)
424
425
user = graphene.Field(User)
426
success = graphene.Boolean()
427
errors = graphene.List(graphene.String)
428
429
@classmethod
430
def mutate_and_get_payload(cls, root, info, data, client_mutation_id=None):
431
errors = []
432
433
# Validation
434
if len(data.name) < 2:
435
errors.append("Name must be at least 2 characters")
436
437
if '@' not in data.email:
438
errors.append("Invalid email format")
439
440
if errors:
441
return CreateUser(success=False, errors=errors)
442
443
# Create user
444
user = create_user(
445
name=data.name,
446
email=data.email,
447
age=data.age
448
)
449
450
return CreateUser(
451
user=user,
452
success=True,
453
errors=[]
454
)
455
456
class UpdateUser(relay.ClientIDMutation):
457
class Input:
458
id = graphene.ID(required=True)
459
name = graphene.String()
460
email = graphene.String()
461
462
user = graphene.Field(User)
463
464
@classmethod
465
def mutate_and_get_payload(cls, root, info, id, **input):
466
# Resolve node from global ID
467
node_type, node_id = relay.Node.resolve_global_id(info, id)
468
469
if node_type != 'User':
470
raise Exception('Invalid user ID')
471
472
user = get_user_by_id(node_id)
473
if not user:
474
raise Exception('User not found')
475
476
# Update fields
477
for field, value in input.items():
478
if value is not None:
479
setattr(user, field, value)
480
481
save_user(user)
482
return UpdateUser(user=user)
483
484
class Mutation(graphene.ObjectType):
485
create_user = CreateUser.Field()
486
update_user = UpdateUser.Field()
487
```
488
489
### Advanced Connection Filtering
490
491
```python
492
import graphene
493
from graphene import relay
494
495
class UserConnection(relay.Connection):
496
class Meta:
497
node = User
498
499
total_count = graphene.Int()
500
501
def resolve_total_count(self, info):
502
return len(self.iterable)
503
504
class Query(graphene.ObjectType):
505
users = relay.ConnectionField(
506
UserConnection,
507
# Custom arguments for filtering
508
name_contains=graphene.String(),
509
active_only=graphene.Boolean(),
510
sort_by=graphene.String()
511
)
512
513
def resolve_users(self, info, name_contains=None, active_only=None, sort_by=None, **args):
514
queryset = User.objects.all()
515
516
# Apply filters
517
if name_contains:
518
queryset = queryset.filter(name__icontains=name_contains)
519
520
if active_only:
521
queryset = queryset.filter(is_active=True)
522
523
# Apply sorting
524
if sort_by == 'name':
525
queryset = queryset.order_by('name')
526
elif sort_by == 'created':
527
queryset = queryset.order_by('-created_at')
528
529
return queryset
530
531
# Usage in GraphQL query:
532
# query {
533
# users(first: 10, nameContains: "john", activeOnly: true, sortBy: "name") {
534
# edges {
535
# node {
536
# name
537
538
# }
539
# cursor
540
# }
541
# pageInfo {
542
# hasNextPage
543
# endCursor
544
# }
545
# totalCount
546
# }
547
# }
548
```