Problem
Hello friends,
I am working on an api using Apollo Server.
I am having the problem of how to display the nextEpisodeDate property only once. My solution shows nextEpisodeDate in all sub-array in the episodes property and it shouldn't be like that.
I hope someone can help me !
JSON Example
"episodes": [
{
"nextEpisodeDate": "2020-01-17"
},
{
"episode": 3,
"id": "53789/dorohedoro-3",
"imagePreview": "https://cdn.animeflv.net/screenshots/3274/3/th_3.jpg"
},
{
"episode": 2,
"id": "53755/dorohedoro-2",
"imagePreview": "https://cdn.animeflv.net/screenshots/3274/2/th_3.jpg"
},
{
"episode": 1,
"id": "53705/dorohedoro-1",
"imagePreview": "https://cdn.animeflv.net/screenshots/3274/1/th_3.jpg"
}
]
typeDefs
const resolvers = require('./resolvers');
const {gql} = require('apollo-server');
const typeDefs = gql `
extend type Query{
latest_anime: [Animes]
}
type Animes{
title: String
poster: String
synopsis: String
debut: String
type: String
rating: String
genres: [String]
episodes: [Episodes]
}
type Episodes{
nextEpisodeDate: String
episode: String
id: String
imagePreview: String
}
`
module.exports = {
typeDefs,
resolvers
};
Apollo Playground
query{
latest_anime{
title
poster
synopsis
debut
type
rating
genres
episodes{
nextEpisodeDate
episode
id
imagePreview
}
}
}
Apollo Playground Output
{
"data": {
"latest_anime": [
{
"title": "Tsugumomo OVA",
"poster": "https://animeflv.net/uploads/animes/covers/3275.jpg",
"synopsis": "OVA 4.6Kazuya Kagami nunca va a ningún lado sin su preciada “Sakura Obi” que su madre le regaló. Un día, una hermosa chica vestida con un kimono llamada Kiriha aparece ante él. Naturalmente, ella comienza a vivir en su habitación. ¿Naturalmente? ¡Esto solo es el inicio de la embarazosa y confusa...",
"debut": null,
"type": "OVA",
"rating": "4.6",
"genres": [
"accion",
"comedia",
"ecchi",
"escolares",
"seinen",
"sobrenatural"
],
"episodes": [
{
"nextEpisodeDate": null,
"episode": null,
"id": null,
"imagePreview": null
},
{
"nextEpisodeDate": null,
"episode": "1",
"id": "53753/tsugumomo-ova-1",
"imagePreview": "https://cdn.animeflv.net/screenshots/3275/1/th_3.jpg"
}
]
},
]
}
}
The only way you can get the desired response structure is to have two separate types. A field must have exactly one type, but you can use an abstract type like a union or interface in order to have each individual item in the list resolve to one of multiple types at runtime.
type AiredEpisode implements Episode {
id: String
episode: String
imagePreview: String
}
type UpcomingEpisode implements Episode {
id: String
nextEpisodeDate: String
}
interface Episode {
id: String
}
type Anime {
episodes: [Episode]
# other fields
}
You would then query the episodes like this:
query {
latest_anime {
episodes {
# fields on the interface itself like id are common to all
# implementing types so they don't need to be inside a fragment
id
# fields specific to one of the types need to be inside a fragment
... on UpcomingEpisode {
nextEpisodeDate
}
... on AiredEpisode {
id
episode
imagePreview
}
}
}
}
Side note: if your API doesn't return an id for the upcoming episodes, you should still provide one (you could use the show's id, for example, you just want to make sure it's unique). This will ensure that you don't run into caching issues if you use a client like Apollo on the front end.
Related
I'm very new into graphql but I'm able to insert a new "notification" that belongs to a "Tienda" which means store, however I'm not able to indicate to which store it belongs to.
This is the schema
type Tienda #model {
id: ID!
name: String!
cliente: [Cliente] #manyToMany(relationName: "clienteDeTienda")
news: [Notificacion] #hasMany
}
type Cliente #model {
id: ID!
name: String!
stores: [Tienda] #manyToMany(relationName: "clienteDeTienda")
}
type Notificacion #model {
id: ID!
store: Tienda #belongsTo
content: String!
}
This is how I insert a new notification:
const response = await API.graphql(graphqlOperation(createNotificacion, {input:data}))
This is what data has inside:
Object {
"content": "Nueva notificacion",
}
And this is the response I get from console.log(response):
Object {
"data": Object {
"createNotificacion": Object {
"content": "Nueva notificacion",
"createdAt": "2022-11-13T18:04:55.000Z",
"id": "9f50d333-fc9e-478f-be6a-c54275797a27",
"store": null,
"tiendaNewsId": null,
"updatedAt": "2022-11-13T18:04:55.000Z",
},
},
}
As you can see "content" was added to the notification but "store" is null but I haven't been successful in inserting the store ID in there.
This is how the mutations is defined:
export const createNotificacion = /* GraphQL */ `
mutation CreateNotificacion(
$input: CreateNotificacionInput!
$condition: ModelNotificacionConditionInput
) {
createNotificacion(input: $input, condition: $condition) {
id
store {
id
name
cliente {
nextToken
}
news {
nextToken
}
createdAt
updatedAt
}
content
createdAt
updatedAt
tiendaNewsId
}
}
`;
I've tried with the following 3 methods which give no error but still doesn't link to it's parent:
API.graphql(graphqlOperation(createNotificacion, {input:data, store:{id:'55988776-11af-42b8-b93b-9de9af35f7dc'}}))
And
API.graphql(graphqlOperation(createNotificacion, {input:data, store:'55988776-11af-42b8-b93b-9de9af35f7dc'}))
And:
API.graphql(graphqlOperation(createNotificacion, {input:data},{store:'55988776-11af-42b8-b93b-9de9af35f7dc'}))
Please somebody point me the right path or documentation about how to do this. I need to know how to perform the graphqlOperation correctly.
Thank you very much
In the end what I had to do is this:
const response = await API.graphql(graphqlOperation(createNotification, {input:{...data, storeNotificationsId:'someValidStoreId'}}));
Let's imagine we have GraphQL API that can return an object Entity with Id and Name properties and I requested Name only:
query {
entities {
name
}
}
And it returns
{
"data": {
"entities": [
{
"name": "Name1"
},
{
"name": "Name2"
}
]
}
}
But what if I want to have only the name of entities as a scalar type? In other words, I want to have something like:
{
"data": {
"entities": [
"Name1",
"Name2"
]
}
}
Is it possible to have such result without changes on the GraphQL API side? Aliases, Fragments, etc. GraphQL has a lot of built-in query capabilities, but none of the known me can return complex objects as scalar type.
what you're asking for is almost impossible if you don't want to change the type definition for Entities.
This: 👇🏽
Entity: id: int! name: String
entities(): [Entity]
returns an array of objects with keys name and id.
To achieve what you're asking you either change Entity to be just a string or have your client reduce that object to an array of just Entity names when they receive it.
They could do something like this:
const data = {
entities: [
{
name: 'Name1',
},
{
name: 'Name2',
},
],
};
const entityNames = data.entities.reduce(
(acc, curr) => [...acc, curr.name],
[]
);
console.log(entityNames);
I'm wrapping a series of REST APIs in GraphQL and I'm having trouble getting a resolver to work.
My typeDefs
const typeDefs = `
type Page {
id: String
path: String!
title: String!
heading: String
type: String
html: String
summary: String
}
type Site {
page(path: String!): Page
}
type Query {
site: Site
}`
and my resolvers
const resolvers = {
Query: {
site: {
page: async (_root, { path }) => getPage(path)
}
}
}
If I try this query
{
site {
page(path: "/services/research-request") {
id
path
title
html
type
summary
}
}
}
I get back
{
"data": {
"site": null
}
}
However if I don't nest the page within the site
const typeDefs = `
type Page {
id: String
path: String!
title: String!
heading: String
type: String
html: String
summary: String
}
type Query {
page(path: String!): Page
}`
and use resolvers
const resolvers = {
Query: {
page: async (_root, { path }) => getPage(path)
}
}
then I try this query
{
page(path: "/services/research-request") {
id
path
title
html
type
summary
}
}
I get back, correctly,
{
"data": {
"page": {
"id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"path": "/services/research-request",
"title": "Research | Requests",
"html": "<p>This is a really well put together Research Requests page<p>\n",
"type": "page",
"summary": "A short piece of summary text"
}
}
}
I'm going to be pulling in data from a number of APIs to populate my graph so wanted to group the various parts according to site vs user, etc but I'm missing something obvious when trying to nest the resolvers. What am I doing wrong?
No 'free' nesting in graphql ... nest > next level depth > next TYPE ... site resolver, site.page resolver ... but also own site id (for client cache), rebuild/adjust all clients code etc. ...
Namespacing should be better: sitePage, siteOptions, userProfile, userSettings ... use alias (or replace in files) for renaming in client.
I'm trying to wrap my head around GraphQL.
Right now I'm just playing with the public API of Artsy (an art website, playground at https://metaphysics-production.artsy.net). What I want to achieve is following:
I want to get all node types entities without declaring them by hand (is there a shortcut for this)?
I want every node with a field type from which I can read the type, without parsing through imageUrl etc. to fint that out.
What I constructed as of right now is this:
{
search(query: "Berlin", first: 100, page: 1, entities: [ARTIST, ARTWORK, ARTICLE]) {
edges {
node {
displayLabel
imageUrl
href
}
}
}}
Very primitive I guess. Can you guys help me?
TL;DR:
1) There is no shortcut, it's not something GraphQL offers out of the box. Nor is it something I was able to find via their Schema.
2) Their returned node of type Searchable does not contain a property for type that you're looking for. But you can access it via the ... on SearchableItem (union) syntax.
Explanation:
For question 1):
Looking at their schema, you can see that their search query has the following type details:
search(
query: String!
entities: [SearchEntity]
mode: SearchMode
aggregations: [SearchAggregation]
page: Int
after: String
first: Int
before: String
last: Int
): SearchableConnection
The query accepts an entities property of type SearchEntity which looks like this:
enum SearchEntity {
ARTIST
ARTWORK
ARTICLE
CITY
COLLECTION
FAIR
FEATURE
GALLERY
GENE
INSTITUTION
PROFILE
SALE
SHOW
TAG
}
Depending on what your usecase is, if you're constructing this query via some code, then you can find out which SearchEntity values they have:
{
__type(name: "SearchEntity") {
name
enumValues {
name
}
}
}
Which returns:
{
"data": {
"__type": {
"name": "SearchEntity",
"enumValues": [
{
"name": "ARTIST"
},
{
"name": "ARTWORK"
},
...
}
}
}
then store them in an array, omit the quotation marks from the enum and pass the array back to the original query directly as an argument.
Something along the lines of this:
query search($entities: [SearchEntity]) {
search(query: "Berlin", first: 100, page: 1, entities: $entities) {
edges {
node {
displayLabel
imageUrl
href
}
}
}
}
and in your query variables section, you just need to add:
{
"entities": [ARTIST, ARTWORK, ...]
}
As for question 2)
The query itself returns a SearchableConnection object.
type SearchableConnection {
pageInfo: PageInfo!
edges: [SearchableEdge]
pageCursors: PageCursors
totalCount: Int
aggregations: [SearchAggregationResults]
}
Digging deeper, we can see that they have edges, of type SearchableEdge - which is what you're querying.
type SearchableEdge {
node: Searchable
cursor: String!
}
and finally, node of type Searchable which contains the data you're trying to access.
Now, the type Searchable doesn't contain type:
type Searchable {
displayLabel: String
imageUrl: String
href: String
}
But, if you look at where that Searchable type is implemented, you can see SearchableItem - which contains the property of displayType - which doesn't actually exist in Searchable.
You can access the property of SearchableItem and get the displayType, like so:
{
search(query: "Berlin", first: 100, page: 1, entities: [ARTIST, ARTWORK, ARTICLE]) {
edges {
node {
displayLabel
imageUrl
href
... on SearchableItem {
displayType
}
}
}
}
}
and your result will look like this:
{
"data": {
"search": {
"edges": [
{
"node": {
"displayLabel": "Boris Berlin",
"imageUrl": "https://d32dm0rphc51dk.cloudfront.net/CRxSPNyhHKDIonwLKIVmIA/square.jpg",
"href": "/artist/boris-berlin",
"displayType": "Artist"
}
},
...
Im new to AWS AppSync however its been pretty easy to learn and understand.
Im trying to create a resolver that when the user runs getChore(id: "") it will return all the chore information. Which its successfully doing, the problem is within the chore there are two fields: createdBy & assignedTo which are linked to a user type.
type Chore {
id: ID!
title: String
desc: String
status: String
reward: Float
retryDeduction: Float
required: Boolean
createdDate: AWSDateTime
date: AWSDateTime
interval: String
assignedTo: User
createdBy: User
}
type User {
id: ID!
age: Int
f_name: String
l_name: String
type: Int
admin: Boolean
family: Family
}
within aws appsync in trying to attach a resolver to assignedTo: User and createdBy: User so my query will look like:
query getChore {
getChore(id: "36d597c8-2c7e-4f63-93ee-38e5aa8f1d5b") {
id
...
...
assignedTo {
id
f_name
l_name
}
createdBy {
id
f_name
l_name
}
}
}
however when i fire off this query im getting an error:
The provided key element does not match the schema (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ValidationException;
which i have researched and cant seem to find the correct soltuion.
The resolver im using is:
{
"version": "2017-02-28",
"operation": "GetItem",
"key": {
"id": $util.dynamodb.toDynamoDBJson($ctx.args.id),
}
}
return:
$util.toJson($ctx.result)
When you get the The provided key element does not match the schema error, it's because your request mapping template key doesn't match the primary key in DynamoDB. You can enable CloudWatch Logs in your Application settings to see exactly what was sent to DynamoDB.
I'm not able to know what's wrong with your template because your sample lacks some information, if you can answers the questions pertaining to your application:
- Where are the users stored? Are they stored in their own DDB table separate from the chores, and is the hash key on the users table id as well?
- In the chores table how do you know which user your chore is assignedTo or createdBy? Is there a user id stored on the chore DDB item?
- Is the request mapping template you posted corresponding to the resolver attached to Chore.assignedTo? If yes, using $ctx.args.id will actually do a GetItem based on the chore id not the user it's assigned to.
Finally, I reproduced your application and I was able to make it work with a few changes.
Prerequisites:
I have a chores and a users DynamoDB table with both having id as hash key. These two tables are mapped as datasources in AppSync.
I have one chore in the chores tables that looks like
{
"assignedTo": "1",
"createdBy": "2",
"id": "36d597c8-2c7e-4f63-93ee-38e5aa8f1d5b",
"title": "Chore1"
}
and two users in the users table:
{
"f_name": "Alice",
"id": "2",
"l_name": "Wonderland"
}
and
{
"f_name": "John",
"id": "1",
"l_name": "McCain"
}
I used your GraphQL schema
Resolvers
Resolver on Query.getChore pointing to the chores table:
{
"version": "2017-02-28",
"operation": "GetItem",
"key": {
"id": $util.dynamodb.toDynamoDBJson($ctx.args.id),
}
}
Resolver on Chore.assignedTo pointing to the users table (note the $ctx.source.assignedTo instead of $ctx.args)
{
"version": "2017-02-28",
"operation": "GetItem",
"key": {
"id": $util.dynamodb.toDynamoDBJson($ctx.source.assignedTo),
}
}
Similarly, resolver on Chore.createdBy pointing to the users table:
{
"version": "2017-02-28",
"operation": "GetItem",
"key": {
"id": $util.dynamodb.toDynamoDBJson($ctx.source.createdBy),
}
}
All resolvers response mapping template use the pass-through.
Running the query
Finally, when running your query:
query getChore {
getChore(id: "36d597c8-2c7e-4f63-93ee-38e5aa8f1d5b") {
id
assignedTo {
id
f_name
l_name
}
createdBy {
id
f_name
l_name
}
}
}
I get the following results:
{
"data": {
"getChore": {
"id": "36d597c8-2c7e-4f63-93ee-38e5aa8f1d5b",
"assignedTo": {
"id": "1",
"f_name": "John",
"l_name": "McCain"
},
"createdBy": {
"id": "2",
"f_name": "Alice",
"l_name": "Wonderland"
}
}
}
}
Hope it helps!