I am using graphQL to perform a search across multiple mongoDB collections and API's by combining queries. All queries return a result type of
{
_id: string;
name: string;
type: string;
}
Is there any way to flatten the data into a single array?
Combined query example:
query searchAll {
books(input: {text: "e"}) {
_id
name
type
}
magazines(input: {text: "e"}) {
_id
name
type
}
}
Response currently looks like:
{"data": {
"books": [
{
"_id": "5a8ac759c25b7235ffdc6888",
"name": "someBook",
"type": "book"
}
],
"magazines": [
{
"_id": "5a87005bc25b7235ffdc4bdf",
"name": "someMagazine-1",
"type": "magazine"
},
{
"_id": "5a870067c25b7235ffdc4be4",
"name": "someMagazine-2",
"type": "client"
}
]
}
}
Desired response:
{"data": {
"results": [
{
"_id": "5a8ac759c25b7235ffdc6888",
"name": "someBook",
"type": "book"
},
{
"_id": "5a87005bc25b7235ffdc4bdf",
"name": "someMagazine-1",
"type": "magazine"
},
{
"_id": "5a870067c25b7235ffdc4be4",
"name": "someMagazine-2",
"type": "client"
}
]
}
}
You want to look into using interfaces, here's an example of a (slightly richer) schema definition:
interface Searchable {
id: ID!
name: String!
}
type Book implements Searchable {
id: ID!
name: String!
author: Author!
publisher: Publisher!
isbn: String!
}
type Magazine implements Searchable {
id: ID!
name: String!
publisher: Publisher!
issn: String!
}
input SearchInput {
text: String!
}
type Query {
search(input: SearchInput!): [Searchable!]!
}
Here's how you'd query it:
query searchAll {
search(input: {text: "e"}) {
__typename
id
name
... on Book {
author
isbn
}
... on Magazine {
issn
}
}
}
The resolver for search would be responsible for calling all the different collections and aggregating the results into a single array. Going into more depth than this is implementation-specific, but there should be docs for using interfaces (and unions, which are similar) in whichever GraphQL implementation you're using.
The __typename field is what tells you the concrete type of the returned object, thereby letting front-end code do the appropriate display logic.
Related
What I want to do
Add data using DQL mutation https://github.com/dgraph-io/dgo#running-a-mutation and these data to also be visible via graphql.
Dgraph version: v21.03.2
DQL schema:
<Product.created_at>: datetime .
<Product.image>: string .
<Product.name>: string #index(hash) #upsert .
<Product.slug>: string #index(hash) #upsert .
<Product.updated_at>: datetime .
<dgraph.drop.op>: string .
<dgraph.graphql.p_query>: string #index(sha256) .
<dgraph.graphql.schema>: string .
<dgraph.graphql.xid>: string #index(exact) #upsert .
type <Product> {
Product.name
Product.slug
Product.image
Product.created_at
Product.updated_at
}
type <dgraph.graphql> {
dgraph.graphql.schema
dgraph.graphql.xid
}
type <dgraph.graphql.persisted_query> {
dgraph.graphql.p_query
}
GraphQL schema:
type Product {
id: ID!
name: String! #id #dgraph(pred: "Product.name")
slug: String! #id #dgraph(pred: "Product.slug")
image: String #dgraph(pred: "Product.image")
created_at: DateTime! #dgraph(pred: "Product.created_at")
updated_at: DateTime! #dgraph(pred: "Product.updated_at")
}
Using the above schema, graphql queries are working fine.
Graphql mutation
mutation MyMutation {
addProduct(input: {name: "product 1", slug: "prod-1", created_at: "2021-10-04T06:37:57.707227339Z", updated_at: "2021-10-04T06:37:57.707227339Z"}) {
numUids
}
}
Graphql Query
query MyQuery {
queryProduct {
name
}
}
response of the graphql query:
{
"data": {
"queryProduct": [
{
"name": "product 1"
}
]
},
"extensions": {
"touched_uids": 2,
"tracing": {
"version": 1,
"startTime": "2021-10-04T06:42:01.064395081Z",
"endTime": "2021-10-04T06:42:01.065675778Z",
"duration": 1280687,
"execution": {
"resolvers": [
{
"path": [
"queryProduct"
],
"parentType": "Query",
"fieldName": "queryProduct",
"returnType": "[Product]",
"startOffset": 110469,
"duration": 1164739,
"dgraph": [
{
"label": "query",
"startOffset": 149859,
"duration": 1123999
}
]
}
]
}
}
}
}
Then I did a mutation using dgo: https://github.com/dgraph-io/dgo#running-a-mutation and the data are shown fine using the ratel tool.
When I try again the Graphql Query:
query MyQuery {
queryProduct {
name
}
}
none of these are returned in the graphql response.
The issue is because the dgraph also wants a DType to be passed via dql mutation like so:
models.Product{
Name: "product name",
Slug: "product-slug",
Image: "test.jpg",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
DType: []string{"Product"}, // here is the trick
}
I am new with Aws Amplify and GraphQL and I am trying to find the best way to handle the following:
I want to be able to edit my FAQ in the frontend. The frontend gets the data from the backend, i edit one field in the frontend and save it, it should be stored in the backend
I have created a simple amplify graphql schema:
type FAQ #model(timestamps: null) {
id: ID!
sectionTitle: String
questionAndAnswer: [QuestionAndAnswer!]!
}
type QuestionAndAnswer {
id: ID!
question: String
answer: String
}
Then I populated it so it contains the following data:
{
"data": {
"listFAQS": {
"items": [
{
"sectionTitle": "Title2",
"id": "22735682-2bab-4695-b93e-1e50642d4654",
"questionAndAnswer": [
{
"answer": "answer4",
"id": "4",
"question": "question4"
},
{
"answer": "answer5",
"id": "5",
"question": "question5"
},
{
"answer": "answer6",
"id": "6",
"question": "question6"
}
]
},
{
"sectionTitle": "Title1",
"id": "21be8234-69f2-47fe-b942-d3e655197c70",
"questionAndAnswer": [
{
"answer": "answer1",
"id": "1",
"question": "question1"
},
{
"answer": "answer2",
"id": "2",
"question": "question2"
},
{
"answer": "answer3",
"id": "3",
"question": "question3"
}
]
}
]
}
}
}
Is there an easy way I can only change for instance the question "question4" without having to send the whole FAQ "row"?
When I was experimenting with it it seemed that the whole array was reseted and only the new data was saved. Should both FAQ and QuestionAndAnswer be a #model each where I connect them with #connection and update them separately as needed?
The following seems to be an easy way, as you can update only "question4" if you need, by using its own unique ID with GraphQL mutation.
type FAQ #model(timestamps: null) {
id: ID!
sectionTitle: String
questionAndAnswer: [QuestionAndAnswer!]!
}
type QuestionAndAnswer #model {
id: ID!
question: Question
answer: Answer
}
type Question #model {
id: ID!
question: String
}
type Answer #model {
id: ID!
answer: String
}
I did not include #connections (they should be in QuestionAndAnswer model), but 'Yes' would be the answer to " Should both FAQ and QuestionAndAnswer be a #model each where I connect them with #connection and update them separately as needed?".
I have this query:
getMyTransactions {
id
createdAt
}
And I got this error:
"message": "Cannot return null for non-nullable field Transaction.createdAt.",
"path": [
"_entities",
0,
"createdAt"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"serviceName": "XXX",
"query": "query($representations:[_Any!]!){_entities(representations:$representations){...on Transaction{createdAt}}}",
"variables": {
"representations": [
{
"__typename": "Transaction",
"id": "29bf75e5-b79e-4a7d-a021-84a8b5662aa8"
},
{
"__typename": "Transaction",
"id": "616f3f8a-3c81-4d2e-bce0-03d031a15062"
}
]
},
//etc
Why isvariables.representations missing createdAt values? When I do a query directly to DynamoDB I can see createdAt values for all those 2 items.
My schemas are like this:
extend type Transaction #key(fields: "id") {
id: ID! #external
}
type Query {
getMyTransactions: [Transaction!]!
}
And the other schema has Transaction type:
type Transaction #key(fields: "id") {
id: ID!
createdAt: String!
}
I have an AppSync graphql schema with Users and Events, that should have a bi-directional connection. My schema looks like this:
type User #model {
id: ID!
email: String
events: [EventUser] #connection(name: "EventUser", keyName: "byUser", fields: ["id"])
}
type Event #model {
id: ID!
name: String
description: String
registeredUsers: [EventUser] #connection(name: "EventUser", keyName: "byEvent", fields: ["id"])
invitedUsers: [User]
}
type EventUser
#model(queries: null)
#key(name: "byEvent", fields: ["eventId", "userId"])
#key(name: "byUser", fields: ["userId", "eventId"]) {
id: ID!
eventId: ID!
userId: ID!
event: Event! #connection(fields: ["eventId"])
user: User! #connection(fields: ["userId"])
}
The query that is generated by the CLI looks like this:
export const listEvents = /* GraphQL */ `
query ListEvents(
$filter: ModelEventFilterInput
$limit: Int
$nextToken: String
) {
listEvents(filter: $filter, limit: $limit, nextToken: $nextToken) {
items {
id
name
description
registeredUsers {
nextToken
}
createdAt
updatedAt
}
nextToken
}
}
`;
When I run this query with:
const result = await API.graphql(graphqlOperation(queries.listEvents, { limit: 10}))
I get these results:
[
{
"createdAt": "2020-08-17T21:36:04.502Z",
"description": "Event description",
"id": "some-unique-id",
"name": "Event name",
"registeredUsers": Object {
"nextToken": null,
},
"updatedAt": "2020-08-17T21:36:04.502Z",
},
{
"createdAt": "2020-08-17T21:36:04.502Z",
"id": "some-other-unique-id",
"name": "event 2 name",
"registeredUsers": Object {
"nextToken": null, // <- This seems wrong.
},
"updatedAt": "2020-08-17T21:36:04.502Z"
}
]
The registeredUsers field should be an array of items. I can run a query in the AppSync console like this:
query ListEvents {
listEvents( limit: 10) {
items {
id
name
registeredUsers {
items {
userId
}
}
}
}
}
and it returns correct results:
{
"data": {
"listEvents": {
"items": [
{
"id": "some-unique-id",
"name": "event name",
"registeredUsers": {
"items": [
{
"userId": "user"
},
{
"userId": "user1"
}
]
}
},
{
"id": "some-other-unique-id",
"name": "event 2 name",
"registeredUsers": {
"items": []
}
},
]
}
}
}
Obviously, the console query is slightly different but it produces correct results, although it does not handle nextToken pagination.
My question is this: Is the CLI-generated query invalid? If so, is it because my Schema is not set up correctly?
If the generated query is valid, how would I call it to get the EventUser data?
I'm using AWS appsync along with DynamoDB for my project, and I have the following schema:
type List {
id: String!
title: String!
items: [String!]! ## it's of String Set (SS) type on DynamoDB
}
type Item {
id: String!
name: String!
}
I want to get a specific list along with their items. the ids of these items are in the List object. e.g
e.g:
List
{
id: "list0001",
title: "My First list",
items: ["item_001", "item_002"]
}
Item
{
id: "item_001",
name: "Item 001"
}
I want to have the following result when querying list0001
{
id: "list0001",
title: "My First list",
items: [
{
id: "item_001",
name: "Item 001"
},
{
id: "item_002",
name: "Item 002"
}
]
}
I know that I can have the list id on Item type and then I use that id to fetch the items but I want to have it as described above by getting the items from the set of the string in List type. I want to know whether it's feasible. if so, what are the mapping templates for both queries.
N.B: I'm using serverless for my project with serverless-appsync-plugin plugin.
You could set this up with two tables, ListTable and ItemTable.
The ListTable would store the information about lists. An example entry would look like:
{
"id": "list_0000",
"title": "List0"
}
The ItemTable would be used to to relate Items to the List that they belong to. An example entry would look like:
{
"id": "item_0001",
"name": "item1",
"listId": "list_0000"
}
You would need to modify your schema as follows:
type List {
id: String!
title: String!
items: [Item!]! ## A List of Items
}
type Item {
id: String!
name: String!
}
type Query {
getList(listId: ID!): List
}
This setup would request setting up 2 resolvers, 1 on getList and 1 on the field items of the type List.
Your request mapping template for getList would look like:
{
"version": "2017-02-28",
"operation": "GetItem",
"key": {
"id": $util.dynamodb.toDynamoDBJson($ctx.args.listId),
}
}
The response mapping template would be:
$util.toJson($ctx.result)
Your request mapping template for items of the type List would look like:
{
"version" : "2018-05-29",
"operation" : "Query",
"query" : {
"expression": "listId = :listId",
"expressionValues" : {
":listId" : { "S": "$ctx.source.id" }
}
}
}
The response mapping template would be:
$util.toJson($ctx.result.items)
Running the query:
query {
getList(listId: "list_0000") {
id
title
items {
id
name
}
}
}
Would have a result like:
{
"data": {
"getList": {
"id": "list_0000",
"title": "List0",
"items": [
{
"id": "item_0001",
"name": "item1"
}
]
}
}
}