Apollo with Graphene- Graphql mutations 400 error (not Query, not Mutation django-graphql-jwt, Relay works) - graphql

I am using Apollo v. 2.1, Graphene-django v.2.
Short question: Some mutations (not all) don't work in Apollo, but they do in Relay. Apolloerror?
Apollo Mutations throw error 400. Network Error: Response not successful. POST - Bad Request. Queries and Django-graphene-JWT Mutations (tokenAuth, etc) returns correct result.
import gql from "graphql-tag";
import { ApolloProvider } from 'react-apollo'
import { ApolloClient } from 'apollo-boost'
import { HttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { ApolloLink } from 'apollo-client-preset'
import { onError } from "apollo-link-error"
const token = cookies.get('AUTH_TOKEN')
const authorizationHeader = token ? `JWT ${token}` : null
const httpLink = new HttpLink({
onError: (e) => { console.log(e.graphQLErrors) },
credentials: 'same-origin',
headers: {
'X-CSRFToken': window.csrf, //CSRF-token from Django-template
Authorization: authorizationHeader, // auth-token from cookie
}
})
const client = new ApolloClient({
link: httpLink,
cache: new InMemoryCache(),
connectToDevTools: true,
})
const COUNTRY_MUTATION = gql`
mutation ($country: String) {
createCountry(
country: "Liberland",
){
country {
name
}
}
}
`
client.mutate({
mutation: COUNTRY_MUTATION, variables: {country:"Liberland"}}).then((resp) => {
console.log(resp)
}).catch((error) => {
console.log(error)
});
Django returns no error. Apollo (console) returns error. It is possible to make mutations inside GraphIQL/Insomnia without errors.
I have tried turning on/off Django-url csrf_excempt without change. If csrf_exempt off and I add no X-CSRFToken to index.js I get a 403 error. GraphIql is turned off. Developer window of browser shows return of JSON.
If I do schema.execute in django mutation work normally.
Main Schema:
import graphene
import graphql_jwt
import about.schema
import shop.schema
class Query(shop.schema.Query, graphene.ObjectType):
pass
class Mutation(shop.schema.Mutation, graphene.ObjectType):
token_auth = graphql_jwt.ObtainJSONWebToken.Field()
verify_token = graphql_jwt.Verify.Field()
refresh_token = graphql_jwt.Refresh.Field()
schema = graphene.Schema(query=Query, mutation=Mutation)
Shop Schema:
class CountryType(DjangoObjectType):
class Meta:
model = Country
class CountryInput(graphene.InputObjectType):
name = graphene.String()
class CreateCountry(graphene.Mutation):
country = graphene.Field(CountryType)
class Arguments:
country = graphene.String()
def mutate(self, info, country="LIBERLAND"):
country = Country(
name=country,
)
country.save()
return CreateCountry(
country=country
)
class Query(graphene.ObjectType):
country = graphene.List(CountryType)
def resolve_country(self, info):
return Country.objects.all()
class Mutation(graphene.ObjectType):
create_country = CreateCountry.Field()
I guess the problem is with APOLLO. Same mutations are possible with RELAY Is my apollo-setup wrong?

Related

Vuex-ORM GraphQL installation troubles

I installed the Vuex-ORM Graphql Plugin into an existing Nuxt project with Laravel/GraphQL API, so that I could try avoiding using the Apollo Cache. In one of my components though, I'm running:
<script>
import Notification from '~/data/models/notification';
export default {
computed: {
notifications: () => Notification.all()
},
async mounted () {
await Notification.fetch();
}
}
</script>
however I'm receiving the error [vuex] unknown action type: entities/notifications/fetch.
I looked through the debug log and found several available getters (entities/notifications/query, entities/notifications/all, entities/notifications/find, and entities/notifications/findIn). I tried running await Notification.all() in the mounted method which removed the error, however looking in Vuex the Notifications data object is empty.
Here is the rest of my setup:
nuxt.config.js
plugins: [
'~/plugins/vuex-orm',
'~/plugins/graphql'
],
plugins/vuex-orm.js
import VuexORM from '#vuex-orm/core';
import database from '~/data/database';
export default ({ store }) => {
VuexORM.install(database)(store);
};
plugins/graphql.js
/* eslint-disable import/no-named-as-default-member */
import VuexORM from '#vuex-orm/core';
import VuexORMGraphQL from '#vuex-orm/plugin-graphql';
import { HttpLink } from 'apollo-link-http';
import fetch from 'node-fetch';
import CustomAdapter from '~/data/adapter';
import database from '~/data/database';
// The url can be anything, in this example we use the value from dotenv
export default function ({ app, env }) {
const apolloClient = app?.apolloProvider?.defaultClient;
const options = {
adapter: new CustomAdapter(),
database,
url: env.NUXT_ENV_BACKEND_API_URL,
debug: process.env.NODE_ENV !== 'production'
};
if (apolloClient) {
options.apolloClient = apolloClient;
} else {
options.link = new HttpLink({ uri: options.url, fetch });
}
VuexORM.use(VuexORMGraphQL, options);
};
/data/adapter.js
import { DefaultAdapter, ConnectionMode, ArgumentMode } from '#vuex-orm/plugin-graphql';
export default class CustomAdapter extends DefaultAdapter {
getConnectionMode () {
return ConnectionMode.PLAIN;
}
getArgumentMode () {
return ArgumentMode.LIST;
}
};
/data/database.js
import { Database } from '#vuex-orm/core';
// import models
import Notification from '~/data/models/notification';
import User from '~/data/models/user';
const database = new Database();
database.register(User);
database.register(Notification);
export default database;
/data/models/user.js
import { Model } from '#vuex-orm/core';
import Notification from './notification';
export default class User extends Model {
static entity = 'users';
static eagerLoad = ['notifications'];
static fields () {
return {
id: this.attr(null),
email: this.string(''),
first_name: this.string(''),
last_name: this.string(''),
// relationships
notifications: this.hasMany(Notification, 'user_id')
};
}
};
/data/models/notification.js
import { Model } from '#vuex-orm/core';
import User from './user';
export default class Notification extends Model {
static entity = 'notifications';
static fields () {
return {
id: this.attr(null),
message: this.string(''),
viewed: this.boolean(false),
// relationships
user: this.belongsTo(User, 'user_id')
};
}
};
package.json
"#vuex-orm/plugin-graphql": "^1.0.0-rc.41"
So in a Hail Mary throw to get this working, I ended up making a couple of changes that actually worked!
If other people come across this having similar issues, here's what I did...
In my nuxt.config.js, swapped the order of the two plugins to this:
plugins: [
'~/plugins/graphql',
'~/plugins/vuex-orm',
],
In my graphql.js plugin, I rearranged the order of the options to this (database first, followed by adapter):
const options = {
database,
adapter: new CustomAdapter(),
url: env.NUXT_ENV_BACKEND_API_URL,
debug: process.env.NODE_ENV !== 'production'
};

React Component subscription detect GraphQL server not responding

I have a device that sends a heartbeat to my Apollo GraphQL server every 30 seconds. I have a React component that subscribes to hbReceived, and displays the most recent heartbeat time. This works fine.
BUT,
If my GraphQL server is down, I want to handle that error. I expect these errors to be returned in the useSubscription() hook's return value error.networkError property. Instead, I just see client.ts:545 WebSocket connection to 'ws://localhost:4000/graphql' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED in the console, and the error key remains undefined in the useSubscripiton response.
schema.graphql:
type Heartbeat {
id: ID!
heartbeatTime: DateISO8601!
deviceId: ID!
}
type Subscription {
heartbeatReceived(chargePointInstallId: ID!) : Heartbeat
hbReceived(deviceId: ID!): Heartbeat
}
I made a simple version of my app in create-react-app to illustrate this problem:
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Heartbeat from './Heartbeat';
import { ApolloProvider } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { WebSocketLink } from 'apollo-link-ws';
const link = new WebSocketLink({
uri: 'ws://localhost:4000/graphql',
options: {
reconnect: true,
}
});
const client = new ApolloClient({
link,
cache: new InMemoryCache()
});
ReactDOM.render(
<React.StrictMode>
<ApolloProvider client = {client}>
<Heartbeat deviceId={1} />
</ApolloProvider>
</React.StrictMode>,
document.getElementById('root')
);
src/Heartbeat.js
import React from 'react';
import './App.css';
import { useSubscription } from 'react-apollo';
import gql from 'graphql-tag';
export default function Heartbeat(props) {
const { loading, data, error} = useSubscription(
gql`
subscription hbReceived($deviceId: ID!) {
hbReceived(deviceId: $deviceId) {
heartbeatTime
}
}`,
{ variables:{ deviceId: `${props.deviceId}`}}
);
let mostRecentHeartbeatTimeStr;
if (error) {
console.log('Error rerturned:');
console.log(error);
mostRecentHeartbeatTimeStr = 'See console for error';
} else if (loading) {
mostRecentHeartbeatTimeStr = 'Waiting for first heartbeat';
} else {
const mostRecentHeartbeatDate = new Date(data.heartbeatReceived.heartbeatTime);
mostRecentHeartbeatTimeStr = 'Last Heartbeat: ' + mostRecentHeartbeatDate.toLocaleString('en-AU',{})
}
return (<div className='device'>
<div className='device-heading'>
Device heartbeat:
</div>
<div className='device-row'>
{mostRecentHeartbeatTimeStr}
</div>
</div>)
}
This is what I see in the console when the graphQL server is down:
WebSocket connection to 'ws://localhost:4000/graphql' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED
How do I catch that WebSocket ERR_CONNECTION_REFUSED error and display some nice message to my user?
What I have tried
I have put a connectionCallback in the options for the new WebSocketLink constructor
parameters(url,{ options: { connectionCallback(error) => { console.log(error);} });
I have tried composing a link with an onError from import { onError } from "apollo-link-error"; in it, and put { errorPolicy: 'all' } in my useSubscription call.
The documentation also says the default behaviour is that network errors are treated like GraphQL errors.
I am stuck! Any help appreciated!

Apollo on local client doesn't trigger the local resolvers

Apollo doesn't trigger the resolvers in the case of Local state Client (frontent local state). Apollo 2.7
Does anyone have any idea why it happens?
Here is the setup:
Apollo client
import { ApolloClient } from 'apollo-client'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { HttpLink } from 'apollo-link-http'
import fetch from 'isomorphic-unfetch'
import { resolvers, typeDefs } from './resolvers';
import { initCache } from './init-cache';
export default function createApolloClient(initialState, ctx) {
// The `ctx` (NextPageContext) will only be present on the server.
// use it to extract auth headers (ctx.req) or similar.
return new ApolloClient({
ssrMode: Boolean(ctx),
link: new HttpLink({
uri: 'https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn', // Server URL (must be absolute)
credentials: 'include', // Additional fetch() options like `credentials` or `headers`
fetch,
}),
typeDefs,
resolvers,
connectToDevTools: true,
cache: initCache({
robot: {
__typename: 'Robot',
name: 'Robbie',
status: 'live',
},
member: {
__typename: 'Member',
name: 'RFesagfd',
}
}),
})
}
Types & resolvers (resolvers.js)
import gql from 'graphql-tag';
export const typeDefs = gql`
type Robot {
name: String!
status: String!
}
type Member {
name: String!
isLogged: Boolean!
}
`;
export const resolvers = {
Member: {
isLogged: (...args) => {
console.log('args', args); // THIS NEVER TRIGGERS SOMEHOW
return true;
}
}
};
Query
const GET_IS_MEMBER_LOGGED = gql`
query isMemberLogged {
member #client {
name
isLogged
}
}
`;
Thanks for any help!
You need to define result type of local queries:
const typeDefs = gql`
extend type Query {
robot: Robot
member: Member
}
... and resolver for your query - not type (as you decorated entire query as local)... but you have to return typed data:
export const resolvers = {
Query: {
member: (...args) => {
console.log('args', args);
return {
__typename: 'Member',
name: 'some name', // read from cache
isLogged: true // function result
};
}
}
};
You should also use __typename for cache writes.
update
assuming you have a Memeber in cache ... you can:
// read (initialized with permanent) data:
const memberData = cache.readQuery(....
// f.e. it should have `__typename` and 'name`
// ... and 'decorate' it with derived properites
memberData.age = currentYear - memberData.birthYear;
memberData.isLogged = someFuncReturningBool();
return memberData; // Member type shaped object
It's about shape/data organization - typed (return type shaped object with defined properties) or simple (return all properties separately) or mixed, f.e. (some global app state)
const GET_IS_MEMBER_LOGGED = gql`
query profileViewData {
member #client {
name
isLogged
}
isProfilePanelOpen #client
termsAccepted #client
}
`;
I found a possible solution. Maybe this info will be useful for someone.
If we want to omit the Query Resolver + Field resolvers and we want to have the only Field resolver we need to use #client(always: true).
The in deep explanation
In general, there is a problem with how the Apollo client works with Cache.
By default, it caches the response, and next time it'll fetch the cached result from the cache (eg. optimistic UI). This behavior is the same even in the case of the Client.
It means when we have the initial model in cache Apollo will fetch in from the cache and ignores the resolvers, even if we pass the #client directive.
To solve this problem and let Apollo know that we need to use Local resolvers EVEN if we have a cached object, we need to use #client(always: true) for the preferred field or the whole object. I made an example below.
P.S. Unfortunately I didn't find how to force Apollo to work with non-existing field so if we want to have some resolver for a specific field, we still need to define the initial field value it the initial Cached Model to let the Apollo know about this field. After that, Apollo will use resolver for it to generate some high-calculated output for this particular field, thanks to #client(always: true).
In general, it's ok, because we should know what kind of dynamic field we'll have in our model.
Apollo client
import { ApolloClient } from 'apollo-client'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { HttpLink } from 'apollo-link-http'
import fetch from 'isomorphic-unfetch'
import { resolvers, typeDefs } from './resolvers';
import { initCache } from './init-cache';
export default function createApolloClient(initialState, ctx) {
// The `ctx` (NextPageContext) will only be present on the server.
// use it to extract auth headers (ctx.req) or similar.
return new ApolloClient({
ssrMode: Boolean(ctx),
link: new HttpLink({
uri: 'https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn', // Server URL (must be absolute)
credentials: 'include', // Additional fetch() options like `credentials` or `headers`
fetch,
}),
typeDefs,
resolvers,
connectToDevTools: true,
cache: initCache({
author: {
__typename: 'Author',
posts: 0,
name: '' // NEED TO SET AN INITIAL VALUE
}
})
}
Types & resolvers (resolvers.js)
import gql from 'graphql-tag';
import { print } from 'graphql';
export const typeDefs = gql`
type Author {
posts: Int!
name: String
}
`;
export const resolvers = {
Author: {
name(author) {
console.log('Author name resolver', author). // WORKS
return 'NAME';
},
},
};
Query
const GET_AUTHOR = gql`
query getAuthor {
author {
posts
name #client(always: true)
}
}
`;

admin-on-rest / restClient : call a resource with no auth

I made a register page that use restClient to send a POST to /users api.
But my problem is that the only way to send a POST is to be logged first as I receive this error log from the restClient :
'Could not find stored JWT and no authentication strategy was given'
Is there a way to desactivate the authentication middleware for a specific api call ?
// registerActions.js
import { CREATE } from 'admin-on-rest'
export const USER_REGISTER = 'AOR/USER_REGISTER'
export const USER_REGISTER_LOADING = 'AOR/USER_REGISTER_LOADING'
export const USER_REGISTER_FAILURE = 'AOR/USER_REGISTER_FAILURE'
export const USER_REGISTER_SUCCESS = 'AOR/USER_REGISTER_SUCCESS'
export const userRegister = (data, basePath) => ({
type: USER_REGISTER,
payload: { data: { email: data.username, ...data } },
meta: { resource: 'users', fetch: CREATE, auth: true },
})
//registerSaga.js
import { put, takeEvery, all } from 'redux-saga/effects'
import { push } from 'react-router-redux'
import { showNotification } from 'admin-on-rest'
import {
USER_REGISTER,
USER_REGISTER_LOADING,
USER_REGISTER_SUCCESS,
USER_REGISTER_FAILURE
} from './registerActions'
function* registerSuccess() {
yield put(showNotification('Register approved'))
yield put(push('/'))
}
function* registerFailure({ error }) {
yield put(showNotification('Error: register not approved', 'warning'))
console.error(error)
}
export default function* commentSaga() {
yield all([
takeEvery(USER_REGISTER_SUCCESS, registerSuccess),
takeEvery(USER_REGISTER_FAILURE, registerFailure),
])
}
You'll probably have to make your own feathers client and explicitly bypass the call to authenticate for this specific request
You can also write a rest wrappper this will intercept the call for this particular case and bypass auth
https://marmelab.com/admin-on-rest/RestClients.html#decorating-your-rest-client-example-of-file-upload
So something like below
const restWrapper = requestHandler => (type, resource, params) => {
import { fetchUtils } from 'admin-on-rest';
if (type === 'CREATE' && resource === 'users') {
return fetchUtils.fetchJson(url, params)
.then((response) => {
const {json} = response;
return { data: json };
})
}
Eliminates the need of rewriting an entire Rest Client when you only want to override the default behaviour for a single case

How to pass mocked executable schema to Apollo Client?

The Mocking example for Apollo GraphQL has the following code (see below).
The interesting thing is the last line - they create and execute the graphql query. But you usually need to create ApolloClient object. I can't figure out how to do that.
The ApolloClient expect the NetworkingInterface as an argument not the executable schema.
So, is there a way to create ApolloClient from the executable schema, without NetworkingInterface?
import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools';
import { graphql } from 'graphql';
// Fill this in with the schema string
const schemaString = `...`;
// Make a GraphQL schema with no resolvers
const schema = makeExecutableSchema({ typeDefs: schemaString });
// Add mocks, modifies schema in place
addMockFunctionsToSchema({ schema });
const query = `
query tasksForUser {
user(id: 6) { id, name }
}
`;
graphql(schema, query).then((result) => console.log('Got result', result));
The following is lifted from a docs PR written by magbicaleman on GitHub, based on our blog post:
You can easily do this with the apollo-test-utils, like so:
import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools';
import { mockNetworkInterfaceWithSchema } from 'apollo-test-utils';
import { typeDefs } from './schema';
// Create GraphQL schema object
const schema = makeExecutableSchema({ typeDefs });
// Add mocks
addMockFunctionsToSchema({ schema });
// Create network interface
const mockNetworkInterface = mockNetworkInterfaceWithSchema({ schema });
// Initialize client
const client = new ApolloClient({
networkInterface: mockNetworkInterface,
});
Now you can use the client instance as normal!
In Apollo client v2, networkInterface has been replaced with link for the network layer (see the client docs here).
apollo-test-utils hasn't been updated for Apollo client v2, and based on conversations from github, it seems the current recommendation is to use apollo-link-schema:
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { SchemaLink } from 'apollo-link-schema';
import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools';
import { typeDefs } from './schema';
const schema = makeExecutableSchema({ typeDefs });
addMockFunctionsToSchema({ schema });
const graphqlClient = new ApolloClient({
cache: new InMemoryCache(),
link: new SchemaLink({ schema })
});
Then you just need to inject the client into whatever you're testing!

Resources