How do we get req.headers.authorization
in Mv3? There’s a forum thread about it here, but it doesn’t seem to apply to Mv3.
Meteor 3 is now using express 4.x through webapp
It works with Mv3:
import { ApolloServer } from "apollo-server-express";
import { WebApp } from "meteor/webapp";
// ...
const apolloServer = new ApolloServer({
schema,
context: async ({ req }) => {
debug("req", req.headers);
const loginToken = req.headers.authorization;
const userAgent = req.headers["user-agent"];
return {
loginToken,
userAgent,
};
},
cache: new InMemoryLRUCache({
maxSize: Math.pow(2, 20) * 30, // 30 MB
ttl: 60 * 5, // 5 minutes (in seconds)
}),
});
apolloServer.start().then(() => {
apolloServer.applyMiddleware({
app: WebApp.connectHandlers,
cors: true,
path: "/graphql",
});
});
Here’s my current ApolloClient setup code:
import React from 'react';
// import { MeteorAccountsLink } from 'meteor/apollo'
import { Hello } from './Hello.jsx';
import { Info } from './Info.jsx';
import { ApolloClient, InMemoryCache, ApolloProvider } from "@apollo/client";
import AccountsPage from "./accounts";
const client = new ApolloClient({
uri: "http://localhost:4000",
cache: new InMemoryCache(),
});
export default client;
Could you please post your code so I make sure I’m using client setup that matches with your server setup?
I think WebApp.connectHandlers
should be WebApp.handlers
instead. Breaking changes | Meteor 3.0 Migration Guide
You’re right. But WebApp.connectHandlers
still works as alias of WebApp.handlers
Here is my client setup:
import {
ApolloProvider,
ApolloClient,
InMemoryCache,
createHttpLink,
ApolloLink,
} from "@apollo/client";
import { setContext } from "@apollo/client/link/context";
import { onError } from "@apollo/client/link/error";
import { GraphQLWsLink } from "@apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";
import { getMainDefinition } from "@apollo/client/utilities";
// ...
const wsLink = theWsLinkUrl
? new GraphQLWsLink(
createClient({
url: theWsLinkUrl,
connectionParams: () => {
return {
authorization: token || "",
};
},
on: {
error: props => {
logger("wsLink error", props);
},
},
}),
)
: null;
const httpAuthLink = createHttpLink({
uri: apiUrl,
fetchOptions: {
rejectUnauthorized: false,
},
});
const authLinkContext = setContext((_, { headers }) => {
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: token || "",
},
};
});
const authLink = ApolloLink.from([
errorLink,
authLinkContext.concat(httpAuthLink),
]);
const httpAppLink = createHttpLink({
uri: appApiUrl || "",
});
const appLinkContext = setContext((_, { headers }) => {
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: token || "",
},
};
});
const appLink = ApolloLink.from([
errorLink,
appLinkContext.concat(httpAppLink),
]);
return new ApolloClient({
link: ApolloLink.split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === "OperationDefinition" &&
definition.operation === "subscription"
);
},
wsLink ? wsLink : appLink,
ApolloLink.split(
operation => operation.getContext().clientName === "auth",
authLink, // <= apollo will send to this if clientName is "auth"
appLink, // <= otherwise will send to this
),
),
cache: new InMemoryCache({
}),
});
For:
const wsLink = theWsLinkUrl
? new GraphQLWsLink(
…is theWsLinkUrl stored somewhere for reference here?
It’s a string, websocket server address, e.g: wss://banana.com/graphql
Here:
const httpAuthLink = createHttpLink({
uri: apiUrl,
fetchOptions: {
rejectUnauthorized: false,
},
});
…where does apiUrl come from?
Also, later in the client code:
- ErrorLink
- appApiUrl
I can make some guesses, but it will be much faster to ask you.
I really appreciate your help! I’ve been trying to get Apollo working with Mv3 for a week or so.
UPDATE:
Is this in the ballpark?
const url = new URL(window.location.href);
let theWsLinkUrl = `wss://${url.host}/graphql`
let appApiUrl = window.location.href;
let errorLink = window.location.href; //I'm sure this one is specific to how the app wants to handle these errors
those url should be ended with graphql
by default. It depends on how you setup your graphql server.
So, with the server setup you provided, would these be good examples?
let theWsLinkUrl = `wss://localhost:3000/graphql`
let appApiUrl = `http://localhost:3000/graphql`;
let errorLink = `http://localhost:3000/myPageThatShowsGraphQLerrors`;
With this code:
let theWsLinkUrl = `wss://localhost:3000/graphql`
let appApiUrl = `http://localhost:3000/graphql`;
let errorLink = `http://localhost:3000/myPageThatShowsGraphQLerrors`;
const authLinkContext = setContext((_, { headers }) => {
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: token || "",
},
};
});
const authLink = ApolloLink.from([
errorLink,
authLinkContext.concat(httpAuthLink),
]);
…I’m getting an authLink that looks like this:
These APIs are not easy to research. Can you provide guidance by any chance?
You don’t need a separate authLink. I need it because I designed my app like that. You don’t even need wsLink if you don’t use subscription feature.
I found a way to get the token to the server, and then use that to look up the authenticated Meteor.userId()! I’ll get the client and server code cleaned up and posted here. I still have to get subscriptions working.
@minhna May I ask what pubsub you are using with Apollo Subscriptions? And could you please post the setup code? I’d previously used graphql-postgres-subscriptions
, but that seems to require an older version of node.
I use graphql-redis-subscriptions
import { RedisPubSub } from "graphql-redis-subscriptions";
export const pubsub = new RedisPubSub({
});
pubsub.publish(channelId, {
someKey: data,
});
The client side, I use Apollo client @apollo/client
package.
If you have Meteor on the client then just use Meteor pub/sub. It’s much easier. I use Apollo because my client side is React Native.
Thanks for this great info. So that’s all the setup – I don’t have to create redis db or something like that?
I’m running this code, from the graphql-redis-subscriptions npm page:
import { RedisPubSub } from "graphql-redis-subscriptions";
import * as Redis from 'ioredis';
const options = {
host: 'localhost',
port: 6379,
retryStrategy: times => {
// reconnect after
return Math.min(times * 50, 2000);
}
};
const pubsub = new RedisPubSub({
publisher: new Redis(options),
subscriber: new Redis(options)
});
…and it’s saying “Redis is not a constructor”. Does that make any sense?