Unknown type "Upload" in Apollo Server 2.6 - node.js

I want to upload a file through GraphQL, and followed this article.
Here's the my schema:
extend type Mutation {
bannerAdd(
title: String!
image: Upload
): ID
}
However when I run the app, this gives me this error:
Unknown type "Upload". Did you mean "Float"?
Followed above article, Apollo Server will automatically generate Upload scalar, but why this is happening?
Also define Upload scalar manually also not working:
scalar Upload
...
Gives me this error:
Error: There can be only one type named "Upload".
Seems nothing wrong with my code. Is there an anything that I missed? Using Node#10.14.2, Apollo Server#2.6.1, Apollo Server Express#2.6.1 and polka#0.5.2.
Any advice will very appreciate it.

Fix this problem with GraphQLUpload of Apollo Server for create a custom scalar called FileUpload.
Server setup with Apollo Server:
const {ApolloServer, gql, GraphQLUpload} = require('apollo-server');
const typeDefs = gql`
scalar FileUpload
type File {
filename: String!
mimetype: String!
encoding: String!
}
type Query {
uploads: [File]
}
type Mutation {
singleUpload(file: FileUpload!): File!
}
`;
const resolvers = {
FileUpload: GraphQLUpload,
Query: {
uploads: (parent, args) => {},
},
Mutation: {
singleUpload: async (_, {file}) => {
const {createReadStream, filename, mimetype, encoding} = await file;
const stream = createReadStream();
// Rest of your code: validate file, save in your DB and static storage
return {filename, mimetype, encoding};
},
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
server.listen().then(({url}) => {
console.log(`🚀 Server ready at ${url}`);
});
Client Setup with Apollo Client and React.js:
You need to install the apollo-upload-client package too.
import React from 'react';
import ReactDOM from 'react-dom';
import { ApolloClient, InMemoryCache, ApolloProvider, gql, useMutation } from '#apollo/client';
import { createUploadLink } from 'apollo-upload-client';
const httpLink = createUploadLink({
uri: 'http://localhost:4000'
});
const client = new ApolloClient({
link: httpLink,
cache: new InMemoryCache()
});
const UPLOAD_FILE = gql`
mutation uploadFile($file: FileUpload!) {
singleUpload(file: $file) {
filename
mimetype
encoding
}
}
`;
function FileInput() {
const [uploadFile] = useMutation(UPLOAD_FILE);
return (
<input
type="file"
required
onChange={({target: {validity, files: [file]}}) =>
validity.valid && uploadFile({variables: {file}})
}
/>
);
}
function App() {
return (
<ApolloProvider client={client}>
<div>
<FileInput/>
</div>
</ApolloProvider>
);
}
ReactDOM.render(
<React.StrictMode>
<App/>
</React.StrictMode>,
document.getElementById('root')
);

Here's the solution what I did, adding custom scalar named "FileUpload" and add GraphQLUpload as resolver like this:
import { GraphQLUpload } from 'graphql-upload';
export const resolvers = {
FileUpload: GraphQLUpload
};
It works great, but it could be not perfect solution. Hope apollo fix this soon.
P.S. To upload file from your browser, you also need to set upload link in Apollo Client properly. Here's my code:
import { ApolloLink, split } from 'apollo-link';
import { createHttpLink } from 'apollo-link-http';
import { createUploadLink } from 'apollo-upload-client';
// Create HTTP Link
const httpLink = createHttpLink({
uri: ...,
credentials: 'include'
});
// Create File Upload Link
const isFile = value =>
(typeof File !== 'undefined' && value instanceof File) || (typeof Blob !== 'undefined' && value instanceof Blob);
const isUpload = ({ variables }) => Object.values(variables).some(isFile);
const uploadLink = createUploadLink({
uri: ...
credentials: 'include'
});
const terminatingLink = (isUpload, uploadLink, httpLink);
const link = ApolloLink.from([<Some Other Link...>, <Another Other Link...>, terminatingLink]);
const apolloClient = new ApolloClient({
link,
...
});

This issue can be caused by passing an executable schema (schema option) when initializing your server instead of the newer API of passing typeDefs and resolvers separately.
Old:
const server = new ApolloServer({
schema: makeExecutableSchema({ typeDefs, resolvers })
})
New:
const server = new ApolloServer({
typeDefs,
resolvers,
})
Or as explained in the docs:
Note: When using typeDefs, Apollo Server adds scalar Upload to your schema, so any existing declaration of scalar Upload in the type definitions should be removed. If you create your schema with makeExecutableSchema and pass it to ApolloServer constructor using the schema param, make sure to include scalar Upload.

Related

How to get query result from postgraphile running as a library

I have postgraphile running as an express middleware. For example:
const pgMiddleware = postgraphile(pool, SCHEMA, postgraphileConfig);
app.use(pgMiddleware);
How to get or intercept the result of a query or mutation without having a separate client?
For example when I send the below query
query {
personById(id: 1){
firstname
}
}
I want to be able to get the data sent back inside the same express app. How can I do that?
I believe what you are asking for is to be able to execute GraphQL operations against a PostGraphile schema from other routes/middlewares in Express without needing to make additional http requests. This is called schema only usage and you will specifically want to use withPostGraphileContext to execute your request and process results:
import type { Express } from "express";
import type { Pool } from "pg";
import {
gql,
makeProcessSchemaPlugin,
postgraphile,
withPostGraphileContext,
} from "postgraphile";
import PgSimplifyInflectorPlugin from "#graphile-contrib/pg-simplify-inflector";
import type { GraphQLSchema } from "graphql";
import { graphql } from "graphql";
// Register your middlewares with express
const schemaOnlyUsageApp = (app: Express, pool: Pool) => {
let schema: GraphQLSchema;
// This plugin will execute a callback each time the PostGraphile
// GraphQl schema is rebuit.
const schemaProcessorPlugin = makeProcessSchemaPlugin((newSchema) => {
schema = newSchema;
return schema;
});
// Register the PostGraphile middleware as normal for requests on /graphql (and /graphiql)
app.use(
postgraphile(pool, "my_schema", {
simpleCollections: "omit",
dynamicJson: true,
legacyRelations: "omit",
setofFunctionsContainNulls: false,
appendPlugins: [PgSimplifyInflectorPlugin, schemaProcessorPlugin],
watchPg: true,
graphiql: true,
enhanceGraphiql: true,
showErrorStack: true,
allowExplain: true,
})
);
// custom route that will execute a predefined gql query directly against the schema
app.get("/posts", async (req, res) => {
// arbitrary gql query
const query = gql`
query posts {
posts {
edges {
node {
id
title
body
likeCount
createdAt
}
}
}
}
`;
const result = await withPostGraphileContext(
{
// Reuse your pool to avoid creating additional connections
pgPool: pool,
},
async (context) => {
// execute your query directly and get results without making
// an additional http request!
const queryResult = await graphql({
schema,
source: query.loc?.source || "",
contextValue: { ...context },
});
return queryResult;
}
);
res.send(result);
});
};
export default schemaOnlyUsageApp;

How to add query params to context of apollo-server-express

I'm using apollo-server-express as so right now:
import { ApolloServer } from 'apollo-server-express';
import { WebApp } from 'meteor/webapp';
import { getUser } from 'meteor/apollo';
import schema from './api';
const server = new ApolloServer({
schema,
context: async ({ req }) => {
const user = await getUser(req.headers.authorization);
return {
user,
};
},
playground: process.env.NODE_ENV === 'development',
introspection: true,
uploads: false,
});
server.applyMiddleware({
app: WebApp.connectHandlers,
path: '/graphql',
});
I'd like to add another field to the context which has all the query params from the URL my user is visiting.
It seems that the req object passed to the context function by apollo-server-express is of type express.Request which ought to have a req.query object. However, when I try to access that like so:
context: async ({ req }) => {
console.log('### query', req.query);
console.log('### params', req.params);
const user = await getUser(req.headers.authorization);
return {
user,
};
},
and visit my app at http://localhost:3000/u/3q2PcjRwyiqR2ywHM/BK2CiG7fN3P7Z5xvy?editToken=qdj3RRYjCuMxFNRnz (note the ?editToken=...)
I see the following log lines:
I20220417-15:08:54.477(-5)? ### query {}
I20220417-15:08:54.478(-5)? ### params undefined
I20220417-15:08:54.570(-5)? ### query {}
I20220417-15:08:54.571(-5)? ### params undefine
What is the correct way to access URL query params when creating the context for apollo-server-express? I'm specifically trying to add the editToken to the context.

Apollo subscriptions - Nextjs - Error: Observable cancelled prematurely at Concast.removeObserver

I am trying to use apollo/graphql subscription in my nextjs project, my graphql server is placed in external nextjs service,I can work with queries and mutation without any problem but when I use an implementation of useSubscription I get the following error:
"Error: Observable cancelled prematurely
at Concast.removeObserver (webpack-internal:///../../node_modules/#apollo/client/utilities/observables/Concast.js:118:33)
at eval (webpack-internal:///../../node_modules/#apollo/client/utilities/observables/Concast.js:21:47)
at cleanupSubscription (webpack-internal:///../../node_modules/zen-observable-ts/module.js:92:7)
at Subscription.unsubscribe (webpack-internal:///../../node_modules/zen-observable-ts/module.js:207:7)
at cleanupSubscription (webpack-internal:///../../node_modules/zen-observable-ts/module.js:97:21)
at Subscription.unsubscribe (webpack-internal:///../../node_modules/zen-observable-ts/module.js:207:7)
at eval (webpack-internal:///../../node_modules/#apollo/client/react/hooks/useSubscription.js:106:26)
at safelyCallDestroy (webpack-internal:///../../node_modules/react-dom/cjs/react-dom.development.js:22763:5)
at commitHookEffectListUnmount (webpack-internal:///../../node_modules/react-dom/cjs/react-dom.development.js:22927:11)
at invokePassiveEffectUnmountInDEV (webpack-internal:///../../node_modules/react-dom/cjs/react-dom.development.js:24998:13)
at invokeEffectsInDev (webpack-internal:///../../node_modules/react-dom/cjs/react-dom.development.js:27137:11)
at commitDoubleInvokeEffectsInDEV (webpack-internal:///../../node_modules/react-dom/cjs/react-dom.development.js:27110:7)
at flushPassiveEffectsImpl (webpack-internal:///../../node_modules/react-dom/cjs/react-dom.development.js:26860:5)
at flushPassiveEffects (webpack-internal:///../../node_modules/react-dom/cjs/react-dom.development.js:26796:14)
at eval (webpack-internal:///../../node_modules/react-dom/cjs/react-dom.development.js:26592:9)
at workLoop (webpack-internal:///../../node_modules/scheduler/cjs/scheduler.development.js:266:34)
at flushWork (webpack-internal:///../../node_modules/scheduler/cjs/scheduler.development.js:239:14)
at MessagePort.performWorkUntilDeadline (webpack-internal:///../../node_modules/scheduler/cjs/scheduler.development.js:533:21)"
I know that the subscriptions server is working right because I can to listening from apollo studio and I have created a spa with create-react-app and it works fine
I have used:
Server:
"apollo-server-express": "^3.6.7"
"graphql-ws": "^5.7.0"
Client
"next": "^12.1.5"
"#apollo/client": "^3.5.10"
"graphql-ws": "^5.7.0"
Hook implementation
const room = useSubscription(
gql`
subscription onRoomAdded($roomAddedId: ID!) {
roomAdded(id: $roomAddedId) {
id
name
}
}
`
);
Client implementation
import { ApolloClient, HttpLink, InMemoryCache, split } from '#apollo/client';
import { GraphQLWsLink } from '#apollo/client/link/subscriptions';
import { getMainDefinition } from '#apollo/client/utilities';
import { createClient } from 'graphql-ws';
import fetch from 'isomorphic-fetch';
const HOST = 'http://localhost:3001/graphql';
const HOST_WS = 'ws://localhost:3001/graphql';
const isServer = typeof window === 'undefined';
if (isServer) {
global.fetch = fetch;
}
const httpLink = new HttpLink({
uri: HOST,
});
const link = isServer
? httpLink
: split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
new GraphQLWsLink(
createClient({
url: HOST_WS,
})
),
httpLink
);
const client = new ApolloClient({
ssrMode: isServer,
link,
cache: new InMemoryCache(),
});
export default client;
any idea about the problem? I think the problem could be that NextJS only works with subscriptions-transport-ws but in the official apollo documentation indicates that the new official way is to use graphql-ws the other library is unmaintained already
UPDATE!
I have checked that the subscriptions are working right in production build, I'm investigating how to implement in development process. any suggestions are welcome.
If it is working in production, but in not in dev, you may have the same issue I had with my React SPA: StrictMode and double rendering as described in this github issue.
So far I have found 2 ways to make it work:
remove StrictMode
subscribe with vanilla JS instead ofuseSubscription
const ON_USER_ADDED = gql`
subscription OnUserAdded {
userAdded {
name
id
}
}
`;
const subscribe = () => {
client.subscribe({
query: ON_USER_ADDED,
}).subscribe({
next(data) {
console.log('data', data);
},
complete(){
console.log('complete');
},
error(err) {
console.log('error', err);
}
})
};

405 Method Not Allowed when trying to connect apollo-angular client to graphql-server

Im trying to connect my angular-Apollo Client to my graphql backend and I Keep getting a 405 error. My Server is hosted on localhost port 4000 and my Client is also on localhost (port 4200). For my client i used the Basic Setup as shown in the Apollo docs and made a few Adjustments to it. If I look at the request in the browser I can see that Apollo is using the Method Options. As far as I graphql Server doesnt support the Options method. So how can I get my client to use the get or post method or allow the Options method?
My client scrpit:
import {Component, OnInit} from '#angular/core';
import {Apollo} from 'apollo-angular';
import gql from 'graphql-tag';
#Component({
selector: 'app-patient',
template: `
<div *ngIf="loading">
Loading...
</div>
<div *ngIf="error">
Error :(
</div>
<div *ngIf="allPatients">
<div *ngFor="let patient of allPatients">
<p>{{patient.id}}</p>
</div>
</div>
`,
})
export class PatientComponent implements OnInit {
allPatients: any[];
loading = true;
error: any;
constructor(private apollo: Apollo) {}
ngOnInit() {
this.apollo
.watchQuery<any>({
query: gql`
{
allPatients {
id
}
}
`,
})
.valueChanges.subscribe(result => {
this.allPatients = result.data && result.data.allPatients;
this.loading = result.loading;
this.error = result.errors;
});
}
}
And here is my server scrpit file:
var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');
// Construct a schema, using GraphQL schema language
var schema = buildSchema(`
type Patient {
id: String
}
type Query {
allPatients: [Patient]
}
`);
// The root provides a resolver function for each API endpoint
var root = {
allPatients: () => {
return [{id: "testID1"}, {id: "testID2"}];
},
};
var graphQLApp = express();
graphQLApp.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true,
}));
graphQLApp.listen(4000);
console.log('Running a GraphQL API server at http://localhost:4000/graphql');

Need to find the error with connecting subscription with schema stitching

I am using apollo-server-express for graphql back-end. I am going to process only mutations there, but I want to redirect query and subscription on hasura by means of schema stitching with introspection. Queries through apollo-server to hasura are working fine and returning the expected data.
But subscriptions are not working and I am getting this error: " Expected Iterable, but did not find one for field subscription_root.users".
And besides, server hasura is receiving events:
But apollo-server resents the answer from hasura. It is not the first day I suffer with this and I can not understand what the problem is.
In the editor hasura subscriptions work.
Link to full code
If you need any additional info, I will gladly provide it to you.
import {
introspectSchema,
makeExecutableSchema,
makeRemoteExecutableSchema,
mergeSchemas,
transformSchema,
FilterRootFields
} from 'graphql-tools';
import { HttpLink } from 'apollo-link-http';
import nodeFetch from 'node-fetch';
import { resolvers } from './resolvers';
import { hasRoleResolver } from './directives';
import { typeDefs } from './types';
import { WebSocketLink } from 'apollo-link-ws';
import { split } from 'apollo-link';
import { getMainDefinition } from 'apollo-utilities';
import { SubscriptionClient } from 'subscriptions-transport-ws';
import * as ws from 'ws';
import { OperationTypeNode } from 'graphql';
interface IDefinitionsParams {
operation?: OperationTypeNode,
kind: 'OperationDefinition' | 'FragmentDefinition'
}
const wsurl = 'ws://graphql-engine:8080/v1alpha1/graphql';
const getWsClient = function (wsurl: string) {
const client = new SubscriptionClient(wsurl, {
reconnect: true,
lazy: true
}, ws);
return client;
};
const wsLink = new WebSocketLink(getWsClient(wsurl));
const createRemoteSchema = async () => {
const httpLink = new HttpLink({
uri: 'http://graphql-engine:8080/v1alpha1/graphql',
fetch: (nodeFetch as any)
});
const link = split(
({ query }) => {
const { kind, operation }: IDefinitionsParams = getMainDefinition(query);
console.log('kind = ', kind, 'operation = ', operation);
return kind === 'OperationDefinition' && operation === 'subscription';
},
wsLink,
httpLink,
);
const remoteSchema = await introspectSchema(link);
const remoteExecutableSchema = makeRemoteExecutableSchema({
link,
schema: remoteSchema
});
const renamedSchema = transformSchema(
remoteExecutableSchema,
[
new FilterRootFields((operation, fieldName) => {
return (operation === 'Mutation') ? false : true; // && fieldName === 'password'
})
]
);
return renamedSchema;
};
export const createNewSchema = async () => {
const hasuraExecutableSchema = await createRemoteSchema();
const apolloSchema = makeExecutableSchema({
typeDefs,
resolvers,
directiveResolvers: {
hasRole: hasRoleResolver
}
});
return mergeSchemas({
schemas: [
hasuraExecutableSchema,
apolloSchema
]
});
};
Fixed by installing graphql-tools 4th version. It tutns out the editor did not even notice that I do not have this dependency and simply took the version of node_modules, which was installed by some other package. Problem was with version 3.x. Pull request is where the bug was fixed.
I had the same problem, different cause and solution.
My subscription was working well, until I introduced the 'resolve' key in
my subscription resolver:
Here is the 'Subscription' part of My resolver:
Subscription: {
mySubName: {
resolve: (payload) => {
console.log('In mySubName resolver, payload:',payload)
return payload;
},
subscribe:() => pubSub.asyncIterator(['requestsIncomplete']),
// )
},
The console.log proved the resolve() function was being called with a well structured payload (shaped the same as my Schema definiton - specifically the an object with a key named after the graphQL Subscriber, pointing to an array (array is an iterable):
In mySubName resolver, payload: { mySubName:
[ { id: 41,
...,
},
{...},
{...}
...
...
]
Even though I was returning that same unadulterated object, it caused the error expected Iterable, but did not find one for field "Subscription.mySubName"
When I commented out that resolve function all together, the subscription worked, which is further evidence that my payload was well structured, with the right key pointing to an iterable.
I must be mis-using the resolve field. From https://www.apollographql.com/docs/graphql-subscriptions/subscriptions-to-schema/
When using subscribe field, it's also possible to manipulate the event
payload before running it through the GraphQL execution engine.
Add resolve method near your subscribe and change the payload as you wish
so I am not sure how to properly use that function, specifically don't know what shape object to return from it, but using it as above breaks the subscription in the same manner you describe in your question.
I was already using graphql-tools 4.0.0, I upgraded to 4.0.8 but it made no difference.

Resources