I need to POST a large payload in a GraphQL mutation. How do I increase the body size limit of Apollo Server?
I'm using apollo-server-express version 2.9.3.
My code (simplified):
const myGraphQLSchema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
user: UserQuery,
},
}),
mutation: new GraphQLObjectType({
name: 'Mutation',
fields: () => ({
...UserMutations,
}),
}),
});
const apolloServer = new ApolloServer(schema: myGraphQLSchema);
const app = express();
app.use(apolloServer.getMiddleware({ path: '/graphql' });
Not exactly sure in which version it was added, but on 2.9.15 you can apply it in applyMiddleware function.
const apolloServer = new ApolloServer(someConfig);
apolloServer.applyMiddleware({
app,
cors: {
origin: true,
credentials: true,
},
bodyParserConfig: {
limit:"10mb"
}
});
Simply add an Express body parser before your Apollo server middleware:
import { json } from 'express';
app.use(json({ limit: '2mb' });
app.use(apolloServer.getMiddleware({ path: '/graphql' });
If you want to get fancy, you can have a separate body size limit for authenticated vs unauthenticated requests:
const jsonParsers = [
json({ limit: '16kb' }),
json({ limit: '2mb' }),
];
function parseJsonSmart(req: Request, res: Response, next: NextFunction) {
// How exactly you do auth depends on your app
const isAuthenticated = req.context.isAuthenticated();
return jsonParsers[isAuthenticated ? 1 : 0](req, res, next);
}
app.use(parseJsonSmart);
app.use(apolloServer.getMiddleware({ path: '/graphql' });
Related
I've got a graphql URI that I need to query from my react-native App. This URI is public and I've got access to its schema/structure when I simply type the URI in my browser.
As soon as I try to query it from my code, I get the [TypeError: Network request failed] error (logs are created in the function that builds my ApolloClient).
I've checked the URI a million time, it's the same as the one I put in my browser, and the one I've used in the past to successfully query the DB.
This is the client-building function:
export function initServices({
uri,
authToken,
mockMeanDelay = 400,
mock = false,
mockScenarios = [],
}: Options): Services {
let mockRemoteController = null;
let linkToOutsideWorld: ApolloLink;
const messageBus = createMessageBus();
const terminatingLink = createUploadLink({
uri: CORRECT_URI_HERE,
})
const authLink = setContext(async (_, { headers }) => {
const token = await authToken();
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : '',
},
};
});
linkToOutsideWorld = from([authLink, withCustomScalars(), terminatingLink]);
const errorReportingLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.forEach(({ message, locations, path }) =>
// eslint-disable-next-line no-console
console.error(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
),
);
// eslint-disable-next-line no-console
if (networkError) console.error(`[Network error]: ${networkError}`);
});
const link = ApolloLink.from([errorReportingLink, linkToOutsideWorld]);
const fragmentMatcher = new IntrospectionFragmentMatcher({
// #ts-ignore
introspectionQueryResultData: introspectionResult,
});
const apolloClient = new ApolloClient({
link,
defaultOptions: {
watchQuery: {
// We prefer using a `cache-and-network` policy so that screens
// are always in sync with backend
// Otherwise, the default policy would not fetch the server
// data from server if the result of query is already in cache
fetchPolicy: 'cache-and-network',
},
},
cache: new InMemoryCache({
cacheRedirects: {
Query: {
// #ts-ignore issue in typing of cacheRedirects
userById: (_, { userId }: QueryUserByIdArgs, { getCacheKey }) =>
getCacheKey({ __typename: 'User', id: userId }),
// #ts-ignore issue in typing of cacheRedirects
gatheringSpaceById: (
_,
{ gatheringSpaceId }: QueryGatheringSpaceByIdArgs,
{ getCacheKey },
) =>
getCacheKey({
__typename: 'GatheringSpace',
id: gatheringSpaceId,
}),
// #ts-ignore issue in typing of cacheRedirects
gatheringInstanceById: (
_,
{ gatheringInstanceId }: QueryGatheringInstanceByIdArgs,
{ getCacheKey },
) =>
getCacheKey({
__typename: 'GatheringInstance',
id: gatheringInstanceId,
}),
},
},
fragmentMatcher,
}),
});
return { apolloClient, messageBus, mockRemoteController };
}
When I replace the URI with another publicly available one, it seems to work so my guess is that there's an issue with the back-end side. But how is it possible that I have full access to the schema and queries with my browser?
Any tips to help debugging are welcome too!
Thanks for your help!
I've managed to have a express + Apollo Backend as a serverMiddleware in Nuxtjs.
Everything works fine(auth, cache, datasources, queries, mutations) but now I'm trying to get subscriptions(websockets) running and its giving me a hard time.
I tried this example https://www.apollographql.com/docs/apollo-server/data/subscriptions/#subscriptions-with-additional-middleware but even letting the httpServer listening didn't work.
This is my API file which I require through the nuxt.config.js with '~/api/index' :
module.exports = async () => {
const app = require('express')()
const server = await require("./apollo")() // apollo-server-express w/ typeDefs and resolvers
// apply Apollo to Express
server.applyMiddleware({ app });
console.log(`🚀 ApolloServer ready at ${server.graphqlPath}`);
const httpServer = http.createServer(app);
server.installSubscriptionHandlers(httpServer);
console.log(`🚀 ApolloSubscriptions ready at ${server.subscriptionsPath}`);
return {
path: '/api',
handler: httpServer
}
}
Now my playground is giving me this error: "Could not connect to websocket endpoint ws://192.168.150.98:3000/api/graphql. Please check if the endpoint url is correct."
TypeDefs:
type Subscription {
postAdded: Post
}
type Post {
author: String
comment: String
}
type Query {
posts: [Post]
}
type Mutation {
addPost(author: String, comment: String): Post
}
Resolvers:
Query: {
posts(root, args, context) {
return Posts;
}
}
Mutation: {
addPost(root, args, context) {
pubsub.publish(POST_ADDED, { postAdded: args });
return Posts.add(args);
}
},
Subscription: {
postAdded: {
// Additional event labels can be passed to asyncIterator creation
subscribe: () => pubsub.asyncIterator([POST_ADDED]),
},
}
First question here, thank u in advance! :)
it can also be a little easier
1.
yarn add apollo-server-express
or
npm install apollo-server-express
create file ./server/index.js
import { ApolloServer, gql } from 'apollo-server-express'
// Construct a schema, using GraphQL schema language
const typeDefs = gql`
type Query {
hello: String
}
`
// Provide resolver functions for your schema fields
const resolvers = {
Query: {
hello: () => 'Hello world!',
},
}
const server = new ApolloServer({ typeDefs, resolvers })
export default server
add in your nuxt.config.js
import server from './server'
export default {
// ... your nuxt config stuff
// ...
hooks: {
render: {
async before({
nuxt: {
server: { app },
},
}) {
await server.applyMiddleware({ app, path: '/api' })
console.log(`🚀 ApolloServer ready at /api`)
},
},
}
}
I found a hacky way to achieve it, import the code as a nuxt module:
import http from 'http'
export default function () {
this.nuxt.hook('render:before', async () => {
const server = require("./apollo")()
// apply Apollo to Express
server.applyMiddleware({ app: this.nuxt.renderer.app });
console.log(`🚀 ApolloServer ready at ${server.graphqlPath}`);
const httpServer = http.createServer(this.nuxt.renderer.app);
// apply SubscriptionHandlers to httpServer
server.installSubscriptionHandlers(httpServer);
console.log(`🚀 ApolloSubscriptions ready at ${server.subscriptionsPath}`);
// overwrite nuxt.server.listen()
this.nuxt.server.listen = (port, host) => new Promise(resolve => httpServer.listen(port || 3000, host || 'localhost', resolve))
// close this httpServer on 'close' event
this.nuxt.hook('close', () => new Promise(httpServer.close))
})
}
Tho I'm now using a probably more stable way, using nuxt programmatically!
With hapi instead of express, since express is giving me trouble compiling and not showing the loading-screen(progress of building).
Just use npx create-nuxt-app and create an app with a hapi server backend.
The code with hapi would look like this:
const consola = require('consola')
const Hapi = require('#hapi/hapi')
const HapiNuxt = require('#nuxtjs/hapi')
async function start () {
const server = require('./apollo/index')()
const app = new Hapi.Server({
host: process.env.HOST || '127.0.0.1',
port: process.env.PORT || 3000
})
await app.register({
plugin: HapiNuxt
})
app.route(await require('./routes')())
await server.applyMiddleware({
app,
path: '/graphql'
});
console.log(`🚀 ApolloServer ready at ${server.graphqlPath}`);
await server.installSubscriptionHandlers(app.listener)
console.log(`🚀 ApolloSubscriptions ready at ${server.subscriptionsPath}`);
await app.start()
consola.ready({
message: `Server running at: ${app.info.uri}`,
badge: true
})
}
process.on('unhandledRejection', error => consola.error(error))
start().catch(error => console.log(error))
Maybe i can help somebody
An easier way is to use the getMiddleware() method of Apollo Server Express:
Create a file under ./api/index.js:
const { ApolloServer, gql } = require('apollo-server-express')
const express = require('express')
const typeDefs = gql`
type Query {
hello: String
}
`
const resolvers = {
Query: {
hello: () => 'Hello world!',
},
}
const server = new ApolloServer({ typeDefs, resolvers })
const app = express()
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
app.use(server.getMiddleware())
module.exports = app
and then register it in ./nuxt.config.js:
{
// other nuxt config ...
serverMiddleware: [{ path: '/api', handler: '~/api/index.js' }],
}
I've been trying to send a cookie back to the client from the server. I get the response data but i don't see "set-cookie" in the response headers
My Apollo Server Configuration:
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req, connection, res }) => ({
dummyModels: dummyModels,
models: models,
req,
connection,
res,
currentUser: dummyModels.users[2],
dummyUsers: dummyModels.dummyUsers,
}),
});
app.use(cors({
credentials: true,
origin: 'http://localhost:3000',
// preflightContinue: true,
}));
My resolver:
login: async (parent, args, context) => {
const _include_headers = function(body, response, resolveWithFullResponse) {
return {'headers': response.headers, 'data': body};
};
const loginRequestOptions = {
method: 'POST',
uri: 'http://localhost:3000/incorta/authservice/login',
qs: {
// access_token: 'xxxxx xxxxx', // -> uri + '?access_token=xxxxx%20xxxxx'
user: args.input.username,
pass: args.input.password,
tenant: args.input.tenantName,
},
transform: _include_headers,
json: true // Automatically parses the JSON string in the response
};
const loginResponse = await request(loginRequestOptions);
console.log(loginResponse);
context.res.cookie(
'JSESSIONID',
tough.Cookie.parse(loginResponse.headers['set-cookie'][0]).value,
{
// expires : new Date(Date.now() + 9999999),
// path: '/incorta/',
// HttpOnly: false,
// maxAge: 1000 * 60 * 60 * 24 * 99, // 99 days
},
);
context.res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
return loginResponse.data;
},
Note: i'm using request-promise-native to make the request
My Apollo Client Configuration:
const httpLink = createHttpLink({
uri: 'http://172.16.16.130:4000/graphql',
credentials: 'include',
fetchOptions: {
credentials: 'include',
},
});
const wsLink = new WebSocketLink({
uri: 'ws://172.16.16.130:4000/graphql',
options: {
reconnect: true,
connectionParams: {
headers: {
'x-user-header': localStorage.getItem('userObject'),
},
},
}
});
const terminatingLink = split(
// split based on operation type
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription';
},
wsLink,
httpLink,
);
const link = ApolloLink.from([terminatingLink]);
const cache = new InMemoryCache();
export const client = new ApolloClient({
link,
cache,
});
I have tried tinkering with options. i don't know what i'm missing here.
You can use the apollo-server-plugin-http-headers package for setting cookies in apollo server.
Usage is as simple as this from within your resolvers:
context.setCookies.push({
name: "cookieName",
value: "cookieContent",
options: {
domain: "example.com",
expires: new Date("2021-01-01T00:00:00"),
httpOnly: true,
maxAge: 3600,
path: "/",
sameSite: true,
secure: true
}
});
My frontend is localhost:3000, and my GraphQL server is localhost:3333.
I've used react-apollo to query/mutate in JSX land, but haven't made a query/mutation from Express yet.
I'd like to make the query/mutation here in my server.js.
server.get('/auth/github/callback', (req, res) => {
// send GraphQL mutation to add new user
});
Below seems like the right direction, but I'm getting TypeError: ApolloClient is not a constructor:
const express = require('express');
const next = require('next');
const ApolloClient = require('apollo-boost');
const gql = require('graphql-tag');
// setup
const client = new ApolloClient({
uri: 'http://localhost:3333/graphql'
});
const app = next({dev});
const handle = app.getRequestHandler();
app
.prepare()
.then(() => {
const server = express();
server.get('/auth/github/callback', (req, res) => {
// GraphQL mutation
client.query({
query: gql`
mutation ADD_GITHUB_USER {
signInUpGithub(
email: "email#address.com"
githubAccount: "githubusername"
githubToken: "89qwrui234nf0"
) {
id
email
githubToken
githubAccount
}
}
`,
})
.then(data => console.log(data))
.catch(error => console.error(error));
});
server.listen(3333, err => {
if (err) throw err;
console.log(`Ready on http://localhost:3333`);
});
})
.catch(ex => {
console.error(ex.stack);
process.exit(1);
});
This post mentions Apollo as the solution, but doesn't give an example.
How do I call a GraphQL mutation from Express server :3000 to GraphQL :3333?
This is more likely to be what you're looking for:
const { createApolloFetch } = require('apollo-fetch');
const fetch = createApolloFetch({
uri: 'https://1jzxrj179.lp.gql.zone/graphql',
});
// Example # 01
fetch({
query: '{ posts { title } }',
}).then(res => {
console.log(res.data);
});
// Example # 02
// You can also easily pass variables for dynamic arguments
fetch({
query: `
query PostsForAuthor($id: Int!) {
author(id: $id) {
firstName
posts {
title
votes
}
}
}
`,
variables: { id: 1 },
}).then(res => {
console.log(res.data);
});
Taken from this post, might be helpful to others as well: https://www.apollographql.com/blog/graphql/examples/4-simple-ways-to-call-a-graphql-api/
You can use graphql-request, it is a simple GraphQL client.
const { request } = require('graphql-request');
request('http://localhost:3333/graphql', `mutation ADD_USER($email: String!, $password: String!) {
createUser(email: $email, password: $password) {
id
email
}
}`, {email: 'john.doe#mail.com', password: 'Pa$$w0rd'})
.then(data => console.info(data))
.catch(error => console.error(error));
It also support CORS.
const { GraphQLClient } = require('graphql-request');
const endpoint = 'http://localhost:3333/graphql';
const client = new GraphQLClient(endpoint, {
credentials: 'include',
mode: 'cors'
});
client.request(`mutation ADD_USER($email: String!, $password: String!) {
createUser(email: $email, password: $password) {
id
email
}
}`, {email: 'john.doe#mail.com', password: 'Pa$$w0rd'})
.then(data => console.info(data))
.catch(error => console.error(error));
I use it to make E2E tests.
As you are getting ApolloClient with require instead of import I think you are missing this part:
// es5 or Node.js
const Boost = require('apollo-boost');
const ApolloClient = Boost.DefaultClient;
or
const ApolloBoost = require('apollo-boost');
const ApolloClient = ApolloBoost.default;
Try one of those and see if it works.
I'd like to add one more way to query from express.
This is what I ended up with.
install required packages
npm install graphql graphql-tag isomorphic-fetch
write graphql on separate file (myQuery.js)
const gql = require('graphql-tag');
const query = gql`
query($foo: String) {
// Graphql query
}
}
Main file
const { print } = require('graphql/language/printer');
const query = require('./myQuery');
require('isomorphic-fetch');
// other logic
const foo = "bar"
const token = "abcdef"
await fetch('https://example.com/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'authorization': `Bearer ${token}`,
},
body: JSON.stringify({
query: `${print(query)}`,
variables: { foo },
}),
})
I have a GraphQL endpoint:
app.use('/graphql', graphqlHTTP(request => ({
graphiql: true,
schema
})));
I also have a Passport route for logging in (and handling the callback, since I'm using Google OAuth2):
this.app.get('/login', passport.authenticate('google'));
this.app.get('/auth/callback/google', ....
Passport add a user to the request, and all of the articles I can find online recommend authenticating in each of my GraphQL resolvers using that:
resolve: (root, args, { user }) => {
if (!user) throw new NotLoggedInError();
However it doesn't make sense to have to add that logic to every resolver when it applies to all of them, so I was hoping to somehow authenticate the entire endpoint.
The problem is that I'm not sure how to combine middleware. I tried the following but it just broke the endpoint:
app.use('/graphql', passport.authenticate('google'), graphqlHTTP(request => ({
graphiql: true,
schema
})));
I have the following working. Some issues I had were around making sure my google API was enabled and the proper scopes were enabled. I am also only using the passport middleware on the auth endpoints and using an isAuthenticated middleware to check if the session is authenticated and if not redirect to the auth endpoint. also putting the request object into the context so that it can be used by the resolver to potentially authorize the user. You would of course need to update the user lookup as I am just passing mock data.
import express from "express";
import graphqlHTTP from "express-graphql";
import passport from "passport";
import cookieParser from "cookie-parser";
import session from "express-session";
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
import { buildSchema } from "graphql";
const PORT = 5000;
const data = [
{ id: "1", name: "foo1" },
{ id: "2", name: "foo2" },
{ id: "3", name: "foo3" },
];
const def = `
type Foo {
id: String!
name: String
}
type Query {
readFoo(id: String!): Foo
}
schema {
query: Query
}
`;
const schema = buildSchema(def);
const fieldMap = schema.getType("Query").getFields();
fieldMap.readFoo.resolve = (source, args) => {
return data.filter(({ id }) => id === args.id)[0] || null;
};
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((obj, done) => {
done(null, obj);
});
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: `http://localhost:${PORT}/auth/google/callback`,
},
(accessToken, refreshToken, profile, cb) => {
return cb(null, {
id: "1",
username: "foo#bar.baz",
googleId: profile.id,
});
}
)
);
function isAuthenticated(req, res, next) {
return req.isAuthenticated() ? next() : res.redirect("/auth/google");
}
const app = express();
app.use(cookieParser());
app.use(
session({
secret: "sauce",
resave: false,
saveUninitialized: false,
})
);
app.use(passport.initialize());
app.use(passport.session());
app.get("/auth/fail", (req, res) => {
res.json({ loginFailed: true });
});
app.get(
"/auth/google",
passport.authenticate("google", { scope: ["profile"] })
);
app.get(
"/auth/google/callback",
passport.authenticate("google", { failureRedirect: "/auth/fail" }),
(req, res) => {
res.redirect("/graphql");
}
);
app.use(
"/graphql",
isAuthenticated,
graphqlHTTP((req) => ({
schema,
graphiql: true,
context: req,
}))
);
app.listen(PORT, () => {
console.log("Started local graphql server on port ", PORT);
});
vbranden's answer was excellent, and it is the basis of this answer. However, his answer has a lot of other code which obfuscates the solution a bit. I didn't want to mess with it, since it offers a more complete view of things, but hopefully this answer will be helpful in its own way by being more direct. But again, all credit for this solution belongs to vbranden (please upvote his answer accordingly).
If you make an isAuthenticated function with the appropriate signature (request, response, next) you can then "chain" that function in when you setup your GraphQL endpoint:
function isAuthenticated(req, res, next) {
return req.isAuthenticated() ?
next() :
res.redirect('/auth/google');
}
app.use(
'/graphql',
isAuthenticated,
graphqlHTTP(req => ({
schema,
graphiql: true,
context: req
}))
);