-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Datasources missing in the context when using subscriptions #1526
Comments
Unfortunately, this is a known issue. Although we run There is work underway on a refactoring of the |
Hi, did anyone come up with a solution to this? I am currently adding my dataSources to both the dataSource and context methods, but this feels kind of icky... const LanguagesAPI = require('./dataSources/LanguagesAPI')
const server = new ApolloServer({
...
dataSources: () => {
return: {
LanguagesAPI: new LanguagesAPI(),
}
},
context: ({req, connection}) => {
if (connection) {
return {
dataSources: {
LanguagesAPI: new LanguagesAPI(),
}
}
return {
// req context stuff
}
}
} |
any update on this? |
Any update? Can't use data sources as newing one up leaves it uninitialised.. |
also just ran into this issue and curious about solutions/fixes (update 5/4/2019 will wait until apollo 3.0, plenty of workarounds for now, helpful to understand) |
I use the method @BrockReece provided but then got the error: |
We can follow the 3.0 Roadmap, which includes "Unify diverging request pipelines": |
I guess it is related to #2561 as well. |
I solved this by using @BrockReece's method but initializing DataSource classes manually: const constructDataSourcesForSubscriptions = (context) => {
const initializeDataSource = (dataSourceClass) => {
const instance = new dataSourceClass()
instance.initialize({ context, cache: undefined })
return instance
}
const LanguagesAPI = initializeDataSource(LanguagesAPI)
return {
LanguagesAPI,
}
}
const server = new ApolloServer({
...
dataSources: () => {
return: {
LanguagesAPI: new LanguagesAPI(),
}
},
context: ({req, connection}) => {
if (connection) {
return {
dataSources: constructDataSourcesForSubscriptions(connection.context)
}
return {
// req context stuff
}
}
} Hope this helps :) |
This is indeed on the near term roadmap! #1526 (comment) |
@jbaxleyiii so why is this ticket closed? Is 3.0 out? |
Any update on this? |
any update on this? |
It's 2020 now... Any update on this? |
Would be great if that could be solved somehow. Still have the same problem. |
I solved this by using @PatrickStrz method with some little tweak to have each Datasource instance to have the full context, Same way apollo server initializes its Datasources const pubSub = new PubSub();
/**
* Generate dataSources for both transport protocol
*/
function dataSources() {
return {
userService: new UserService(),
ministryService: new MinistryService(),
mailService: new MailService(),
};
}
/**
* Authenticate subscribers on initial connect
* @param {*} connectionParams
*/
async function onWebsocketConnect(connectionParams) {
const authUser = await authProvider.getAuthenticatedUser({
connectionParams,
});
if (authUser) {
return { authUser, pubSub, dataSources: dataSources() };
}
throw new Error("Invalid Credentials");
}
/**
* Initialize subscribtion datasources
* @param {Context} context
*/
function initializeSubscriptionDataSources(context) {
const dataSources = context.dataSources;
if (dataSources) {
for (const instance in dataSources) {
dataSources[instance].initialize({ context, cache: undefined });
}
}
}
/**
* Constructs the context for transport (http and ws-subscription) protocols
*/
async function context({ req, connection }) {
if (connection) {
const subscriptionContext = connection.context;
initializeSubscriptionDataSources(subscriptionContext);
return subscriptionContext;
}
const authUser = await authProvider.getAuthenticatedUser({ req });
return { authUser, pubSub };
}
/**
* Merges other files schema and resolvers to a whole
*/
const schema = makeExecutableSchema({
typeDefs: [rootTypeDefs, userTypeDefs, ministryTypeDefs, mailTypeDefs],
resolvers: [rootResolvers, userResolvers, ministryResolvers, mailResolvers],
});
/**
* GraphQL server config
*/
const graphQLServer = new ApolloServer({
schema,
context,
dataSources,
subscriptions: {
onConnect: onWebsocketConnect,
},
});
Hope this helps :) |
@josedache Awesome, works like a charm! Thank you! |
Glad to help @IsmAbd |
It's quite an old issue now but I want to share with you my solution :) It's very simple and looks like the previous solutions. I rename my import { PrismaClient } from '@prisma/client'
import { PubSub } from 'apollo-server'
import { Request, Response } from 'express'
import { ExecutionParams } from 'subscriptions-transport-ws'
import { Auth } from './auth'
import { createDataSources, DataSources } from './data-sources'
import { getUserId } from './utils'
const prisma = new PrismaClient()
const pubSub = new PubSub()
export interface ExpressContext {
req: Request
res: Response
connection?: ExecutionParams
}
// You can put whatever you like into this (as you can see I personnally use prisma, pubSub and an homemade Auth API)
export interface BaseContext extends ExpressContext {
prisma: PrismaClient
pubSub: PubSub
auth: Auth
}
export type Context = BaseContext & { dataSources: DataSources }
export function createBaseContext(context: ExpressContext): BaseContext {
const userId = getUserId(context)
return {
...context,
// You can put whatever you like into this
prisma,
pubSub,
auth: new Auth(userId),
}
}
export function createContext(context: ExpressContext): Context {
const base = createBaseContext(context) as Context
if (context.connection) {
base.dataSources = createDataSources()
}
return base
} |
Just want to note/summarize that
My impression is that the I suspect most people will instead want to explicitly pass a For reference, the default is (src) a |
Just worth noting that the subscriptions integration in Apollo Server has always been incredibly superficial, and we are planning to remove it in Apollo Server 3 (replacing it with instructions on how to use a more maintained subscriptions package alongside Apollo Server). We do hope to have a more deeply integrated subscriptions implementation at some point but the current version promises more than it delivers, as this and many other issues indicate. |
This didn't work for me at all. I got the following error when attempting to instantiate data sources into the subscription context:
I have no idea why this would happen. |
For future visitors, here's an example implementation using Apollo Server 3 (apollo-server-express 3.9), WebSocketServer from 'ws', and useServer from 'graphql-ws', including authorization. import { InMemoryLRUCache } from 'apollo-server-caching';
import { ApolloServer } from 'apollo-server-express';
import { ApolloServerPluginDrainHttpServer } from 'apollo-server-core';
import express from 'express';
import http from 'http';
import { WebSocketServer } from 'ws';
import { useServer } from 'graphql-ws/lib/use/ws';
import { getUser, connectToDatabase } from './utils';
import schema from './setup';
const makeContext = async ({ req, database }) => {
return {
database, // used to set up dataSources, required to be in context
user: getUser(req.headers, database), // checks for Authorization on req.headers and fetches user from database
};
};
const makeWScontext = async ({ connectionParams, database, dataSources: dataSourceFn }) => {
const cache = new InMemoryLRUCache();
const dataSources = dataSourceFn(); // pass in as function, then call to receive objects
for (const dataSource in dataSources)
dataSources[dataSource].initialize({ context, cache });
const user = getUser(context.connectionParams); // this uses the connectionParams sent from frontend to look for Authorization Bearer token
return {
database,
user,
dataSources, // unlike query context, we need to manually add dataSources to WebSocket context
};
};
const makeServer = async () => {
const database = await connectToDatabase(); // for example mongo instance
const dataSources = () => ({
posts: new Posts(...), // ... could refer to database.collection('posts') if using mongo
users: new Users(...),
});
const app = express();
const httpServer = http.createServer(app);
const wsPath = '/graphql';
const wsServer = new WebSocketServer({
server: httpServer,
path: wsPath,
});
const serverCleanup = useServer(
{
schema,
context: (context) => {
return makeWScontext({
context,
database,
dataSources, // pass dataSources function to custom WebSocket context function, which must call dataSource.initialize({ context, cache }) manually. supply cache here if you want it to be shared across sockets
});
},
},
wsServer
);
const server = new ApolloServer({
csrfPrevention: true,
schema,
dataSources, // Pass dataSources function to new ApolloServer, and it will call dataSource.initialize({ context, cache }) on all, and add to context automatically
context: async ({ req }) =>
await makeContext({
database,
req,
}),
plugins: [
ApolloServerPluginDrainHttpServer({ httpServer }),
{
async serverWillStart() {
return {
async drainServer() {
await serverCleanup.dispose();
},
};
},
},
],
});
await server.start();
server.applyMiddleware({
app,
cors: {
origin: '*', // <- allow request from all domains
methods: 'POST,GET,OPTIONS',
allowedHeaders: 'Content-Type, Authorization',
},
});
httpServer.listen({ port: PORT }, () => {
const hostUrl = process.env.API_HOST || 'http://localhost:4000';
const wsUrl = hostUrl.replace('http', 'ws');
console.log(
`🚀 Queries and mutations ready at ${hostUrl}${server.graphqlPath}`
);
console.log(`🚀 Subscriptions ready at ${wsUrl}${wsPath}`);
});
makeServer() |
Intended outcome: Context in the resolver functions will contain
dataSources
field when using subscriptions just like when calling with queries.Actual outcome: Field
dataSources
is missing while using subscriptions in the context (insubscribe
andresolve
functions). The context contains everything except this one field.How to reproduce the issue:
dataSources
to the serverI think this is quite serious because otherwise, I cannot query my data sources at all while using subscriptions.
The text was updated successfully, but these errors were encountered: