Nodes And Relay IDs

Use a normal Chemist type plus an explicit sc.Node base when the GraphQL type participates in relay/node resolution.

Basic node

@sc.type(model=BookModel)
class Book(sc.Node):
    title: str

By default, Chemist infers the identifier columns from the SQLAlchemy mapper primary key.

Custom identifiers

@sc.type(model=BookmarkModel)
class Bookmark(sc.Node):
    id = sc.node_id(ids=("user_id", "book_id"))
    created_at: datetime

Composite IDs are supported.

Root node lookup

@strawberry.type
class Query:
    node = sc.node_field()

sc.configure(
    default_pagination=sc.CursorPagination(default_limit=10, max_limit=20),
    default_relay_id_codec=sc.relay.IntRegistryCodec(registry={BookModel: 1}),
)

schema = strawberry.Schema(
    query=Query,
    types=(Book, Bookmark),
    extensions=sc.extensions(),
)

You can also narrow the field to specific node types with sc.node_field(allowed_types=(Book,)).

When an unrestricted sc.node_field() is present, the concrete node types must already be visible to the schema at build time, either through normal field reachability or through types=(...).

Call sc.configure(...) before strawberry.Schema(...) when an application wants package-level defaults such as relay codecs or connection pagination.

Node ID codecs

The default IDs are readable, for example Book_1.

If an application needs a different token format, configure it on the node ID field:

@sc.type(model=LegacyBookmarkModel)
class LegacyBookmark(sc.Node):
    id = sc.node_id(
        codec=sc.relay.IntRegistryCodec(registry={LegacyBookmarkModel: 7}),
    )

Or set a package-level default before building the schema:

sc.configure(
    default_relay_id_codec=sc.relay.IntRegistryCodec(
        registry={BookModel: 1, ShelfModel: 2},
    )
)

For application code and tests, use the schema-bound helpers instead of hardcoding token strings:

book_id = sc.relay.encode_node_id(schema, Book, values=(1,))
decoded = sc.relay.decode_node_id(schema, book_id)
assert decoded.node_type is Book
assert decoded.values == ("1",)

Primary example:

Resolver node injection

Use sc.node_lookup(...) when a query or mutation should accept a node ID argument but operate on the loaded ORM object.

@sc.node_lookup(model=PostModel, id_name="post_id", node_param_name="post")
async def rename_post(self, info, post, title: str) -> Post:
    ...

Primary example: