Reputation: 3955
Assume you have a GraphQL type and it includes many fields. How to query all the fields without writing down a long query that includes the names of all the fields?
For example, If I have these fields :
public function fields()
{
return [
'id' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The id of the user'
],
'username' => [
'type' => Type::string(),
'description' => 'The email of user'
],
'count' => [
'type' => Type::int(),
'description' => 'login count for the user'
]
];
}
To query all the fields usually the query is something like this:
FetchUsers{users(id:"2"){id,username,count}}
But I want a way to have the same results without writing all the fields, something like this:
FetchUsers{users(id:"2"){*}}
//or
FetchUsers{users(id:"2")}
Is there a way to do this in GraphQL ??
I'm using Folkloreatelier/laravel-graphql library.
Upvotes: 385
Views: 378955
Reputation: 75
{
__schema {
types {
name
fields {
name
args {
name
description
type {
name
kind
ofType {
name
kind
}
}
}
}
}
}
}
Upvotes: 0
Reputation: 11028
Less than ideal, by a long shot, but you can make a tool for handling it. Mine looks something like this:
const ExampleUser = {
id: "u_01",
firstName: "Seph",
}
const ExamplePermission = {
id: "p_01",
userId: "u_01",
type: "admin"
}
type User = typeof ExampleUser;
type Perm = typeof ExamplePermission;
const UserFields = Array.from(Object.keys(ExampleUser))
const PermFields = Array.from(Object.keys(ExamplePermission))
// an overview of all gql possibilities, could be generated
const gqlMapOverview = {
Users: {
fields: UserFields,
Perms: {
fields: PermFields,
}
},
Perms: {
fields: PermFields,
}
}
function createGqlString(map, queryObj){ /*real work goes here*/ }
const queryString = createGqlString(gqlMapOverview, {
Users: {
_filter: { where: { firstName: "Seph" }, limit: 2 },
Perms: {}
}
})
queryString == `Users(firstName: "Seph", _limit: 2) {
id,
firstname,
Perms { id, userId, type }
}`
If you get very crafty with your Type Definitions, you can even make it so the return from the gql query is correctly typed.
It's a bummer that GQL doesn't respect the standard use case of querying an item from a server and wanting all fields, but I honestly think abstracting away from it like this sets one up to easier replace it in the future.
Upvotes: 0
Reputation: 23582
Yes, you can do this using introspection. Make a GraphQL query like (for type UserType)
{
__type(name:"UserType") {
fields {
name
description
}
}
}
and you'll get a response like (actual field names will depend on your actual schema/type definition)
{
"data": {
"__type": {
"fields": [
{
"name": "id",
"description": ""
},
{
"name": "username",
"description": "Required. 150 characters or fewer. Letters, digits, and @/./+/-/_ only."
},
{
"name": "firstName",
"description": ""
},
{
"name": "lastName",
"description": ""
},
{
"name": "email",
"description": ""
},
( etc. etc. ...)
]
}
}
}
You can then read this list of fields in your client and dynamically build a second GraphQL query to get the values of these fields.
This relies on you knowing the name of the type that you want to get the fields for -- if you don't know the type, you could get all the types and fields together using introspection like
{
__schema {
types {
name
fields {
name
description
}
}
}
}
NOTE: This is the over-the-wire GraphQL data -- you're on your own to figure out how to read and write with your actual client. Your GraphQL javascript library may already employ introspection in some capacity. For example, the apollo codegen command uses introspection to generate types.
2022 Update
Since this answer was originally written, it is now a recommended security practice to TURN OFF introspection in production. References: Why you should disable GraphQL introspection in production and OWASP | Testing GraphQL. I recommend that you consider if these risks pertain to you.
For an environment where introspection is off in production, you could use it in development as a way to assist in creating a static query that was used in production; you wouldn't actually be able to create a query dynamically in production.
Upvotes: 250
Reputation: 801
Package graphql-type-json supports custom-scalars type JSON. Use it can show all the field of your json objects. Here is the link of the example in ApolloGraphql Server. https://www.apollographql.com/docs/apollo-server/schema/scalars-enums/#custom-scalars
Upvotes: 1
Reputation: 5742
GraphQL query format was designed in order to allow:
However, according to GraphQL documentation, you may create fragments in order to make selection sets more reusable:
# Only most used selection properties
fragment UserDetails on User {
id,
username
}
Then you could query all user details by:
FetchUsers {
users() {
...UserDetails
}
}
You can also add additional fields alongside your fragment:
FetchUserById($id: ID!) {
users(id: $id) {
...UserDetails
count
}
}
Upvotes: 9
Reputation: 4628
I faced this same issue when I needed to load location data that I had serialized into the database from the google places API. Generally I would want the whole thing so it works with maps but I didn't want to have to specify all of the fields every time.
I was working in Ruby so I can't give you the PHP implementation but the principle should be the same.
I defined a custom scalar type called JSON which just returns a literal JSON object.
The ruby implementation was like so (using graphql-ruby)
module Graph
module Types
JsonType = GraphQL::ScalarType.define do
name "JSON"
coerce_input -> (x) { x }
coerce_result -> (x) { x }
end
end
end
Then I used it for our objects like so
field :location, Types::JsonType
I would use this very sparingly though, using it only where you know you always need the whole JSON object (as I did in my case). Otherwise it is defeating the object of GraphQL more generally speaking.
Upvotes: 21
Reputation: 6846
Unfortunately what you'd like to do is not possible. GraphQL requires you to be explicit about specifying which fields you would like returned from your query.
Upvotes: 367
Reputation: 3625
I guess the only way to do this is by utilizing reusable fragments:
fragment UserFragment on Users {
id
username
count
}
FetchUsers {
users(id: "2") {
...UserFragment
}
}
Upvotes: 70