GraphQL Schema Language Handle Map Type from Uncontrolled API [duplicate] - graphql

Let's say my graphql server wants to fetch the following data as JSON where person3 and person5 are some id's:
"persons": {
"person3": {
"id": "person3",
"name": "Mike"
},
"person5": {
"id": "person5",
"name": "Lisa"
}
}
Question: How to create the schema type definition with apollo?
The keys person3 and person5 here are dynamically generated depending on my query (i.e. the area used in the query). So at another time I might get person1, person2, person3 returned.
As you see persons is not an Iterable, so the following won't work as a graphql type definition I did with apollo:
type Person {
id: String
name: String
}
type Query {
persons(area: String): [Person]
}
The keys in the persons object may always be different.
One solution of course would be to transform the incoming JSON data to use an array for persons, but is there no way to work with the data as such?

GraphQL relies on both the server and the client knowing ahead of time what fields are available available for each type. In some cases, the client can discover those fields (via introspection), but for the server, they always need to be known ahead of time. So to somehow dynamically generate those fields based on the returned data is not really possible.
You could utilize a custom JSON scalar (graphql-type-json module) and return that for your query:
type Query {
persons(area: String): JSON
}
By utilizing JSON, you bypass the requirement for the returned data to fit any specific structure, so you can send back whatever you want as long it's properly formatted JSON.
Of course, there's significant disadvantages in doing this. For example, you lose the safety net provided by the type(s) you would have previously used (literally any structure could be returned, and if you're returning the wrong one, you won't find out about it until the client tries to use it and fails). You also lose the ability to use resolvers for any fields within the returned data.
But... your funeral :)
As an aside, I would consider flattening out the data into an array (like you suggested in your question) before sending it back to the client. If you're writing the client code, and working with a dynamically-sized list of customers, chances are an array will be much easier to work with rather than an object keyed by id. If you're using React, for example, and displaying a component for each customer, you'll end up converting that object to an array to map it anyway. In designing your API, I would make client usability a higher consideration than avoiding additional processing of your data.

You can write your own GraphQLScalarType and precisely describe your object and your dynamic keys, what you allow and what you do not allow or transform.
See https://graphql.org/graphql-js/type/#graphqlscalartype
You can have a look at taion/graphql-type-json where he creates a Scalar that allows and transforms any kind of content:
https://github.com/taion/graphql-type-json/blob/master/src/index.js

I had a similar problem with dynamic keys in a schema, and ended up going with a solution like this:
query lookupPersons {
persons {
personKeys
person3: personValue(key: "person3") {
id
name
}
}
}
returns:
{
data: {
persons: {
personKeys: ["person1", "person2", "person3"]
person3: {
id: "person3"
name: "Mike"
}
}
}
}
by shifting the complexity to the query, it simplifies the response shape.
the advantage compared to the JSON approach is it doesn't need any deserialisation from the client
Additional info for Venryx: a possible schema to fit my query looks like this:
type Person {
id: String
name: String
}
type PersonsResult {
personKeys: [String]
personValue(key: String): Person
}
type Query {
persons(area: String): PersonsResult
}
As an aside, if your data set for persons gets large enough, you're going to probably want pagination on personKeys as well, at which point, you should look into https://relay.dev/graphql/connections.htm

Related

How to approach a GraphQL query that returns a boolean value?

Need to check whether an email is available or taken during the user sign-up process. The goal is to quickly query, using GraphQL, the API server and have it tell us if the email is available or taken.
What is the general best practice on a simple boolean-ish type of situation using GraphQL?
Below is what I have come up with but I am unsure if this is a good practice or not and want to hear feedback on a better practice on queries like this.
Request:
query {
emailExists(email:"jane#doe.com") {
is
}
}
Response:
{
"data": {
"emailExists": {
"is": true
}
}
}
A "query" is just a field on what happens to be the Query type. A field can return any output type, including scalars -- it doesn't need to return an object. So it's sufficient to have a schema like:
type Query {
emailExists(email: String!): Boolean!
}
The only reason to prefer an object type would be if you anticipated wanting to add additional fields in the future (i.e. something other than your current is field).

Are ids automitaclly encrypted in Graphql queries?

In my database (postgres) I store a table of events and each event has an "id" column which is a UUID type. But when I send a GraphQL query the id I receive back is not a string UUID but looks like some encrypted version.
I'm using Relay and Graphene on the server side if that information helps.
query {
allEvents {
edges {
node {
id
}
}
}
}
{
"data": {
"allEvents": {
"edges": [
{
"node": {
"id": "RXZlbnQ6NzRkZTIxZmUtZWQyNy00OTg1LTk2NjEtNmU4ZDUzMGEwMjQ3"
}
}
]
}
}
}
A frequent implementation choice is to base64-encode IDs and cursor values (the query you show follows Relay's pagination conventions). If you base64-decode the string you put in the question, you'll find a UUID again.
At a GraphQL level, ID is nothing more or less than an opaque ID. The spec itself doesn't say much about it, other than that it serializes as a string but could accept a number as input instead. Most more application-oriented server libraries I've worked with don't put much in the way of special semantics around ID either, like other scalar types it gets passed in and out as-is.
The last paragraph of the documentation on Relay's object identification scheme (the node top-level query) also has a strong opinion that applications shouldn't be synthesizing ID values, and so the base64 encoding at least hints to consumers that the value isn't supposed to be understood. The base64 encoding here isn't anything generic or hard-coded in GraphQL, and an application or library could choose a different ID scheme if it wanted.

How to load the graphql queries from the server without defining it in the front end?

Now let's say we are using a REST API. I have one endpoint like this: /homeNewsFeed. This API will give us a response like this:
[
{
blockTitle: 'News',
type: 'list',
api: 'http://localhost/news'
},
{
blockTitle: 'Photos',
type: 'gallery',
api: 'http://localhost/gallery'
}
]
Now after getting this we go through the array and call the respective endpoints to load the data. My question is, how to do this in GraphQL? Normally we define the query in the front end code. Without doing that, how to let the server decide what to send?
The main reason to do this is. Imagine we have a mobile app. We need to push new blocks to this news feed without sending an app update. But each item can have their own query.
Normally we define the query in the front end code. Without doing that, how to let the server decide what to send?
Per the spec, a GraphQL execution request must include two things: 1) a schema; and 2) a document containing an operation definition. The operation definition determines what operation (which query or mutation) to execute as well as the format of the response. There are work arounds and exceptions (I'll discuss some below), but, in general, if specifying the shape of the response on the client-side is undesirable or somehow not possible, you should carefully consider whether GraphQL is the right solution for your needs.
That aside, GraphQL lends itself more to a single request, not a series of structured requests like your existing REST API requires. So the response would look more like this:
[
{
title: 'News',
content: [
...
],
},
{
title: 'Photos',
content: [
...
],
}
]
and the corresponding query might look like this:
query HomePageContent {
blocks {
title
content {
# additional fields
}
}
}
Now the question becomes how do differentiate between different kinds of content. This is normally solved by utilizing an interface or union to aggregate multiple types into a single abstract type. The exact structure of your schema will depend on the data you're sending, but here's an example:
interface BlockContentItem {
id: ID!
url: String!
}
type Story implements BlockContentItem {
id: ID!
url: String!
author: String!
title: String!
}
type Image implement BlockContentItem {
id: ID!
url: String!
alt: String!
}
type Block {
title: String!
content: [BlockContentItem!]!
}
type Query {
blocks: [Block!]!
}
You can now query blocks like this:
query HomePageContent {
blocks {
title
content {
# these fields apply to all BlockContentItems
__typename
id
url
# then we use inline fragments to specify type-specific fields
... on Image {
alt
}
... on Story {
author
title
}
}
}
}
Using inline fragments like this ensures type-specific fields are only returned for instances of those types. I included __typename to identify what type a given object is, which may be helpful to the client app (clients like Apollo automatically include this field anyway).
Of course, there is still the issue of what happens when you want to add a new block. If the block's content fits an existing type, no sweat. But what happens when you anticipate you will need a different type in the future, but can't design around that right now?
Typically, that sort of change would require both a schema change on the server and a query change on the client. And in most cases, this will probably be fine because if you're getting data in a different structure, you will have to update your client app anyway. Otherwise, your app won't know how to render the new data structure correctly.
But let's say we want to future-proof our schema anyway. Here's two ways you could go about doing it.
Instead of specifying an interface for content, just utilize a custom JSON scalar. This will effectively throw the response validation out the window, but it will allow you to return whatever you want for the content of a given block.
Abstract out whatever fields might be needed in the future into some kind of value-key type. For example:
.
type MetaItem {
key: String!
value: String!
}
type Block {
title: String!
meta: [MetaItem!]!
# other common fields
}
There's any number of other workarounds, some better than others depending on the kind of data you're working with. But hopefully that gives you some idea how to address the scenario you describe in a GraphQL context.

Apollo/GraphQL field type for object with dynamic keys

Let's say my graphql server wants to fetch the following data as JSON where person3 and person5 are some id's:
"persons": {
"person3": {
"id": "person3",
"name": "Mike"
},
"person5": {
"id": "person5",
"name": "Lisa"
}
}
Question: How to create the schema type definition with apollo?
The keys person3 and person5 here are dynamically generated depending on my query (i.e. the area used in the query). So at another time I might get person1, person2, person3 returned.
As you see persons is not an Iterable, so the following won't work as a graphql type definition I did with apollo:
type Person {
id: String
name: String
}
type Query {
persons(area: String): [Person]
}
The keys in the persons object may always be different.
One solution of course would be to transform the incoming JSON data to use an array for persons, but is there no way to work with the data as such?
GraphQL relies on both the server and the client knowing ahead of time what fields are available available for each type. In some cases, the client can discover those fields (via introspection), but for the server, they always need to be known ahead of time. So to somehow dynamically generate those fields based on the returned data is not really possible.
You could utilize a custom JSON scalar (graphql-type-json module) and return that for your query:
type Query {
persons(area: String): JSON
}
By utilizing JSON, you bypass the requirement for the returned data to fit any specific structure, so you can send back whatever you want as long it's properly formatted JSON.
Of course, there's significant disadvantages in doing this. For example, you lose the safety net provided by the type(s) you would have previously used (literally any structure could be returned, and if you're returning the wrong one, you won't find out about it until the client tries to use it and fails). You also lose the ability to use resolvers for any fields within the returned data.
But... your funeral :)
As an aside, I would consider flattening out the data into an array (like you suggested in your question) before sending it back to the client. If you're writing the client code, and working with a dynamically-sized list of customers, chances are an array will be much easier to work with rather than an object keyed by id. If you're using React, for example, and displaying a component for each customer, you'll end up converting that object to an array to map it anyway. In designing your API, I would make client usability a higher consideration than avoiding additional processing of your data.
You can write your own GraphQLScalarType and precisely describe your object and your dynamic keys, what you allow and what you do not allow or transform.
See https://graphql.org/graphql-js/type/#graphqlscalartype
You can have a look at taion/graphql-type-json where he creates a Scalar that allows and transforms any kind of content:
https://github.com/taion/graphql-type-json/blob/master/src/index.js
I had a similar problem with dynamic keys in a schema, and ended up going with a solution like this:
query lookupPersons {
persons {
personKeys
person3: personValue(key: "person3") {
id
name
}
}
}
returns:
{
data: {
persons: {
personKeys: ["person1", "person2", "person3"]
person3: {
id: "person3"
name: "Mike"
}
}
}
}
by shifting the complexity to the query, it simplifies the response shape.
the advantage compared to the JSON approach is it doesn't need any deserialisation from the client
Additional info for Venryx: a possible schema to fit my query looks like this:
type Person {
id: String
name: String
}
type PersonsResult {
personKeys: [String]
personValue(key: String): Person
}
type Query {
persons(area: String): PersonsResult
}
As an aside, if your data set for persons gets large enough, you're going to probably want pagination on personKeys as well, at which point, you should look into https://relay.dev/graphql/connections.htm

Can graphql return aggregate counts?

Graphql is great and I've started using it in my app. I have a page that displays summary information and I need graphql to return aggregate counts? Can this be done?
You would define a new GraphQL type that is an object that contains a list and a number. The number would be defined by a resolver function.
On your GraphQL server you can define the resolver function and as part of that, you would have to write the code that performs whatever calculations and queries are necessary to get the aggregate counts.
This is similar to how you would write an object serializer for a REST API or a custom REST API endpoint that runs whatever database queries are needed to calculate the aggregate counts.
GraphQL's strength is that it gives the frontend more power in determining what data specifically is returned. Some of what you write in GraphQL will be the same as what you would write for a REST API.
There's no automatic aggregate function in GraphQL itself.
You can add a field called summary, and in the resolve function calculate the totals.
You should define a Type of aggregated data in Graphql and a function you want to implement it. For example, if you want to write the following query:
SELECT age, sum(score) from student group by age;
You should define the data type that you want to return:
type StudentScoreByAge{
age: Int
sumOfScore: Float
}
and a Graphql function:
getStudentScoreByAge : [StudentScoreByAge]
async function(){
const res = await client.query("SELECT age, sum(score) as sumOfScore
from Student group by age");
return res.rows;
}
... need graphql to return aggregate counts? Can this be done?
Yes, it can be done.
Does GraphQL does it automatically for you? No, because it does not know / care about where you get your data source.
How? GraphQL does not dictate how you get / mutate the data that the user has queried. It's up to your implementation to get the requested aggregated data. You could get aggregated data directly from your MongoDB and serve it back, or you get all the data you need from your data source and do the aggregation yourself.
If you are using Hasura, in the explorer, you can definitely see an "agregate" table name, thus, your query would look something similar to the following:
query queryTable {
table_name {
field1
field2
}
table_name_aggregate {
aggregate { count }
}
}
In your results, you will see the total row count for the query
"table_name_aggregate": {
"aggregate": {
"count": 9973
}
This depends on whether you build the aggregator into your schema and are able to resolve the field.
Can you share what kind of GraphQL Server you're running? As different languages have different implementations, as well as different services (like Hasura, 8base, and Prisma).
Also, when you say "counts", I'm imagining a count of objects in a relation. Such as:
query {
user(id: "1") {
name
summaries {
count
}
}
}
// returns
{
"data": {
"user": {
"name": "Steve",
"summaries": {
"count": 10
}
}
}
}
8base provides the count aggregate by default on relational queries.

Resources