Here’s my current setup code. It supports queries as well as subscriptions, and uses a function from swydo to send the value of Meteor.userId() to the resolver in the context variable.
APOLLO SERVER SETUP
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
import express from 'express';
import http from 'http';
import cors from 'cors';
import typeDefs from '/imports/apollo/schema';
import {resolvers} from "../imports/apollo/resolvers";
import { WebSocketServer } from 'ws';
import { useServer } from 'graphql-ws/lib/use/ws';
import { makeExecutableSchema } from '@graphql-tools/schema';
import { createServer } from 'http';
import {getUserIdByLoginToken} from "../imports/apollo/meteor-apollo-utils";
const app = express();
const httpServer = http.createServer(app);
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
});
await server.start();
app.use(
'/graphql',
cors(),
express.json(),
expressMiddleware(server, {
context: async ({req}) => {
let token = req.headers['token']
let userId = null;
try {
if (token !== "null") {
userId = await getUserIdByLoginToken(token);
}
} catch (error) {
console.log('context: ', error)
}
return {
userId: userId
};
},
}),
);
await new Promise((resolve) => httpServer.listen({ port: 4000 }, resolve));
console.log(`🚀 Server ready at http://localhost:4000/graphql`);
APOLLO CLIENT
import { ApolloClient, HttpLink, InMemoryCache, ApolloLink, ApolloProvider } from "@apollo/client";
// Create an HttpLink pointing to your GraphQL endpoint
const httpLink = new HttpLink({ uri: 'http://localhost:4000/graphql' });
// Middleware to add custom headers
const customHeadersMiddleware = new ApolloLink((operation, forward) => {
// Define your custom headers
const customHeaders = {
"token": localStorage.getItem("Meteor.loginToken")
};
// Use operation.setContext to add the custom headers to the request
operation.setContext(({ headers }) => ({
headers: {
...headers,
...customHeaders,
},
}));
return forward(operation);
});
// Combine the middleware with the HttpLink
const apolloClient = new ApolloClient({
link: customHeadersMiddleware.concat(httpLink),
cache: new InMemoryCache(),
});
export {apolloClient};
FUNCTION GetUserIdByLoginToken
from swydo:ddp-apollo
//this code was found in swydo:ddp-apollo
const USER_TOKEN_PATH = 'services.resume.loginTokens.hashedToken';
export const NO_VALID_USER_ERROR = new Error('NO_VALID_USER');
export async function getUserIdByLoginToken(loginToken) {
// `accounts-base` is a weak dependency, so we'll try to require it
// eslint-disable-next-line global-require, prefer-destructuring
const { Accounts } = require('meteor/accounts-base');
if (!loginToken) { throw NO_VALID_USER_ERROR; }
if (typeof loginToken !== 'string') {
throw new Error("GraphQL login token isn't a string");
}
// the hashed token is the key to find the possible current user in the db
const hashedToken = Accounts._hashLoginToken(loginToken);
// get the possible user from the database with minimal fields
const fields = { _id: 1, 'services.resume': 1 };
const user = await Meteor.users.rawCollection()
.findOne({ [USER_TOKEN_PATH]: hashedToken }, { fields });
if (!user) { throw NO_VALID_USER_ERROR; }
// find the corresponding token: the user may have several open sessions on different clients
const currentToken = user.services.resume.loginTokens
.find((token) => token.hashedToken === hashedToken);
const tokenExpiresAt = Accounts._tokenExpiration(currentToken.when);
const isTokenExpired = tokenExpiresAt < new Date();
if (isTokenExpired) { throw NO_VALID_USER_ERROR; }
return user._id;
}
I haven’t yet got pubsub working in the resolvers.