Related
I am trying the GraphQL for the first time. I have a express-graphql server connected to MySQL for hypothetical juice shops, where a owner has ability add or remove or rename the serve type.
For example
Shop A has serves like "Cute Small","The Regular" and "Extravaganza"
Where as shop B serves like "Xsmall","small","medium","large" and "Xlarge"
As the GraphQL fields are mandatory, I am unable think of solution for this particular scenario.
In short, I would love to know if there is a way to write a GraphQLObjectType where the fields can be any/not mentioned.
Snippet of a menu type, were the fields is very specific
var typeDef = new GraphQLObjectType({
name: "Menu",
fields: {
name: { type: GraphQLString },
small_serve: { type: GraphQLFloat },
regular_serve: { type: GraphQLFloat },
medium_serve: { type: GraphQLFloat },
large_serve: { type: GraphQLFloat },
},
});
GraphiQL
{
menus{
name,
small_serve,
regular_serve,
medium_serve,
large_serve
}
}
I'm using Prisma 1.34. fro API development. Testing it through the localhost playground.
Sorry for long text in advance, can't understand where I went wrong.
I have the following scheme representing hierarchy Script template consist of the Cards templates, and Cards include Tasks templates:
type ScriptTemplate {
id: ID!
name: String!
cards: [CardTemplate!]
input: String!
output: String!
}
type CardTemplate {
id: ID!
title: String!
description: String
tasks: [TaskTemplate!]
}
input ExistingCardTemplateInput {
id: ID!
}
input NewCardTemplateInput {
title: String!
description: String!
tasks: [NewTaskTemplateInput!]
}
type TaskTemplate {
id: ID!
label: String!
description: String!
cards: [CardTemplate!]
}
input ExistingTaskTemplateInput {
id: ID!
}
input NewTaskTemplateInput {
label: String!
description: String!
}
Corresponding mutations are:
type Mutation {
createScriptTemplate(name: String!, input: String!, output: String!, cards: [ExistingCardTemplateInput!], new_cards: [NewCardTemplateInput!]): ScriptTemplate
createCardTemplate(title: String!, description: String! tasks:[ExistingTaskTemplateInput!], new_tasks:[NewTaskTemplateInput!]): CardTemplate
createTaskTemplate(label: String!, description: String! cards:[ExistingCardTemplateInput!]): TaskTemplate
}
So basically, if I trying to use createTaskTemplate mutation or createCardTemplate mutation - everything is working fine. I can create these entities, including nested mutation creating new Card with the new Tasks in it or binding already existing Tasks. Or existing Card to newly created Task. That's why explicitly defined input types: ExistingTaskTemplateInput, NewTaskTemplateInput and NewCardTemplateInput.
Everything is working as expected when I'm trying to create a new script with the inclusion of a new Card or connecting it to an existing one, as well.
However, if I'm trying to create Script, Card and include new Tasks in it I've got error messages above.
When trying the following mutation:
mutation{
createScriptTemplate(
name: "Script via API_H2"
input: "Something describing initial state"
output: "Something describing required state at the end"
cards: [
{
id: "cjycl2nup00ta0703sd0kd8oa"
},
{
id: "cjye3ryee01ey070383sxaoxz"
}
]
new_cards:[
{
title:"New card via scriptis2"
description:"desc"
tasks: [
{
description: "test dewscription"
label: "test label"
}
]
},
{
title:"New card through scriptos2"
description: "desc"
}
]
){
id
name
input
output
createdAt
updatedAt
cards{
id
title
tasks{
id
label
}
}
}
}
I'm having error:
{
"data": {
"createScriptTemplate": null
},
"errors": [
{
"message": "Variable '$data' expected value of type 'ScriptTemplateCreateInput!' but got:
{\"name\":\"First Script through API_H2\",\"input\":\"Something describing initial state\",\"output\":\"Something describing requred state at the end\",\"cards\":{\"connect\":[{\"id\":\"cjycl2nup00ta0703sd0kd8oa\"},{\"id\":\"cjye3ryee01ey070383sxaoxz\"}],\"create\":[{\"title\":\"New card via scriptis2\",\"description\":\"desc\",\"tasks\":[{\"label\":\"test label\",\"description\":\"test dewscription\"}]},{\"title\":\"New card through scriptos2\",\"description\":\"desc\"}]}}.
Reason: 'cards.create[0].tasks'
Expected 'TaskTemplateCreateManyWithoutCardsInput', found not an object. (line 1, column 11):\nmutation ($data: ScriptTemplateCreateInput!) {\n ^",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"createScriptTemplate"
]
}
]
}
The actual request looks like this (via console.log) :
{ name: 'First Script through API_H2',
input: 'Something describing initial state',
output: 'Something describing requred state at the end',
cards:
{ connect:
[ [Object: null prototype] { id: 'cjycl2nup00ta0703sd0kd8oa' },
[Object: null prototype] { id: 'cjye3ryee01ey070383sxaoxz' } ],
create:
[ [Object: null prototype] {
title: 'New card via scriptis2',
description: 'desc',
tasks:
[ [Object: null prototype] { label: 'test label', description: 'test dewscription' } ] },
[Object: null prototype] { title: 'New card through scriptos2', description: 'desc' } ] } }
Looks like I missing {connect or create} bit for the tasks field.
However when I'm altering it to look like:
tasks: {create: [
{
description: "test dewscription"
label: "test label"
}
]
}
I'm getting error that Field \"create\" is not defined by type NewTaskTemplateInput and Field NewTaskTemplateInput.label and description of required type String! was not provided
However, this works perfectly fine (same request without tasks) :
mutation{
createScriptTemplate(
name: "Script via API_H2"
input: "Something describing initial state"
output: "Something describing required state at the end"
cards: [
{
id: "cjycl2nup00ta0703sd0kd8oa"
},
{
id: "cjye3ryee01ey070383sxaoxz"
}
]
new_cards:[
{
title:"New card via scriptis2"
description:"desc"
},
{
title:"New card through scriptos2"
description: "desc"
}
]
){
id
name
input
output
createdAt
updatedAt
cards{
id
title
tasks{
id
label
}
}
}
}
Checked generated scheme, can't spot any problems there.
input TaskTemplateCreateManyWithoutCardsInput {
create: [TaskTemplateCreateWithoutCardsInput!]
connect: [TaskTemplateWhereUniqueInput!]
}
input TaskTemplateCreateWithoutCardsInput {
id: ID
label: String!
description: String!
}
Looks like I'm confusing scheme that I defined in gql files and the one I'm doing requests again, but don't know which direction to go.
Description
First outline the entire process of a request.
The above image is taken from Prisma Basics in the official Prisma documentation.
As shown in this picture, localhost playground is the client in the figure. The Server accessed by localhost playground is the API Server in the figure, which is Prisma Client.Here is the first connection, the green arrow in the picture.
Prisma Client gets the data from the database by accessing Prisma Server. This is the second connection, the yellow arrow in the figure.
The process of data flow, sent from localhost playground to API Server to complete the first connection transmission, and then processed by resolve, use Prisma Client to send to Prisma Server to complete the second connection transmission. Prisma Server retrieves data from Database according to the requested data and returns it to API Server, completes the second connection acceptance. API Server returns the data to localhost playground, completing the acceptance of the first connection. A complete process is completed.
In the process of using Prisma, we define two schema, one for defining Prisma Server, the name is usually datamodel.prisma. The other one is used to define Prisma Client, if it is defined in the file , usually named schema.graphql, can also be defined in the JS file through the template string, there are other ways, here is not discussed in depth. What you are showing belongs to Client Schema.
Successful completion of this process requires that the data sent by each part is correct.
Your first error occurred when the second connection was sent. The data sent from API Server did not match the schema definition in Prisma Server.
The second error is that the data sent from localhost playground does not match the schema definition in API Server when the first connection is sent.
Example
Assume the following architecture
# datamodel.prisma
type User {
id: #id!
name: String!
post: [Post!]!
}
type Post {
id: #id!
title: String!
}
# schema.graphql
input CreatePost {
title: String!
}
type Mutation {
createUser(name: String!,posts:[CreatePost])
}
Use the following mutation Create user
mutation {
createUser(name: "prisma", posts: [{ title: "test" }]) {
id
name
}
}
When sent from localhost playground to API Server, the data is as follows
{
"name": "prisma",
"posts": [{ "title": "test" }]
}
If you send this data directly to Prisma Server, it does not conform to the definition of Prisma Server. You need to convert the data to a definition that conforms to the Prisma Server schema.
{
"name": "prisma",
"posts": { "create": [{ "title": "test" }] }
}
Then send it to Prisma Server via Prisma Client.
const resolve = {
Mutation: {
createUser: async (_, args) => {
const userCreateInput = {
name: args.name,
posts: {
create: args.posts,
},
}
return await prisma.createUser(userCreateInput)
},
},
}
This way the final data arrives at the database via Prisma Server.
Solve the problem
Check out the definition of createScriptTemplate in API Server.It should be that it did not convert the received data into an error caused by the format required by API Server.
Understand Prisma
I have two collections:
dbPosts
id: mongoose.Schema.Types.ObjectId,
title: { type: String },
content: { type: String },
excerpt: { type: String },
slug: { type: String },
author: {
id: { type: String },
fname: { type: String },
lname: { type: String },
}
dbAuthors
id: mongoose.Schema.Types.ObjectId,
fname: { type: String },
lname: { type: String },
posts: [
id: { type: String },
title: { type: String }
]
I resolve my author queries as follows:
Query: {
authors: (parent, root, args, context) => {
return dbAuthor.find({});
},
author: (root, args, context) => {
return dbAuthor.findById(args.id);
},
},
Author: {
posts: (parent) => {
if(parent.posts) {
return parent.posts;
} else {
return dbAuthor.find({'author.id': parent.id});
}
},
}
The reason I'm resolving thus is to optimize my MongoDB requests by denormalizing my relationships. Here's the objective:
If you need just a list of authors with the titles of their works, all necessary fields are right there in dbAuthors, so no need to look up dbPosts. But if you need more details on each post returned, say, excerpts or slug, you look up dbPosts for the following condition:
{'author.id': parent.id}
But the problem is, if your query looks like this:
authors(id: "3") {
fname
lname
posts {
title
excerpt
}
}
it breaks, because there's no excerpt field returned in the parent object. This problem could be easily fixed if there were some way I could determine what fields are being queried on an author's posts field and then decide if the values returned in parent would suffice. If not, I could then proceed to look up dbPosts with the author's id value. Is it possible? Because if not, it would defeat the whole purpose of denormalizing your collections, something Mongo strongly urges you to do!
It's rather denormalized - data is duplicated ;)
You're probably looking for info.fieldNodes
I'm attempting to use graphql to tie together a number of rest endpoints, and I'm stuck on how to filter, sort and page the resulting data. Specifically, I need to filter and/or sort by nested values.
I cannot do the filtering on the rest endpoints in all cases because they are separate microservices with separate databases. (i.e. I could filter on title in the rest endpoint for articles, but not on author.name). Likewise with sorting. And without filtering and sorting, pagination cannot be done on the rest endpoints either.
To illustrate the problem, and as an attempt at a solution, I've come up with the following using formatResponse in apollo-server, but am wondering if there is a better way.
I've boiled down the solution to the most minimal set of files that i could think of:
data.js represents what would be returned by 2 fictional rest endpoints:
export const Authors = [{ id: 1, name: 'Sam' }, { id: 2, name: 'Pat' }];
export const Articles = [
{ id: 1, title: 'Aardvarks', author: 1 },
{ id: 2, title: 'Emus', author: 2 },
{ id: 3, title: 'Tapir', author: 1 },
]
the schema is defined as:
import _ from 'lodash';
import {
GraphQLSchema,
GraphQLObjectType,
GraphQLList,
GraphQLString,
GraphQLInt,
} from 'graphql';
import {
Articles,
Authors,
} from './data';
const AuthorType = new GraphQLObjectType({
name: 'Author',
fields: {
id: {
type: GraphQLInt,
},
name: {
type: GraphQLString,
}
}
});
const ArticleType = new GraphQLObjectType({
name: 'Article',
fields: {
id: {
type: GraphQLInt,
},
title: {
type: GraphQLString,
},
author: {
type: AuthorType,
resolve(article) {
return _.find(Authors, { id: article.author })
},
}
}
});
const RootType = new GraphQLObjectType({
name: 'Root',
fields: {
articles: {
type: new GraphQLList(ArticleType),
resolve() {
return Articles;
},
}
}
});
export default new GraphQLSchema({
query: RootType,
});
And the main index.js is:
import express from 'express';
import { apolloExpress, graphiqlExpress } from 'apollo-server';
var bodyParser = require('body-parser');
import _ from 'lodash';
import rql from 'rql/query';
import rqlJS from 'rql/js-array';
import schema from './schema';
const PORT = 8888;
var app = express();
function formatResponse(response, { variables }) {
let data = response.data.articles;
// Filter
if ({}.hasOwnProperty.call(variables, 'q')) {
// As an example, use a resource query lib like https://github.com/persvr/rql to do easy filtering
// in production this would have to be tightened up alot
data = rqlJS.query(rql.Query(variables.q), {}, data);
}
// Sort
if ({}.hasOwnProperty.call(variables, 'sort')) {
const sortKey = _.trimStart(variables.sort, '-');
data = _.sortBy(data, (element) => _.at(element, sortKey));
if (variables.sort.charAt(0) === '-') _.reverse(data);
}
// Pagination
if ({}.hasOwnProperty.call(variables, 'offset') && variables.offset > 0) {
data = _.slice(data, variables.offset);
}
if ({}.hasOwnProperty.call(variables, 'limit') && variables.limit > 0) {
data = _.slice(data, 0, variables.limit);
}
return _.assign({}, response, { data: { articles: data }});
}
app.use('/graphql', bodyParser.json(), apolloExpress((req) => {
return {
schema,
formatResponse,
};
}));
app.use('/graphiql', graphiqlExpress({
endpointURL: '/graphql',
}));
app.listen(
PORT,
() => console.log(`GraphQL Server running at http://localhost:${PORT}`)
);
For ease of reference, these files are available at this gist.
With this setup, I can send this query:
{
articles {
id
title
author {
id
name
}
}
}
Along with these variables (It seems like this is not the intended use for the variables, but it was the only way I could get the post processing parameters into the formatResponse function.):
{ "q": "author/name=Sam", "sort": "-id", "offset": 1, "limit": 1 }
and get this response, filtered to where Sam is the author, sorted by id descending, and getting getting the second page where the page size is 1.
{
"data": {
"articles": [
{
"id": 1,
"title": "Aardvarks",
"author": {
"id": 1,
"name": "Sam"
}
}
]
}
}
Or these variables:
{ "sort": "-author.name", "offset": 1 }
For this response, sorted by author name descending and getting all articles except the first.
{
"data": {
"articles": [
{
"id": 1,
"title": "Aardvarks",
"author": {
"id": 1,
"name": "Sam"
}
},
{
"id": 2,
"title": "Emus",
"author": {
"id": 2,
"name": "Pat"
}
}
]
}
}
So, as you can see, I am using the formatResponse function for post processing to do the filtering/paging/sorting. .
So, my questions are:
Is this a valid use case?
Is there a more canonical way to do filtering on deeply nested properties, along with sorting and paging?
Is this a valid use case? Is there a more canonical way to do filtering on deeply nested properties, along with sorting and paging?
Major part of original questing lies on segregating collections on different databases on separate microservices. In fact, it's nessasary to perform collection joining and subsequent filtering on some key, but it's directly impossible since there is no field in original collection to filter, sort or paginate.
Strightforward solution is perform full or filtered queries to original collections, and then perform joining and filtering result dataset on application server, e.g. by lodash, such at your solution. In is possible for small collections, but in general case causes large data transfer and unefficent sorting since there is no index structure - real RB-tree or SkipList, so with quadratic complexity it's not very good.
Dependent on resource volume on application server, special cache and index tables can be build there. If collection structure is fixed, some relations between collection entries and their fields can be reflected in special search table and update respectively on demain. It's like find & search index creation, but not it database, but on application server. Of cource, it will consume resources, but will be more fast than direct lodash-like sorting.
Also task can be solved from another side, if there is access to structure of original databases. Key is denormalization. In counter for classical relation approach, collections can have dublicate information for avioding further join operation. E.g., Articles collection can have some information from Authors collection, which is nessasary to perform filtering, sorting and pagination in further operations.
I'm new to Firebase and I'm building my first app on it so thought I'd ask if my current plans for the app's data structure make sense.
I've read the Firebase blog posts and several answers on SO which have helped me understand the concept of "optimise for the way the data will be read". However, my data will be read in a few different ways and it feels like I may be over complicating things.
Background
The app is like a directory for businesses in multiple towns (schemes) to promote their upcoming events and offers. I think of the data hierarchy like this:
Scheme: A town (the app has multiple schemes)
Category: A group of businesses around a theme (e.g. shoe shops)
Business: An administrative organisation (handles billing etc). Each business can have multiple locations (shops in different towns).
Location: A shop in a town.
Event: Each location can promote events. An event can be promoted at multiple locations but not necessarily all of a business's locations.
Offer: Similar to an event but a different type of object.
Viewing the data
The app user can view the offer & event data in 5 ways:
specific to a business (e.g. Joe's shoes' offers)
for a scheme (e.g. all offers in a Smalltown)
for the whole app (e.g. all offers anywhere)
in a category in a scheme (e.g. all shoe offers in Smalltown)
in a category in the whole app (e.g. all shoe offers anywhere)
In addition, I need to make sure that an administrator from each business can see/edit all of their business's data via a CMS I'm also building.
My approach
This is the data structure I'm thinking of using:
root {
schemes{
scheme1{
name: "smalltown",
logo: "base64 data",
bgcolor: "#FF0000"
},
scheme2{...}
},
businesses{
business1{
name: "Joe's Shoes",
logo: "base64 data",
locations: {
location1: true,
location3: true,
location15: true
},
address_hq: {
street: "45 Acacia Avenue",
town: "Bigtown",
postcode: "BT1 1JS"
},
contact_hq: {
name: "Joe Simpson",
position: "Owner",
email: "joe#joesshoes.com",
tel: "07123 456789"
},
subscription: {
plan: "Standard",
date_start: "10/10/2015",
date_renewal: "10/10/2016"
},
owner: "james1"
},
business2{...}
},
locations{
location1{
name: "Joe's Shoes",
logo: "base64 data",
scheme: "scheme1",
events: {
event1: true,
event27: true
},
offers: {
offer1: true,
offer6: true
},
business: "business1",
owner: "james1"
},
location2{...}
},
events{
event1{
schemes: {
scheme1: true,
scheme4: true
},
locations{
location1: true,
location21: true
},
categories: {
shoes: true,
footwear: true,
fashion: true
},
business: "business1",
date: "5/5/2016",
title: "The History of Shoes",
description: "A fascinating talk about the way shoes have...",
image: "base64 data",
venue: {
street: "Great Hotel",
town: "Bigtown",
postcode: "BT1 1JS"
},
price: "£10"
},
event2{...}
},
offers{
offer1{
schemes: {
scheme1: true,
scheme4: true
},
locations{
location1: true,
location21: true
},
categories: {
shoes: true,
footwear: true,
fashion: true
},
business: "business1",
date_start: "5/5/2016",
date_end: "5/5/2016",
title: "All children's shoes Half Price",
description: "Get 50% off all children's shoes - just in time for the summer",
image: "base64 data",
},
offer2{...}
}
}
Here's a graphic of similar data structure in case it's easier to read:
My question is whether I need to denormalise the data further (repeat more data in more places) or is there a better way to think about this altogether?
It feels like I'm getting potential complications from having to keep data in sync without the ability to simply read from a single place (e.g. I'll need to use queries and indexes (?) to combine location and event data for scheme-wide event listings).
Any advice on making this data structure more efficient would be great.