Mutating an array with apollo - javascript

I have a server side schema with this mutation type
type Mutation {
updateSettings(settings: SettingsInput): Settings
}
input SettingsInput {
repositories: [RepositoryInput]
}
input RepositoryInput {
id: String
name: String
url: String
}
I can mutate this exactly like I want to if I use a client such as Altair, with this query:
mutation{
updateSettings(settings: {
repositories: [
{
name: "name1"
url: "url1"
},
{
name: "name2"
url: "url2"
}
]
}){
repositories {
id
name
url
}
}
}
However I am struggling to get it working when using Apollo
The best I can get is this
import { SubscriptionClient } from "subscriptions-transport-ws";
import { gql } from "apollo-boost";
import { WebSocketLink } from "apollo-link-ws";
const wsClient = new SubscriptionClient("ws://localhost:5001/graphql", {
reconnect: true
});
const client = new WebSocketLink(wsClient);
const UPDATE_SETTINGS = gql`
mutation UpdateSettings($settings: SettingsInput) {
updateSettings(settings: $settings) {
repositories {
id
name
url
}
}
}
`;
client
.request({
query: UPDATE_SETTINGS,
variables: { repository: [{name: "name1", url:"url1"},
{name: "name2", url:"url2"}]}
})
I am obviously missing something. The client doesn't seem to be aware of the servers SettingsInput, but I can't really figure out how to create a query for the client that takes complex objects or arrays as variables.
Am I going about this in a totaly weird way or how do I go about sending "complex" mutations to the server from an apollo client?

The client aside, you also need to fix the variables object you're passing in. The variable you've defined in your operation is named settings, but you are only passing in a variable named repository. Additionally, the shape of this variable doesn't match SettingsInput as shown in your schema. variables should look something like:
const variables = {
settings: {
repositories: [
{
name: 'name1',
url: 'url1'
},
{
name: 'name2',
url: 'url2'
},
],
},
}

Related

Get localized data from multilingual schema with GraphQL query

I have following MongoDB schema.
Item {
_id: ObjectId,
translations: [{
language: String
name: String
}]
}
So my Item instance could look something like this.
{
_id: ObjectId("5ba3bf09d3121aba3ba2f488"),
translations: [
{
language: "en"
name: "a Car"
},
{
language: "de",
name: "der Wagen"
}]
}
And I want to be able to query my data with specific language with Graphql this way.
{
item(where: {language: "en"}) {
name
}
}
So it would produce nice output with shape like this.
{
name: "a Car"
}
Please can you tell me some good practice or nice way I can setup my Graphql resolvers map?
I'm using Apollo Server.
Thank you very much!
A general solution for a language specific query (with more than one field) could be:
passing the language parameter to the query resolver
store the language on the resolver context
use the language from the context wherever needed
Query:
query {
item(language: "en") {
name
otherField
}
}
Resolver:
{
item: (_, { language }, context) => {
context.language = language;
return {
name: (_, context) => getNameByLang(context.language),
otherField: (_, context) => getOtherByLang(context.language),
};
},
}
Or if there's only one translated field:
query {
item {
name(language: "en")
}
}
so you get the language directly in the name resolver as an argument.
{
item: () => ({
name: ({ language }) => getNameByLang(language),
})
}

apollo-link-state: How to write Query resolvers?

I create my state link with defaults values, something like this:
const stateLink = withClientState({
cache,
resolvers,
defaults: {
quote: {
__typename: 'Quote',
name: '',
phoneNumber: '',
email: '',
items: []
}
}
})
So my cache should not be empty. Now my resolvers map looks like this:
resolvers = {
Mutation: { ... },
Query: {
quote: (parent, args, { cache }) => {
const query = gql`query getQuote {
quote #client {
name phoneNumber email items
}
}`
const { quote } = cache.readQuery({ query, variables: {} })
return ({ ...quote })
}
}
}
The datasource of my resolvers is the cache right ? so I have to query the cache somehow. But this is not working, I guess it is because I am trying to respond to quote query, and for that I am making another quote query.
I think I should get the quote data without querying for quote, but how ?
I am getting this error:
Can't find field **quote** on object (ROOT_QUERY) undefined
Please help
Just wanted to post the same question - and fortunatly just figured it out.
readQuery-Methode only allows you to query from root. So instead you should use readFragment, because it allows you to access any normalized field in the cache, as long you got it's id (Something like this: GraphQlTypeName:0 typically constructed from the fields: id and __typename ). Your Query-Resolver should then look something like this:
protected resolvers = {
Query: {
getProdConfig: (parent, args, { cache, getCacheKey }) => {
const id = getCacheKey({ __typename: 'ProdConfig', id: args.id });
const fragment = gql`fragment prodConfig on ProdConfig {
id,
apiKey,
backupUrl,
serverUrl,
cache,
valid
}`;
const data = cache.readFragment({ fragment, id })
return ({ ...data });
}
}
and the call from apollo like:
let query = this.$apollo.query(`
query prodConfig($id: Int!) {
getProdConfig(id: $id) #client {
apiKey,
backupUrl,
serverUrl,
cache,
valid
}
}`,
{ id: 0 }
);

GraphQL.js - use Interface as a default (fallback) type in resolveType function

I'm trying to return generic type in resolveType function if none of the provided types is matched. The example below shows this problem: API worked like a charm supporting UserType and MovieType until in database was added BookType (not supported by GraphQL schema).
const {
graphql,
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
GraphQLNonNull,
GraphQLList,
GraphQLInterfaceType
} = require("graphql");
const DATA = [
{
// UserType
name: "catherine woolf",
nick: "catherine"
},
{
// MovieType
name: "cat woman",
director: "Jack Wolfgang"
},
{
// --- missing type --- (BookType)
name: "cats secrets",
author: "Nicky Glace"
}
];
const resolveType = data => {
if (data.nick) {
return UserType;
}
if (data.director) {
return MovieType;
}
};
const SearchableType = new GraphQLInterfaceType({
name: "Searchable",
fields: {
name: { type: GraphQLString }
},
resolveType: resolveType
});
const UserType = new GraphQLObjectType({
name: "User",
interfaces: [SearchableType],
fields: {
name: { type: GraphQLString },
nick: { type: GraphQLString }
}
});
const MovieType = new GraphQLObjectType({
name: "Movie",
interfaces: [SearchableType],
fields: {
name: { type: GraphQLString },
director: { type: GraphQLString }
}
});
const schema = new GraphQLSchema({
types: [MovieType, UserType, SearchableType],
query: new GraphQLObjectType({
name: "RootQueryType",
fields: {
search: {
type: new GraphQLList(SearchableType),
args: {
text: { type: new GraphQLNonNull(GraphQLString) }
},
resolve(_, { text }) {
return DATA.filter(d => d.name.indexOf(text) !== -1);
}
}
}
})
});
const query = `
{
search(text: "cat") {
name
... on User {
nick
}
... on Movie {
director
}
}
}
`;
graphql(schema, query).then(result => {
console.log(JSON.stringify(result, null, 2));
});
So now this code ends with error:
"Abstract type Searchable must resolve to an Object type at runtime for field RootQueryType.search with value \"[object Object]\", received \"undefined\". Either the Searchable type should provide a \"resolveType\" function or each possible types should provide an \"isTypeOf\" function."
This is nothing surprising since currently resolveType may not return any type.
Workaround
Crate type containing the same field like interface SearchableType (1 to 1 implementation):
const _SearchableType = new GraphQLObjectType({
name: '_Searchable',
interfaces: [SearchableType],
fields: {
name: { type: GraphQLString },
}
});
Use it as a fallback type:
const resolveType = data => {
if (data.nick) {
return UserType;
}
if (data.director) {
return MovieType;
}
return _SearchableType;
};
And add it to types in schema definition:
types: [MovieType, UserType, SearchableType, _SearchableType],
But the problem with this solution is presence of this dummy _SearchableType in documentation like this:
Question
Is there any way to return interface SearchableType or equivalent of it in resolveType? The key point for me is hiding of this "fallback type" in a documentation.
GraphQL is strongly typed and doesn't support generics or some kind of "fallback" mechanism when resolving unions and interfaces. At the end of the day, if your underlying data layer is returning some type that you have not yet implemented in your schema, the simplest solution is to simply add that type to your schema. Migrations to your database and changes to your schema should go hand-in-hand.
If you want to derive your schema from your storage layer, I would suggest looking into something like PostGraphile (formerly PostGraphQL).
That said, if you're bent on using a workaround, you could just fallback to one of the existing types:
const resolveType = data => {
if (data.nick) {
return UserType
}
return MovieType
}
Now a book's name will still be accessible, provided you query it on the interface and not one of the types. The only downside to this approach is that movie-specific fields will be returned for a book and will resolve to null, but that won't cause any issues unless they're specifically defined as non-null in your schema.

POST request with a JSON object that contains a File

I have a JSON object like so:
const people = {
admin: {
name: 'john',
avatar: {
img: File
}
},
moderator: {
name: 'jake',
avatar: {
img: File
}
}
};
The img property is just a File object.
What I want to do
I want to send this data as a POST request to my node server.
What I've tried:
So one approach that seems to work is to create a FormData object and then manually append each property to it like so:
client.js
let formData = new FormData();
formData.append('admin-name', people.admin.name);
formData.append('admin-avatar', people.admin.avatar.img);
formData.append('moderator-name', people.moderator.name);
formData.append('moderator-avatar', people.moderator.avatar.img);
fetch('/submit', { method: 'POST', body: formData })
server.js
import formidable from 'express-formidable';
router.use('/submit', formidable());
router.post('/submit', (req, res) => {
console.log(req.files); // This contains the Files objects
console.log(req.fields); // This has the rest of the data
res.end();
});
Server Output
{ 'admin-avatar': File {}, 'moderator-avatar': File {} }
{ 'admin-name': 'john', 'moderator-name': 'jake' }
The problem with this approach
The main reason I don't like doing it this way is because I have to manually append every single field. I don't think I can do this in a loop because in my data, some of the fields are nested objects. Also, in my server, the data is no longer grouped together like it was in the original object.
Is there any better way to do this? Thanks!
I don't think there is any clean/elegant way of dynamically creating a FormData object. You could solve the repetitiveness by looping over the properties with Object.keys():
const people = {
admin: {
name: 'john',
avatar: {
img: {}
}
},
moderator: {
name: 'jake',
avatar: {
img: {}
}
}
}
const formData = new FormData()
Object.keys(people)
.forEach(authority => {
const { name, avatar: img } = people[authority]
formData.append(`${authority}-name`, name)
formData.append(`${authority}-avatar`, img)
})

Redux normalizr - nested API responses

How can I use normalizr to deal with nested standardised JSON API responses that are key via the { data: ... } standard?
For example a Book
{
data: {
title: 'Lord of the Rings',
pages: 9250,
publisher: {
data: {
name: 'HarperCollins LLC',
address: 'Big building next to the river',
city: 'Amsterdam'
},
},
author: {
data: {
name: 'J.R.R Tolkien',
country: 'UK',
age: 124,
}
}
}
}
How would I design schemas to deal with the nested data key?
For each entity in your response, you should create it's own schema.In your example, we have three entities - books, authors and publishers:
// schemas.js
import { Schema } from 'normalizr';
const bookSchema = new Schema('book');
const publisherSchema = new Schema('publisher');
const authorSchema = new Schema('author');
If some entity contains nested data which should be normalized, we need to use define method of it schema.This method accepts an object with nesting rules.If we need to normalize publisher and author props of book entity, we should pass an object to define function with same structure as our response:
// schemas.js
bookSchema.define({
data: {
publisher: publisherSchema,
author: authorSchema
}
});
Now we can normalize our response:
import { normalize } from 'normalizr';
import { bookSchema } from './schemas.js';
const response = {
data: {
title: 'Lord of the Rings',
pages: 9250,
publisher: {
data: {
name: 'HarperCollins LLC',
address: 'Big building next to the river',
city: 'Amsterdam'
},
},
author: {
data: {
name: 'J.R.R Tolkien',
country: 'UK',
age: 124,
}
}
}
}
const data = normalize(response, bookSchema);
I believe what you're after is the use of the assignEntity function which can be passed in the options of normalize. In this instance it lets us, where appropriate, filter out the redundant data properties and go straight to the values underneath.
Effectively assignEntity let's you control how each key of data is normalized. Take a look here for a little more on how it works.
I put this together as a demonstration, take a look: http://requirebin.com/?gist=b7d89679202a202d72c7eee24f5408b6. Here's a snippet:
book.define({
data: {
publisher: publisher,
author: author,
characters: normalizr.arrayOf(character)
}}
);
publisher.define({
data: {
country: country
}
});
const result = normalizr.normalize(response, book, { assignEntity: function (output, key, value, input) {
if (key === 'data') {
Object.keys(value).forEach(function(d){
output[d] = value[d];
})
} else {
output[key] = value;
}
}});
Also see in particular Ln 29, where the array of characters has some objects with the information nested within data and some without. All are normalized correctly.
I also added some parts to show how it works with arrays and deeply nested data, see the country model within publisher.
With the data provided you will need a slug due to the absence of id's, which each schema also contains in the example.
Normalizr is fantastic, I hope that helps explain a little more about it :)

Categories