I am running into a POST http://localhost:4000/ 400 (Bad Request) Error.
I am trying to create a new user with the following frontend.
const REGISTER_USER = gql`
mutation Mutation(
$createUser: CreateUserInput!
) {
createUser(createUserInput: $createUserInput){
email
name
token
password
}
}
`
const Register = () => {
const context = useContext(AuthContext)
let navigate = useNavigate()
const [errors, setErrors] = useState([])
function registerUserCallback() {
console.log("Callback hit")
registerUser()
}
const {onChange, onSubmit, values} = useForm(registerUserCallback, {
name: '',
email: '',
password:'',
confirmPassword: '',
})
const [registerUser, {loading}] = useMutation(REGISTER_USER, {
update(proxy, {data: {registerUser: userData}}) {
context.login(userData)
navigate('/Dashboard')
},
onError({graphQLErrors}) {
setErrors(graphQLErrors)
console.log("Error: " + graphQLErrors)
console.log(graphQLErrors)
},
variables: {createUserInput: values}
})
However, the grapQLErrors is not even being console.logged for some reason. When I run the Mutation via Apollo Studio it works. Any information would be great!
Edit: Network Tab Screenshot:
Adding Code for my httpLink:
import { ApolloClient, InMemoryCache, createHttpLink } from "#apollo/client";
import { setContext } from "#apollo/client/link/context";
const httpLink = createHttpLink({
uri: 'http://localhost:4000'
})
const authLink = setContext((_, {headers}) => {
return {
headers: {
...headers,
authorization: localStorage.getItem('token') || ""
}
}
})
export const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});
Edit: createUser Mutation seems to be the issue. This is the Network response error: ["GraphQLError: Unknown argument "createUserInput" on field "Mutation.createUser".","
#Mutation((returns) => User)
async createUser(#Arg('data') data:CreateUserInput, #Ctx() ctx: Context) {
const oldUser = await ctx.prisma.user.findFirst({ where: { email: data.email}})
if(oldUser) {
throw new ApolloError('A user is already registered with the email' + data.email, 'USER_ALREADY_EXISTS')
}
var encryptedPassword = await bcrypt.hash(data.password, 10)
const newUser = await ctx.prisma.user.create({
data: {
name: data.name,
email: data.email,
password: encryptedPassword
}
})
return {token: jwt.sign(newUser, 'supersecret')}
}
Here is a screen shot of my Preview in my Network...I really don't get it.
export class CreateUserInput {
#Field((type) => String)
name: string
#Field((type) => String)
email: string
#Field((type) => String)
password: string
Related
I have been working on this for 3 days now and keep getting stuck. I am trying to register a new user and then have it push to a new page after registration.
The error that I am stuck on is the error above. Here is my frontend code.
const REGISTER_USER = gql`
mutation createUser($input: CreateUserInput!) {
createUser(input: $input) {
email
name
password
}
}
`
const Register = () => {
const context = useContext(AuthContext)
let navigate = useNavigate()
const [errors, setErrors] = useState([])
function registerUserCallback() {
console.log("Callback hit")
registerUser()
}
const {onChange, onSubmit, values} = useForm(registerUserCallback, {
name: '',
email: '',
password:'',
})
const [registerUser, {loading}] = useMutation(REGISTER_USER, {
update(proxy, {data: {createUser: userData}}) {
context.login(userData)
navigate('/Dashboard')
},
onError({graphQLErrors}) {
setErrors(graphQLErrors)
console.log("Error: " + graphQLErrors)
},
variables: {CreateUserInput: values}
})
Any information would be great!
I've been working on a way to set up authentication and authorization for my NextJS app, so far it was pretty easy but I've hit a wall.
I have a value that lives and is watched on a context, and I have a HOC that I need for my NextJS app to be able to use hooks with GraphQl, the issues is that I don't think I can call the context and use the value from a HOC, since it is simply not allowed.
Is there a way I can dynamically change the value on the HOC so that when the user logs in, I can then update the HOC to have the proper access token?
Some context: the user is first anonymous, whenever he/she logs in, I get an auth state change from Firebase from which I can extract the access token and add it to any future requests. But the point of the hoc is to provide next with full Graphql capabilities, the thing is that I need that hoc go listen for changes on a context state.
This is the Connection Builder:
import {
ApolloClient,
InMemoryCache,
HttpLink,
NormalizedCacheObject,
} from "#apollo/client";
import { WebSocketLink } from "#apollo/client/link/ws";
import { SubscriptionClient } from "subscriptions-transport-ws";
const connectionString = process.env.HASURA_GRAPHQL_API_URL || "";
const createHttpLink = (authState: string, authToken: string) => {
const isIn = authState === "in";
const httpLink = new HttpLink({
uri: `https${connectionString}`,
headers: {
// "X-hasura-admin-secret": `https${connectionString}`,
lang: "en",
"content-type": "application/json",
Authorization: isIn && `Bearer ${authToken}`,
},
});
return httpLink;
};
const createWSLink = (authState: string, authToken: string) => {
const isIn = authState === "in";
return new WebSocketLink(
new SubscriptionClient(`wss${connectionString}`, {
lazy: true,
reconnect: true,
connectionParams: async () => {
return {
headers: {
// "X-hasura-admin-secret": process.env.HASURA_GRAPHQL_ADMIN_SECRET,
lang: "en",
"content-type": "application/json",
Authorization: isIn && `Bearer ${authToken}`,
},
};
},
})
);
};
export default function createApolloClient(
initialState: NormalizedCacheObject,
authState: string,
authToken: string
) {
const ssrMode = typeof window === "undefined";
let link;
if (ssrMode) {
link = createHttpLink(authState, authToken);
} else {
link = createWSLink(authState, authToken);
}
return new ApolloClient({
ssrMode,
link,
cache: new InMemoryCache().restore(initialState),
});
}
This is the context:
import { useState, useEffect, createContext, useContext } from "react";
import { getDatabase, ref, set, onValue } from "firebase/database";
import { useFirebase } from "./use-firebase";
import { useGetUser } from "../hooks/use-get-user";
import { getUser_Users_by_pk } from "../types/generated/getUser";
import { getApp } from "firebase/app";
const FirebaseAuthContext = createContext<FirebaseAuthContextProps>({
authUser: null,
authState: "",
authToken: null,
currentUser: undefined,
loading: true,
login: () => Promise.resolve(undefined),
registerUser: () => Promise.resolve(undefined),
loginWithGoogle: () => Promise.resolve(undefined),
loginWithMicrosoft: () => Promise.resolve(undefined),
});
export const FirebaseAuthContextProvider: React.FC = ({ children }) => {
const [loading, setLoading] = useState<boolean>(true);
const [authUser, setAuthUser] = useState<User | null>(null);
const { data } = useGetUser(authUser?.uid || "");
const [authState, setAuthState] = useState("loading");
const [authToken, setAuthToken] = useState<string | null>(null);
const currentUser = data?.Users_by_pk;
// ...
const authStateChanged = async (user: User | null) => {
if (!user) {
setAuthUser(null);
setLoading(false);
setAuthState("out");
return;
}
const token = await user.getIdToken();
const idTokenResult = await user.getIdTokenResult();
const hasuraClaim = idTokenResult.claims["https://hasura.io/jwt/claims"];
if (hasuraClaim) {
setAuthState("in");
setAuthToken(token);
setAuthUser(user);
} else {
// Check if refresh is required.
const metadataRef = ref(
getDatabase(getApp()),
"metadata/" + user.uid + "/refreshTime"
);
onValue(metadataRef, async (data) => {
if (!data.exists) return;
const token = await user.getIdToken(true);
setAuthState("in");
setAuthUser(user);
setAuthToken(token);
});
}
};
useEffect(() => {
const unsubscribe = getAuth().onAuthStateChanged(authStateChanged);
return () => unsubscribe();
}, []);
const contextValue: FirebaseAuthContextProps = {
authUser,
authState,
authToken,
currentUser,
loading,
login,
registerUser,
loginWithGoogle,
loginWithMicrosoft,
};
return (
<FirebaseAuthContext.Provider value={contextValue}>
{children}
</FirebaseAuthContext.Provider>
);
};
export const useFirebaseAuth = () =>
useContext<FirebaseAuthContextProps>(FirebaseAuthContext);
This is the HOC:
export const withApollo =
({ ssr = true } = {}) =>
(PageComponent: NextComponentType<NextPageContext, any, {}>) => {
const WithApollo = ({
apolloClient,
apolloState,
...pageProps
}: {
apolloClient: ApolloClient<NormalizedCacheObject>;
apolloState: NormalizedCacheObject;
}) => {
let client;
if (apolloClient) {
// Happens on: getDataFromTree & next.js ssr
client = apolloClient;
} else {
// Happens on: next.js csr
// client = initApolloClient(apolloState, undefined);
client = initApolloClient(apolloState);
}
return (
<ApolloProvider client={client}>
<PageComponent {...pageProps} />
</ApolloProvider>
);
};
const initApolloClient = (initialState: NormalizedCacheObject) => {
// Make sure to create a new client for every server-side request so that data
// isn't shared between connections (which would be bad)
if (typeof window === "undefined") {
return createApolloClient(initialState, "", "");
}
// Reuse client on the client-side
if (!globalApolloClient) {
globalApolloClient = createApolloClient(initialState, "", "");
}
return globalApolloClient;
};
I fixed it by using this whenever I have an update on the token:
import { setContext } from "#apollo/client/link/context";
const authStateChanged = async (user: User | null) => {
if (!user) {
setAuthUser(null);
setLoading(false);
setAuthState("out");
return;
}
setAuthUser(user);
const token = await user.getIdToken();
const idTokenResult = await user.getIdTokenResult();
const hasuraClaim = idTokenResult.claims["hasura"];
if (hasuraClaim) {
setAuthState("in");
setAuthToken(token);
// THIS IS THE FIX
setContext(() => ({
headers: { Authorization: `Bearer ${token}` },
}));
} else {
// Check if refresh is required.
const metadataRef = ref(
getDatabase(getApp()),
"metadata/" + user.uid + "/refreshTime"
);
onValue(metadataRef, async (data) => {
if (!data.exists) return;
const token = await user.getIdToken(true);
setAuthState("in");
setAuthToken(token);
// THIS IS THE FIX
setContext(() => ({
headers: { Authorization: `Bearer ${token}` },
}));
});
}
};
I have two AWS stacks :
one has a dynamoDB table and "exports" (to appConfig) the tableArn, tableName and tableRoleArn (which ideally should allow access to the table).
import { App, Stack, StackProps } from '#aws-cdk/core';
import * as dynamodb from '#aws-cdk/aws-dynamodb';
import * as cdk from '#aws-cdk/core';
import * as appconfig from '#aws-cdk/aws-appconfig';
import { Effect, PolicyStatement, Role, ServicePrincipal } from '#aws-cdk/aws-iam';
export class ExportingStack extends Stack {
constructor(scope: App, id: string, props: StackProps) {
super(scope, id, props);
const table = new dynamodb.Table(this, id, {
billingMode: dynamodb.BillingMode.PROVISIONED,
readCapacity: 1,
writeCapacity: 1,
removalPolicy: cdk.RemovalPolicy.DESTROY,
partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
sortKey: { name: 'createdAt', type: dynamodb.AttributeType.NUMBER },
pointInTimeRecovery: true
});
const tablePolicy = new PolicyStatement({
effect: Effect.ALLOW,
resources: [table.tableArn],
actions: ['*']
});
const role = new Role(this, 'tableRoleArn', {
assumedBy: new ServicePrincipal('lambda.amazonaws.com')
});
role.addToPolicy(
tablePolicy
);
const app = '***';
const environment = '***';
const profile = '***';
const strategy = 'v';
const newConfig = new appconfig.CfnHostedConfigurationVersion(this, 'ConfigurationName', {
applicationId: app,
configurationProfileId: profile,
contentType: 'application/json',
content: JSON.stringify({
tableArn: table.tableArn,
tableName: table.tableName,
tableRoleArn: role.roleArn
}),
description: 'table config'
});
const cfnDeployment = new appconfig.CfnDeployment(this, 'MyCfnDeployment', {
applicationId: app,
configurationProfileId: profile,
environmentId: environment,
configurationVersion: newConfig.ref,
deploymentStrategyId: strategy
});
}
}
The second has a function which I would like to be able to use the appConfig configuration to dynamically access the table.
import { App, CfnOutput, Stack, StackProps } from '#aws-cdk/core';
import { LayerVersion, Runtime } from '#aws-cdk/aws-lambda';
import { NodejsFunction } from '#aws-cdk/aws-lambda-nodejs';
import { Effect, PolicyStatement } from '#aws-cdk/aws-iam';
export class ConsumingStack extends Stack {
constructor(scope: App, id: string, props: StackProps) {
super(scope, id, props);
const fn = new NodejsFunction(this, 'foo', {
runtime: Runtime.NODEJS_12_X,
handler: 'foo',
entry: `stack/foo.ts`
});
fn.addToRolePolicy(
new PolicyStatement({
effect: Effect.ALLOW,
resources: ['*'],
actions: [
'ssm:*',
'appconfig:*',
'sts:*',
]
})
);
new CfnOutput(this, 'functionArn', { value: fn.functionArn});
// https://docs.aws.amazon.com/appconfig/latest/userguide/appconfig-integration-lambda-extensions.html
// https://github.com/aws-samples/aws-appconfig-codepipeline-cdk/blob/main/infrastructure/src/main/kotlin/com/app/config/ServerlessAppStack.kt
const appConfigLayer = LayerVersion.fromLayerVersionArn(
this,
'appconfigLayer',
'arn:aws:lambda:eu-west-2:282860088358:layer:AWS-AppConfig-Extension:47'
);
fn.addLayers(appConfigLayer);
}
}
and handler
import type { Context } from 'aws-lambda';
import fetch from 'node-fetch';
import { DynamoDB, STS } from 'aws-sdk';
import { Agent } from 'https';
export const foo = async (event: any, lambdaContext: Context): Promise<void> => {
const application = '*****';
const environment = '*****';
const configuration = '*****';
const response = await fetch(
`http://localhost:2772/applications/${application}/environments/${environment}/configurations/${configuration}`
);
const configurationData = await response.json();
console.log(configurationData);
const credentials = await assumeRole(configurationData.tableRoleArn);
const db = new DynamoDB({
credentials: {
sessionToken: credentials.sessionToken,
secretAccessKey: credentials.secretAccessKey,
accessKeyId: credentials.accessKeyId
},
apiVersion: '2012-08-10',
region: '*****',
httpOptions: {
agent: new Agent({ keepAlive: true }),
connectTimeout: 1000,
timeout: 5000
},
signatureVersion: 'v4',
maxRetries: 3
});
const item = await db
.getItem({ TableName: configurationData.tableName, Key: { id: { S: 'coolPeople' }, createdAt: { N: '0' } } }, (e) => {
console.log('e', e);
})
.promise();
console.log('item:', item?.Item?.value?.L);
};
/**
* Assume Role for cross account operations
*/
export const assumeRole = async (tableRoleArn: string): Promise<any> => {
let params = {
RoleArn: tableRoleArn,
RoleSessionName: 'RoleSessionName12345'
};
console.info('Assuming Role with params:', params);
let sts = new STS();
return new Promise((resolve, reject) => {
sts.assumeRole(params, (error, data) => {
if (error) {
console.log(`Could not assume role, error : ${JSON.stringify(error)}`);
reject({
statusCode: 400,
message: error['message']
});
} else {
console.log(`Successfully Assumed Role details data=${JSON.stringify(data)}`);
resolve({
statusCode: 200,
body: data
});
}
});
});
};
The issue is that I get this error when trying to assumeRole within the lambda.
Could not assume role, error : {"message":"User: arn:aws:sts::****:assumed-role/ConsumingStack-fooServiceRole****-***/ConsumingStack-foo****-*** is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::****:role/ExportingStack-tableRoleArn****-***","code":"AccessDenied","time":"2022-02-21T16:06:44.474Z","requestId":"****-***-****-****","statusCode":403,"retryable":false,"retryDelay":26.827985116659757}
So is it possible for a Lambda to dynamically assume a role to access a table from a different stack?
I've got it working by changing the trust relationship of the table role to be arn:aws:iam::${Stack.of(this).account}:root
import { App, Stack, StackProps } from '#aws-cdk/core';
import * as dynamodb from '#aws-cdk/aws-dynamodb';
import * as cdk from '#aws-cdk/core';
import * as appconfig from '#aws-cdk/aws-appconfig';
import { Effect, PolicyStatement, Role, ArnPrincipal } from '#aws-cdk/aws-iam';
export class ExportingStack extends Stack {
constructor(scope: App, id: string, props: StackProps) {
super(scope, id, props);
const table = new dynamodb.Table(this, id, {
billingMode: dynamodb.BillingMode.PROVISIONED,
readCapacity: 1,
writeCapacity: 1,
removalPolicy: cdk.RemovalPolicy.DESTROY,
partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
sortKey: { name: 'createdAt', type: dynamodb.AttributeType.NUMBER },
pointInTimeRecovery: true
});
const tablePolicy = new PolicyStatement({
effect: Effect.ALLOW,
resources: [table.tableArn],
actions: ['*']
});
const role = new Role(this, 'tableRoleArn', {
assumedBy: new ArnPrincipal(`arn:aws:iam::${Stack.of(this).account}:root`)
});
role.addToPolicy(tablePolicy);
const app = '***';
const environment = '***';
const profile = '****';
const strategy = '****';
const newConfig = new appconfig.CfnHostedConfigurationVersion(this, 'myConfiguration', {
applicationId: app,
configurationProfileId: profile,
contentType: 'application/json',
content: JSON.stringify({
tableArn: table.tableArn,
tableName: table.tableName,
tableRoleArn: role.roleArn
}),
description: 'table config'
});
const cfnDeployment = new appconfig.CfnDeployment(this, 'MyCfnDeployment', {
applicationId: app,
configurationProfileId: profile,
environmentId: environment,
configurationVersion: newConfig.ref,
deploymentStrategyId: strategy
});
}
}
I'm sitting for 2 days trying to get my setup right. My target is Next.js with ApolloGraphQl for Database request as well as local state management handling.
My problem is the setup for the local state management. Don't know why i'm unable to correctly get it to work. I hope you can help me out.
I pushed my actual status of the project on github using the following repo:
https://github.com/Maetes/apollo-next-local
UPDATE:
it is fine to query and mutate the state when i just call the cache object itself so all is fine. But when i try to trigger a resolver, nothing happens. I'ts like the Apollo-client does not see the typedefs and resolvers.
Here is my useApollo Hook with Client init:
import { IncomingMessage, ServerResponse } from 'http';
import { useMemo } from 'react';
import { ApolloClient } from 'apollo-client';
import { InMemoryCache, NormalizedCacheObject } from 'apollo-cache-inmemory';
import { resolvers } from './localResolvers';
import { typeDefs } from './localTypeDefs';
import {
CREATE_USER,
GET_CURRENT_USER,
SET_CURRENT_USER,
ALL_USERS,
} from './localDocuments';
let apolloClient: ApolloClient<NormalizedCacheObject> | undefined;
export type ResolverContext = {
req?: IncomingMessage;
res?: ServerResponse;
};
function createIsomorphLink(context: ResolverContext = {}) {
if (typeof window === 'undefined') {
const { SchemaLink } = require('apollo-link-schema');
const { schema } = require('../pages/api/index');
return new SchemaLink({ schema, context });
} else {
const { HttpLink } = require('apollo-link-http');
return new HttpLink({
uri: 'http://localhost:3000/api',
credentials: 'same-origin',
});
}
}
function createApolloClient(context?: ResolverContext) {
let cache = new InMemoryCache();
cache.writeData({
data: {
currentUser: {
__typename: 'CurrentUser',
email: '',
nachname: '',
title: '',
id: '',
},
},
});
let client = new ApolloClient({
ssrMode: typeof window === 'undefined',
link: createIsomorphLink(context),
cache: cache,
typeDefs,
resolvers,
});
// client.writeData({
// data: {
// currentUser: {
// __typename: 'CurrentUser',
// email: '',
// nachname: '',
// title: '',
// id: '',
// },
// },
// });
return client;
}
export function initializeApollo(
initialState: any = null,
// Pages with Next.js data fetching methods, like `getStaticProps`, can send
// a custom context which will be used by `SchemaLink` to server render pages
context?: ResolverContext
) {
const _apolloClient = apolloClient ?? createApolloClient(context);
// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// get hydrated here
if (initialState) {
_apolloClient.cache.restore(initialState);
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === 'undefined') return _apolloClient;
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient;
return _apolloClient;
}
export function useApollo(initialState: any) {
const store = useMemo(() => initializeApollo(initialState), [initialState]);
return store;
}
This is my typedef file:
import gql from 'graphql-tag';
export const typeDefs = gql`
extend type Query {
getCurrentUser: CurrentUser!
}
type CurrentUser {
email: String!
nachname: String!
title: String!
id: ID!
__typename: String!
}
extend type Mutation {
setCurrentUser(
email: String!
nachname: String!
title: String!
id: ID!
): CurrentUser!
}
`;
this are my resolvers:
import { InMemoryCache } from 'apollo-cache-inmemory';
import { GET_CURRENT_USER } from './localDocuments';
export const resolvers = {
CurrentUser: {
currentUser: (_: {}, { cache }: { cache: InMemoryCache }) => {
console.log('Resolver Query triggered');
// const { user }: any = cache.readQuery({
// query: GET_CURRENT_USER,
// });
const data: any = {};
data.__typename = 'CurrentUser';
return data;
},
getCurrentUser: (_: {}, { cache }: { cache: InMemoryCache }) => {
console.log('Resolver Query triggered');
// const { user }: any = cache.readQuery({
// query: GET_CURRENT_USER,
// });
const data: any = {};
data.__typename = 'CurrentUser';
return data;
},
},
Query: {
getCurrentUser: (_: {}, { cache }: { cache: InMemoryCache }) => {
console.log('Resolver Query triggered');
// const { user }: any = cache.readQuery({
// query: GET_CURRENT_USER,
// });
const data: any = {};
data.__typename = 'CurrentUser';
return data;
},
currentUser: (_: {}, { cache }: { cache: InMemoryCache }) => {
console.log('Resolver Query triggered');
// const { user }: any = cache.readQuery({
// query: GET_CURRENT_USER,
// });
const data: any = {};
data.__typename = 'CurrentUser';
return data;
},
},
Mutation: {
setCurrentUser: (
_: {},
variables: any,
{ cache }: { cache: InMemoryCache }
) => {
const { currentUser }: any = cache.readQuery({ query: GET_CURRENT_USER });
console.log('mutationResolver Triggered');
console.log(
'olduser',
currentUser.email,
currentUser.nachname,
currentUser.title
);
const newUser = {
email: variables.email,
title: variables.title,
nachname: variables.nachname,
__typename: 'CurrentUser',
};
console.log('newUser', newUser);
const erg = cache.writeQuery({
query: GET_CURRENT_USER,
data: newUser,
});
return newUser;
},
},
};
And finaly my Document file for individual input. Please note in the GetCurrentUserQuery query when i change "getCurrentUser" to "currentUser" all is working fine cause apollo targets the raw object itself.
import gql from 'graphql-tag';
//Server
export const ALL_USERS = gql`
query allUsersQuery {
allUsers {
title
nachname
email
id
__typename
}
}
`;
//client
export const GET_CURRENT_USER = gql`
query GetCurrentUserQuery {
getCurrentUser #client(always: true) {
email
nachname
title
__typename
}
}
`;
//Client
export const SET_CURRENT_USER = gql`
mutation SetCurrentUserMutation(
$email: String!
$nachname: String!
$title: String!
$id: String!
) {
setCurrentUser(email: $email, nachname: $nachname, title: $title) #client {
email
title
nachname
id
__typename
}
}
`;
//Server
export const CREATE_USER = gql`
mutation CreateUserMutation(
$email: String!
$nachname: String!
$title: String!
$password: String!
) {
createUser(
email: $email
nachname: $nachname
title: $title
password: $password
) {
__typename
email
nachname
title
password
id
}
}
`;
I am trying to use REST endpoints to post data and GraphQL for query and fetch along with apollo-link-state. My rest endpoint is getting hit and application is getting created. But when I try to run the query to write to cache it's not hitting the graphql endpoint. and I keep getting the following error:
Unhandled Rejection (Error): Can't find field findApplicationByUuid({"uuid":"912dc46d-2ef8-4a77-91bc-fec421f5e4bc"}) on object (ROOT_QUERY) {
"application": {
"type": "id",
"id": "$ROOT_QUERY.application",
"generated": true
}
}.
Here are my GQL query
import gql from 'graphql-tag';
const START_APP = gql`
mutation startApp($type: String!) {
application: startApp( input: { applicationType: $type})
#rest(type: "Application", path: "v1/member/application/create", method: "POST") {
uuid: applicationUuid
}
}
`;
const GET_APP = gql`
query getAppByUuid($uuid: String!) {
application: findApplicationByUuid(uuid: $uuid) {
uuid,
type,
version,
}
}
`;
export {
START_APP,
GET_APP,
};
Here is my resolver:
import { START_APP, GET_APP } from './application'
import client from '../apolloClient';
const startApp = async (_, { type }, { cache }) => {
client.mutate({
variables: { type },
mutation: START_APP,
}).then(({ data: { application } }) => {
const { uuid } = application;
const { data } = cache.readQuery({
query: GET_APP,
variables: { uuid },
});
cache.writeQuery({
query: GET_APP,
data,
});
});
};
const resolvers = {
Mutation: {
startApp,
},
};
Here are my links:
import { resolvers, defaults } from './resolvers';
const cache = new InMemoryCache();
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.map(({ message, locations, path }) =>
console.log(`[GQL Error]: Msg: ${message}, Loc: ${locations}, Path: ${path}`));
if (networkError) console.log(`[Network error]: ${networkError}`);
});
const stateLink = withClientState({
cache,
defaults,
resolvers,
});
const restLink = new RestLink({
uri: 'http://localhost:7010/api/',
credentials: 'include',
});
const batchHttpLink = new BatchHttpLink({
uri: 'http://localhost:7010/api/graphql',
credentials: 'include',
});
const httpLink = new HttpLink({
uri: 'http://loaclhost:7010/api/graphql',
credentials: 'include',
});
const link = ApolloLink.from([
errorLink,
stateLink,
restLink,
httpLink,
]);
my client
const client = new ApolloClient({
link,
cache,
});
My react component:
// Remote Mutation
const START_APP = gql`
mutation startApp($type: String!) {
startApp(type: $type) #client {
uuid
}
}
`;
const StartApp = ({ match }) => {
const { type } = match.params;
return (
<Mutation mutation={START_APP} variables={{ type }}>
{startApp => (<button onClick={startApp}>click me</button>)}
</Mutation>
)
};
When I hit the button it calls create endpoint and creates the app and returns the uuid. But the following I want to happen is hit the graphql endpoint and query for the application using the uuid returned from the rest request, and write that data to the cache/state.