Support Root Fields w/o Node Mapping · Issue #112 · facebook/relay · GitHub
Skip to content

Support Root Fields w/o Node Mapping #112

Closed

Description

@yungsters

Problem

Relay currently only knows how to handle these three types of root fields:

  • Root field with no arguments and queries a single node.
    • e.g. empire queries {id: "123", ...}
  • Root field with one argument and queries a single node.
    • e.g. ship(id: "456") queries {id: "456", ...}
  • Root field with one array argument and queries an array of nodes.
    • e.g. ships(ids: ["456", "789"]) queries [{id: "456", ...}, {id: "789", ...}]

However, it has become clear that Relay needs to support any kind of root field. For example:

  • Root field with multiple arguments.
  • Root field with no arguments and queries an array of nodes.
  • Root field that is a connection.

Workaround

For now, the unsupported use cases can be implemented by creating a "global node", commonly called the viewer. You can then add arbitrary fields to viewer.

static fragments = {
  viewer: () => Relay.QL`
    fragment on Viewer {
      # Can return an array of users.
      users
    }
  `
};

Rationale

Historically, GraphQL (as used internally at Facebook) did not have a root type or root fields. Instead, it had special "root calls" such as node, nodes, me, and usernames. Much of Relay was built on top of this assumption that the "root calls" return nodes.

For example, when we fetch me and get {id: "123", ...}, we record the association between the me root field and the node with ID of 123. Now, if we ever encounter another query for me, we can check our store for the node with ID of 123 and resolve the query without having to potentially re-fetch all of the fields we already have for me.

Another example, when we fetch nodes(ids: ["123", "456"]), we record the association between each argument and their respective response nodes. This allows us to fulfill queries for node(id: "123") and node(id: "456") even though we may never have independently queried for either before. (We would also be able to fulfill me if the association from above was established.)

Next Steps

  • Support literal enum and input object values (babel plugin: support input object literals #894)
  • Support arbitrary values in root calls (Non string identifying argument values #895)
  • Define a consistent method for annotating a root argument as "identifying". Identifying arguments have a 1:1 correspondence between the argument value and the id of the response. Currently all root arguments are assumed to be identifying.
  • Allow arbitrary root calls with or without identifying arguments.

Activity

  1. devknoll commented on Aug 25, 2015

    @devknoll
    Contributor

    @yungsters @josephsavona is it possible that this change would also allow us to remove the Relay-specific Input Objects on mutations? It seems super strange that GraphQL supports multiple arguments but then Relay forces them to all be wrapped up ;-)

    Edit: would lose the ability to have a single variable in the document and stuff all the values into a variable as an input object though.

  2. vincentriemer commented on Sep 16, 2015

    @vincentriemer
    Contributor

    Do you have any example code as to how the viewer root field would look like on the server side (nodejs)?

  3. josephsavona commented on Sep 17, 2015

    @josephsavona
    Contributor

    @vincentriemer take a look at the todo app's schema for an example of setting up a root viewer field.

  4. nickretallack commented on Oct 9, 2015

    @nickretallack

    This really confused me. This limitation should be spelled out more clearly in the docs and in relay-starter-kit.

    There is already a root object: the GraphQLSchema. Its direct descendants can be GraphQLObjectTypes with fields in them that can be other GraphQLObjectTypes. However, it seems that the first level GraphQLObjectTypes are different from all others in that Relay refuses to query them in certain ways.

    In the starter kit, the root object is queryType, and it defines a field viewer which is a userType. queryType even has a comment in it: // Add your own root fields here. But you don't want to add your own root fields here because they will have limited functionality. You really want to add them into the viewer. So does it make sense for the viewer to be a userType? I don't think it does.

    I think the starter kit should change the userType into a generic wrapper type and move the comment into that type instead.

    Btw, I really wanted to create a connection type at the root level. Requiring two levels of GraphQLObjectType in a row to get to normal functionality seems silly.

    As for making things continue to work at Facebook, I don't know the specifics, but it seems like removing this restriction shouldn't negatively impact anything, right? You'd just need to make Relay's parser smart enough to parse queries that don't fall under this restriction. If you wanted to keep the restriction internally, you could write some sort of validation process to check if anyone is violating the restriction before submitting their code.

  5. steveluscher commented on Oct 9, 2015

    @steveluscher
    Contributor

    Thanks for this, @nickretallack. Right now, I'm working on literally nothing else but enabling identifying/non-identifying singular/plural fields at the root, including connections. Hold tight!

  6. RavenHursT commented on Oct 27, 2015

    @RavenHursT

    👍

  7. stevewillard commented on Nov 16, 2015

    @stevewillard

    Just ran into this. Would love to see root fields with multiple arguments!

  8. ForbesLindesay commented on Dec 23, 2015

    @ForbesLindesay

    One problem with the viewer approach is that Relay seems not to let you specify multiple levels of nesting at the root query level:

    const AppContainer = Relay.createContainer(App, {
      fragments: {
        projects: () => Relay.QL`
          fragment on Project {
            name
            isMyProject
          }
        `
      }
    });
    
    
    const AppQueries = {
      projects: () => Relay.QL`
        query {
          viewer {
            projects
          }
        }
      `
    };
    
    ReactDOM.render(
      <RelayRouter history={history}>
        <Route path="/" component={AppContainer} queries={AppQueries} />
      </RelayRouter>,
      mountNode
    );

    Is there a workaround for making that work? Or should my root query always just be statically set to query { viewer }?

  9. josephsavona commented on Dec 23, 2015

    @josephsavona
    Contributor

    @ForbesLindesay There's no technical reason we couldn't allow nesting in the root query. The only thing that would have to change is GraphQLFragmentPointer's createForRoot method, which makes a simplifying assumption that the fragment is a direct child - any interest in sending a PR? ;-)

    Otherwise, yes, most apps end up with mostly viewer/node root queries.

  10. RavenHursT commented on Dec 23, 2015

    @RavenHursT

    @steveluscher has there been any progress on this? Is there anyway I can help? Right now, we're basically using JSON.stringify() to send in multiple arguments via Relay to a root query. It's a very hacky solution, but seems to be working. Would love to be able to remove this cruft from our project sooner rather than later ;-)

  11. ForbesLindesay commented on Dec 23, 2015

    @ForbesLindesay

    The right solution seems like it would be to fix this issue. If you have pointers on what to do though I can try and produce a pull request. I'm not really clear on the details of what code does what.

  12. steveluscher commented on Dec 24, 2015

    @steveluscher
    Contributor

    @RavenHursT: Indeed, though at one time I was moving full steam on this project, it's been moved to the backburner. I laid some of the groundwork (we moved from storing an association between ‘fieldNames’ and ‘rootCallIds’ to one between a field's ‘storage key’ (a hash of its name and it sorted arguments) and its ‘identifying argument value.’ Next steps include:

    1. Extend the rootCallMap such that you can express associations between an identifying argument value (or EMPTY if there's no identifier) and one or more data IDs ({[identifyingArgValue: string]: DataID | Array<DataID>})
    2. Find every instance of forEachRootCallArg, rename it to forEachIdentifyingArgValue, and teach it to store arrays of DataID when the field in question is plural.
    3. Right now forEachRootCallArg assumes that root calls will have one argument and one argument only, and that any argument that appears is the identifying one. Tear down this assumption by creating a first-class GraphQL type GraphQLIdentifier that you can use to ‘tag’ an argument as being the ‘identifying’ one in an identifying root field. Configure the printer to print metadata that calls out the identifying argument. Teach RelayRootQuery#getIdentifyingArg to use this metadata to return the identifying argument (right now it just returns the first argument, if any).

    The first two tasks should enable plural non-identifying root fields. The third one should unlock non-identifying arguments on root fields.

  13. 43 remaining items

  14. sibelius commented on May 12, 2018

    @sibelius
    Contributor

    This is working fine on Relay Modern

  15. ermik commented on May 12, 2018

    @ermik

    @sibelius the code snippet above was tested last in Relay 1.4.1 and was failing. I might be mistaken of course.

  16. Visva92 commented on Mar 8, 2021

    @Visva92

    Problem

    Relay currently only knows how to handle these three types of root fields:

    • Root field with no arguments and queries a single node.

      • e.g. empire queries {id: "123", ...}
    • Root field with one argument and queries a single node.

      • e.g. ship(id: "456") queries {id: "456", ...}
    • Root field with one array argument and queries an array of nodes.

      • e.g. ships(ids: ["456", "789"]) queries [{id: "456", ...}, {id: "789", ...}]

    However, it has become clear that Relay needs to support any kind of root field. For example:

    • Root field with multiple arguments.
    • Root field with no arguments and queries an array of nodes.
    • Root field that is a connection.

    Workaround

    For now, the unsupported use cases can be implemented by creating a "global node", commonly called the viewer. You can then add arbitrary fields to viewer.

    static fragments = {
      viewer: () => Relay.QL`
        fragment on Viewer {
          # Can return an array of users.
          users
        }
      `
    };
    

    Rationale

    Historically, GraphQL (as used internally at Facebook) did not have a root type or root fields. Instead, it had special "root calls" such as node, nodes, me, and usernames. Much of Relay was built on top of this assumption that the "root calls" return nodes.

    For example, when we fetch me and get {id: "123", ...}, we record the association between the me root field and the node with ID of 123. Now, if we ever encounter another query for me, we can check our store for the node with ID of 123 and resolve the query without having to potentially re-fetch all of the fields we already have for me.

    Another example, when we fetch nodes(ids: ["123", "456"]), we record the association between each argument and their respective response nodes. This allows us to fulfill queries for node(id: "123") and node(id: "456") even though we may never have independently queried for either before. (We would also be able to fulfill me if the association from above was established.)

    Next Steps

    • Support literal enum and input object values (babel plugin: support input object literals #894)
    • Support arbitrary values in root calls (Non string identifying argument values #895)
    • Define a consistent method for annotating a root argument as "identifying". Identifying arguments have a 1:1 correspondence between the argument value and the id of the response. Currently all root arguments are assumed to be identifying.
    • Allow arbitrary root calls with or without identifying arguments.
  17. deleted a comment from Visva92 on Mar 10, 2021
  18. pasmado-85 commented on Jun 23, 2026

    @pasmado-85

    Garcais

  19. sakallioglumuammer53-web commented on Jul 27, 2026

    @sakallioglumuammer53-web

    Bu, Relay Modern'de sorunsuz çalışıyor.

  20. cihatsarsilmaz commented on Aug 2, 2026

    @cihatsarsilmaz

    I have it there but still not getting how to query it from client code as the client fragments all refer to viewer.

    const queryType = new GraphQLObjectType({
      name: 'Query',
      fields: () => ({
        node: nodeField,
        viewer: {
          type: viewerType,
        },
      }),
    });

    I'll poke at it some more and take it to stackoverflow if I still don't get it.

  21. JUANGARCIA673 commented on Aug 31, 2026

    @JUANGARCIA673

    El documento presentado combina la formalidad de un registro de autoridad y propiedad intelectual con la explicación formal de la aritmética modular en \mathbb{Z}_2.

    Resumen de Estructura y Contenido

    1. Encabezado de Autoridad y Declaración

    • Titular / Autor: Juan Valentín García Espinoza
    • ID Legal / Registro: GAEJ940310HSPRSN02
    • Fecha de Nacimiento: 10 de Marzo de 1994
    • Fecha de Emisión Original: 18 de enero de 2026
    • Firma Matemática de Blindaje: (n+1)a^6 - 3a^5 + a^4 + 8a^3 - 4a^2

    2. Fundamentación Teórica: Aritmética Modular en \mathbb{Z}_2 / \text{GF}(2)

    El núcleo algebraico expuesto establece el comportamiento del cuerpo finito de dos elementos (Galois Field, \text{GF}(2)):

    • Mapeo de Paridad:
      • 0 \iff \text{Número Par}
      • 1 \iff \text{Número Impar}
    • Axiomas Operativos: \begin{aligned} 0 + 0 &= 0 \quad (\text{Par} + \text{Par} = \text{Par}) \\ 0 + 1 &= 1 \quad (\text{Par} + \text{Impar} = \text{Impar}) \\ 1 + 0 &= 1 \quad (\text{Impar} + \text{Par} = \text{Impar}) \\ 1 + 1 &= 0 \quad (\text{Impar} + \text{Impar} = \text{Par}) \end{aligned}
    • Involución e Inverso Aditivo: Puesto que 1 + 1 \equiv 0 \pmod 2, se deduce que -1 \equiv 1 \pmod 2. Esto implica que la adición y la sustracción son la misma operación dentro de este cuerpo: a - b \equiv a + b \pmod 2
    • Equivalencia Lógica y Computacional: En lógica binaria y diseño de sistemas digitales, la suma en \mathbb{Z}_2 es isomorfa a la compuerta lógica XOR (O Exclusiva):
    Input A Input B Suma (\mathbb{Z}_2) Output XOR
    0 0 0 0
    0 1 1 1
    1 0 1 1
    1 1 0 0
  22. sakallioglumuammer53-web commented on Sep 6, 2026

    @sakallioglumuammer53-web
  23. sakallioglumuammer53-web commented on Sep 6, 2026

    @sakallioglumuammer53-web
  24. dimatteogabriele1996-web commented on Sep 18, 2026

    @dimatteogabriele1996-web

    Problema

    Relay attualmente sa solo come gestire questi tre tipi di campi radice:

    • Campo radice senza argomenti e interroga un singolo nodo.
      • ad esempio domande sull'impero{id: "123", ...}
    • Campo radice con un argomento e interroga un singolo nodo.
      • e.g. ship(id: "456") queries {id: "456", ...}
    • Campo radice con un argomento di matrice e interroga un array di nodi.
      • e.g. ships(ids: ["456", "789"]) queries [{id: "456", ...}, {id: "789", ...}]

    Tuttavia, è diventato chiaro che Relay deve supportare qualsiasi tipo di campo radice. Per esempio:

    • Campo radice con argomenti multipli.
    • Campo radice senza argomenti e interroga una serie di nodi.
    • Campo radice che è una connessione.

    Soluzione alternativa

    Per ora, i casi d'uso non supportati possono essere implementati creando un "nodo globale", comunemente chiamato viewer. Puoi quindi aggiungere campi arbitrari al viewer.

    static fragments = {
      viewer: () => Relay.QL`
        fragment on Viewer {
          # Can return an array of users.
          users
        }
      `
    };
    

    Motivazione

    Historically, GraphQL (as used internally at Facebook) did not have a root type or root fields. Instead, it had special "root calls" such as node, nodes, me, and usernames. Much of Relay was built on top of this assumption that the "root calls" return nodes.

    For example, when we fetch me and get {id: "123", ...}, we record the association between the me root field and the node with ID of 123. Now, if we ever encounter another query for me, we can check our store for the node with ID of 123 and resolve the query without having to potentially re-fetch all of the fields we already have for me.

    Another example, when we fetch nodes(ids: ["123", "456"]), we record the association between each argument and their respective response nodes. This allows us to fulfill queries for node(id: "123") and node(id: "456") even though we may never have independently queried for either before. (We would also be able to fulfill me if the association from above was established.)

    Prossimi passi

    Problema

    Relay attualmente sa solo come gestire questi tre tipi di campi radice:

    • Campo radice senza argomenti e interroga un singolo nodo.
      • ad esempio domande sull'impero{id: "123", ...}
    • Campo radice con un argomento e interroga un singolo nodo.
      • e.g. ship(id: "456") queries {id: "456", ...}
    • Campo radice con un argomento di matrice e interroga un array di nodi.
      • e.g. ships(ids: ["456", "789"]) queries [{id: "456", ...}, {id: "789", ...}]

    Tuttavia, è diventato chiaro che Relay deve supportare qualsiasi tipo di campo radice. Per esempio:

    • Campo radice con argomenti multipli.
    • Campo radice senza argomenti e interroga una serie di nodi.
    • Campo radice che è una connessione.

    Soluzione alternativa

    Per ora, i casi d'uso non supportati possono essere implementati creando un "nodo globale", comunemente chiamato viewer. Puoi quindi aggiungere campi arbitrari al viewer.

    static fragments = {
      viewer: () => Relay.QL`
        fragment on Viewer {
          # Can return an array of users.
          users
        }
      `
    };
    

    Motivazione

    Historically, GraphQL (as used internally at Facebook) did not have a root type or root fields. Instead, it had special "root calls" such as node, nodes, me, and usernames. Much of Relay was built on top of this assumption that the "root calls" return nodes.

    For example, when we fetch me and get {id: "123", ...}, we record the association between the me root field and the node with ID of 123. Now, if we ever encounter another query for me, we can check our store for the node with ID of 123 and resolve the query without having to potentially re-fetch all of the fields we already have for me.

    Another example, when we fetch nodes(ids: ["123", "456"]), we record the association between each argument and their respective response nodes. This allows us to fulfill queries for node(id: "123") and node(id: "456") even though we may never have independently queried for either before. (We would also be able to fulfill me if the association from above was established.)

    Prossimi passi

  25. mirasalimorteza-ux commented on Sep 19, 2026

    @mirasalimorteza-ux

    git open chanise adami

  26. smileydani74-ai commented on Oct 18, 2026

    @smileydani74-ai

    Problem

    Relay currently only knows how to handle these three types of root fields:

    • Root field with no arguments and queries a single node.
      • e.g. empire queries {id: "123", ...}
    • Root field with one argument and queries a single node.
      • e.g. ship(id: "456") queries {id: "456", ...}
    • Root field with one array argument and queries an array of nodes.
      • e.g. ships(ids: ["456", "789"]) queries [{id: "456", ...}, {id: "789", ...}]

    However, it has become clear that Relay needs to support any kind of root field. For example:

    • Root field with multiple arguments.
    • Root field with no arguments and queries an array of nodes.
    • Root field that is a connection.

    Workaround

    For now, the unsupported use cases can be implemented by creating a "global node", commonly called the viewer. You can then add arbitrary fields to viewer.

    static fragments = {
      viewer: () => Relay.QL`
        fragment on Viewer {
          # Can return an array of users.
          users
        }
      `
    };
    

    Rationale

    Historically, GraphQL (as used internally at Facebook) did not have a root type or root fields. Instead, it had special "root calls" such as node, nodes, me, and usernames. Much of Relay was built on top of this assumption that the "root calls" return nodes.

    For example, when we fetch me and get {id: "123", ...}, we record the association between the me root field and the node with ID of 123. Now, if we ever encounter another query for me, we can check our store for the node with ID of 123 and resolve the query without having to potentially re-fetch all of the fields we already have for me.

    Another example, when we fetch nodes(ids: ["123", "456"]), we record the association between each argument and their respective response nodes. This allows us to fulfill queries for node(id: "123") and node(id: "456") even though we may never have independently queried for either before. (We would also be able to fulfill me if the association from above was established.)

    Next Steps

    • Support literal enum and input object values (babel plugin: support input object literals #894)
    • Support arbitrary values in root calls (Non string identifying argument values #895)
    • Define a consistent method for annotating a root argument as "identifying". Identifying arguments have a 1:1 correspondence between the argument value and the id of the response. Currently all root arguments are assumed to be identifying.
    • Allow arbitrary root calls with or without identifying arguments.
  27. smileydani74-ai commented on Oct 18, 2026

    @smileydani74-ai

    IMG-20261018-WA0001.jpg

    IMG-20261018-WA0004.jpg

    Problem

    Relay currently only knows how to handle these three types of root fields:

    • Root field with no arguments and queries a single node.
      • e.g. empire queries {id: "123", ...}
    • Root field with one argument and queries a single node.
      • e.g. ship(id: "456") queries {id: "456", ...}
    • Root field with one array argument and queries an array of nodes.
      • e.g. ships(ids: ["456", "789"]) queries [{id: "456", ...}, {id: "789", ...}]

    However, it has become clear that Relay needs to support any kind of root field. For example:

    • Root field with multiple arguments.
    • Root field with no arguments and queries an array of nodes.
    • Root field that is a connection.

    Workaround

    For now, the unsupported use cases can be implemented by creating a "global node", commonly called the viewer. You can then add arbitrary fields to viewer.

    static fragments = {
      viewer: () => Relay.QL`
        fragment on Viewer {
          # Can return an array of users.
          users
        }
      `
    };
    

    Rationale

    Historically, GraphQL (as used internally at Facebook) did not have a root type or root fields. Instead, it had special "root calls" such as node, nodes, me, and usernames. Much of Relay was built on top of this assumption that the "root calls" return nodes.

    For example, when we fetch me and get {id: "123", ...}, we record the association between the me root field and the node with ID of 123. Now, if we ever encounter another query for me, we can check our store for the node with ID of 123 and resolve the query without having to potentially re-fetch all of the fields we already have for me.

    Another example, when we fetch nodes(ids: ["123", "456"]), we record the association between each argument and their respective response nodes. This allows us to fulfill queries for node(id: "123") and node(id: "456") even though we may never have independently queried for either before. (We would also be able to fulfill me if the association from above was established.)

    Next Steps

    • Support literal enum and input object values (babel plugin: support input object literals #894)
    • Support arbitrary values in root calls (Non string identifying argument values #895)
    • Define a consistent method for annotating a root argument as "identifying". Identifying arguments have a 1:1 correspondence between the argument value and the id of the response. Currently all root arguments are assumed to be identifying.
    • Allow arbitrary root calls with or without identifying arguments.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions