upgrading to apollo-server-express 2.0.0 context missing - express

Before the upgrade we were having
import express from 'express';
import { graphqlExpress, graphiqlExpress } from 'apollo-server-express';
const app = express();
app.use(
'/graphql',
bodyParser.json(),
graphqlExpress(req => ({
schema,
tracing: true,
context: { req },
})),
);
app.use(
'/graphiql',
graphiqlExpress({
endpointURL: '/graphql',
}),
);
In our resolvers we could get the req and set req.session.token as follows,
const customResover = {
Query: {
custom: async (root, args, context) => {
console.log(' resolver called with args', args);
const { req } = context;
... fetch token info and set
req.session.token = ${token};
...
but with the upgrade to version 2.0.0 the code is changed to following and I am not sure how to fix the CustomResolver, to set the session token, any idea how the above could be accomplished ?
import express from 'express';
import { ApolloServer, gql } from 'apollo-server-express';
import { typeDefs, resolvers } from './schema/';
const app = express();
const apollo = new ApolloServer({
typeDefs
resolvers,
engine: false
});
apollo.applyMiddleware({
app,
});

https://www.apollographql.com/docs/apollo-server/migration-two-dot.html#request-headers
const apollo = new ApolloServer({
typeDefs
resolvers,
context: ({ req }) => ({ req })
engine: false
});
Solves it but got an issue with Cookie with token not getting to the browser.

Related

UnhandledPromiseRejectionWarning: Error: You must `await server.start()` before calling `server.applyMiddleware()` at ApolloServer

I am trying to start my nestJs server and It keeps giving me this error:
UnhandledPromiseRejectionWarning: Error: You must await server.start() before calling server.applyMiddleware()
at ApolloServer
I'm not even sure where to debug from as I am still very new at NestJs and GraphQL.
This is a known bug with an open issue and a merged PR to fix it. For now, you can downgrade to apollo-server-express#^2
A complete working code is:
const express = require("express");
const { ApolloServer } = require("apollo-server-express");
const http = require("http");
const app = express();
const typeDefs = `
type Query{
totalPosts: Int!
}
`;
const resolvers = {
Query: {
totalPosts: () => 100,
},
};
let apolloServer = null;
async function startServer() {
apolloServer = new ApolloServer({
typeDefs,
resolvers,
});
await apolloServer.start();
apolloServer.applyMiddleware({ app });
}
startServer();
const httpserver = http.createServer(app);
app.get("/rest", function (req, res) {
res.json({ data: "api working" });
});
app.listen(4000, function () {
console.log(`server running on port 4000`);
console.log(`gql path is ${apolloServer.graphqlPath}`);
});
I faced this issue when upgrading Ben Awad's Graphql-Next-Typeorm[...] stack, simply adding an await to server start fixed the warnings
const apolloServer = new ApolloServer({
introspection: true,
schema: await buildSchema({
resolvers: [__dirname + '/resolvers/**/*.js'],
validate: false
}),
context: ({ req, res }) => ({
req,
res,
redis: redisClient
}),
formatError
});
// added this line
await apolloServer.start();
apolloServer.applyMiddleware({
app,
cors: false
});
For Apollo Server Express 3.0 and above, you need to define an async function that takes in typeDefs and resolvers parameters, then assign the server to the same Apollo initialization as before as shown here
async function startApolloServer(typeDefs, resolvers){
const server = new ApolloServer({typeDefs, resolvers})
const app = express();
await server.start();
server.applyMiddleware({app, path: '/graphql'});
app.listen(PORT, () => {
console.log(`Server is listening on port ${PORT}${server.graphqlPath}`);
})
}
startApolloServer(typeDefs, resolvers);
downgrading is not the option (at least anymore)
here is the solution =>
https://javascriptsu.wordpress.com/2021/08/02/apollo-error-must-await-server-start/
const server = new ApolloServer({ typeDefs, resolvers });
const app = express();
server.start().then(res => {
server.applyMiddleware({ app });
app.listen({ port: 3000 }, () =>
console.log("nice")
)
})
You can put everything in an async function and execute the function in your server(app,index...).js. You may also check the npm package.
https://www.npmjs.com/package/apollo-server-express
For example:
const express = require('express')
, http = require('http')
, path = require('path');
const { ApolloServer } = require('apollo-server-express');
async function startExpressApolloServer() {
const { typeDefs } = require('./graphql/schemas/schema');
const { resolvers } = require('./graphql/resolvers/resolver');
const server = new ApolloServer({ typeDefs, resolvers });
await server.start();
const app = express();
server.applyMiddleware({ app, path: '/api/graphql' });
await new Promise(resolve => app.listen({ port: 3001 }, resolve));
console.log(`Server ready at http://localhost:3001${server.graphqlPath}`);
return { server, app };
}
startExpressApolloServer();
I had the same type of problem. I was using TypeScript, Express, ApolloServer. What I did-
async function a(){
const server = new ApolloServer({ typeDefs, resolvers });
await server.start();
server.applyMiddleware({ app, path: '/graphql' });
}
a();
This is not a bug. As per the documentation, the Apollo server needs to be instantiated in an async function. This is the recommended setup for Apollo Express:
import { ApolloServer } from 'apollo-server-express';
import { ApolloServerPluginDrainHttpServer } from 'apollo-server-core';
import express from 'express';
import http from 'http';
async function startApolloServer(typeDefs, resolvers) {
const app = express();
const httpServer = http.createServer(app);
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
});
await server.start();
server.applyMiddleware({ app });
await new Promise(resolve => httpServer.listen({ port: 4000 }, resolve));
console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`);
}
There are already some great answers here. But we should know why and where we should call server.start(). From apollo docs -
Always call await server.start() before calling
server.applyMiddleware and starting your HTTP server. This allows
you to react to Apollo Server startup failures by crashing your
process instead of starting to serve traffic.
One other option is to downgrade your apollo to any 2.x.x. It solved my problem
This is my working server:
import express from 'express';
import { ApolloServer } from 'apollo-server-express';
import typeDefs from './schema';
const app = express();
const server = new ApolloServer({
typeDefs,
mocks: true
});
server.start().then(() => {
server.applyMiddleware({
app,
cors: true,
});
});
const PORT = 4000;
app.listen(PORT, () => {
console.log(
`GraphQL endpoint and playground accessible at http://localhost:${PORT}${server.graphqlPath}`,
);
});
The key thing here is to wrap the "applyMiddleware" function call inside the "server.start" async function.
In v3, if you use apollo-server-express the start function is required https://www.apollographql.com/docs/apollo-server/api/apollo-server/#start.
You can do something like this.
const app = express()
app.use(express.urlencoded({ extended: true }))
app.use(express.json())
....
export const startup = async () => {
await server.start()
server.applyMiddleware({ app, path: `/api/${configs.region}/graphql` })
return app
}
// call startup in another file to get app
It is not ok to start the apollo server in advance. What happens with the case when I have to explicitly use http/https. Please see the following case:
const server = new ApolloServer({
typeDefs: [KeycloakTypeDefs, typeDefs], // 1. Add the Keycloak Type Defs
schemaDirectives: KeycloakSchemaDirectives, // 2. Add the
formatError: new ApolloErrorConverter(),
resolvers: resolvers,
context: ({ req }) => {
return makeContextWithDependencies(req);
}
});
server.applyMiddleware({ app });
http.createServer(app).listen(config.server.port, os.hostname());
const options = {
key: fs.readFileSync(config.server.ssl.keyFile, "utf8"),
cert: fs.readFileSync(config.server.ssl.certFile, "utf8"),
passphrase: config.server.ssl.passphrase
};
https
.createServer(options, app)
.listen(config.server.securePort, os.hostname());
console.log(
"Server waiting for requests on ports: " +
config.server.port +
"," +
config.server.securePort
);
We must wait for the server to get ready before adding middleware to it.
const app = express();
const apolloServer = new ApolloServer({
schema: await buildSchema({
resolvers: [HelloResolver],
validate: false,
}),
});
await apolloServer.start(); // First start the server then apply middleware on it
apolloServer.applyMiddleware({ app });
you can do like that, it works for me.
const server = new ApolloServer({ schema });
const startApollo = async () => {
try {
await server.start();
server.applyMiddleware({ app, path: "/api"})
} catch (error) {
console.log(error);
}
}

Complete express req not in apollo-server-express

Below you can see I have an Apollo server (using express). In lines 33-39 I use an express middleware to check an auth token and (if the token is valid) set the users _id onto the request object.
In my server constructor, I set the context to return the req, as well as the _id as the userId. I have also tried just doing ({req}) => {..req}.
No matter what I am trying, I am not getting acces to the userId in the apollo context. I've made a simple gql query resolver in Apollo to just log out the value, and it's always undefined.
Furthermore, I have a simple REST route (lines 43-49) that seem to attach the userId to every request just fine, so something isn't connecting, but I'm not sure where.
At the end of the day my goal is to receive a JWT token from a cookie sent client side, verify it, and add it the context so I can access the values in my graphQL resolvers. Any thoughts?
index.js
const express = require('express');
const { ApolloServer } = require('apollo-server-express');
const bodyParser = require('body-parser');
const cors = require('cors');
const cookieParser = require('cookie-parser');
const jwt = require('jsonwebtoken');
require('dotenv').config();
const db = require('./db');
const User = require('./models/User');
const typeDefs = require('./gqlSchema');
const queries = require('./resolvers/queries');
const mutations = require('./resolvers/mutations');
const resolvers = {
Mutation: mutations,
Query: queries,
}
const app = express();
const corsOptions = {
origin: process.env.FRONTEND_URL,
credentials: true,
};
app.use(cors(corsOptions));
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use((req, res, next) => {
const { token } = req.cookies;
if (token) {
const tokenData = jwt.verify(token, process.env.SECRET);
req._id = tokenData._id;
}
next();
})
//// TEMP REST LOGIN ////
const login = require('./controllers/login');
app.post('/auth/google', login);
app.post('/token', (req, res) => {
console.log('/token middlware req: ', req._id);
res.send('test token route')
})
///////////////////
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => ({
...req,
userId: req._id,
})
});
server.applyMiddleware({
app,
path: '/graphql',
cors: false,
});
app.listen({ port: 4000 }, () => {
console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`);
})

Apollo Server as Nuxt serverMiddleware

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' }],
}

REST API router forward to Apollo GraphQL endpoinr

I have a node/express/Apollo application to provide GraphQL service to the frontend application. I also have the REST API endpoing in the application to provide service for legacy applications. I want to forward the REST API calls to the GraphQL endpoint. For example:
From
GET /api/roles
to
POST /graphql
{ *body* }
I tried like this:
// app.js
import express from 'express';
import { ApolloServer } from 'apollo-server-express';
import routes from './routes';
const port = process.env.PORT || 8088;
const app = express();
app.use('/api/roles', routes.role);
const server = new ApolloServer({
......
},
});
server.applyMiddleware({ app, path: '/graphql' });
app.listen({ port: port }, () => {
console.log(`Apollo Server on http://localhost:${port}/graphql`);
});
// routes/role.js
import { Router } from 'express';
const router = Router();
router.get('/', (req, res, next) => {
req.url = '/graphql';
req.originalUrl = '/graphql';
req.method = 'POST';
req.body = `
{
findRoles {
data {
roleId
name
}
}
}`;
return router.handle(req, res, next);
});
It doesn't work and gives the error "Cannot POST /graphql". Any idea how to do it?

Apollo express don't have express passport session

For our portal, i integrated my express server with passport to authenticate user. Once user authenticated need to fetch details for user from our api using same session from client. passport authentication working for my portal and able to see identity provider data for user logged in. but when any GraphQL call executed from react side, Apollo servers don't have user session in its request but express had.
Followed the steps mentioned here https://www.apollographql.com/docs/apollo-server/essentials/server.html#ssl but when setting up context for ApolloServer, req don't have passport in its session.
const express = require('express');
const session = require('express-session');
const { ApolloServer } = require('apollo-server-express');
const addResolveFunctionsToSchema = require('graphql-tools').addResolveFunctionsToSchema;
const mongooseConnection = require('./src/persistence/mongooseConnection');
const MongoSession = require('connect-mongo')(session);
const config = require('config');
const mongo = config.get('mongo');
const fs = require('fs');
const https = require('https');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const configSession = config.get('session');
const configPassport = config.get('passport');
const configCommon = config.get('common');
const resolvers = require('./src/resolvers');
const schema = require('./src/schema');
const uri = `${mongo.uri}/${mongo.database}`;
const app = express();
var passport = require('passport');
const SamlStrategy = require('passport-saml').Strategy;
const authRoute = require('./routes/auth');
const apiRoute = require('./routes/userDetails');
const Helmet = require('helmet');
require('reflect-metadata');
mongooseConnection.create();
let mongoOptions = {};
const mongoSessionStore = new MongoSession({
url: `${uri}`,
mongoOptions: mongoOptions,
collection: configSession.mongo.collection
});
addResolveFunctionsToSchema(schema, resolvers);
passport.use(
new SamlStrategy(
{
entryPoint: configPassport.entry_point,
// issuer: 'passport-saml',
protocol: 'https',
callbackURL: `${configCommon.dns}/${configPassport.callback_url}`,
// cert: adfsTokenSigningCert,
identifierFormat: null,
forceAuthn: true,
signatureAlgorithm: 'sha256',
acceptedClockSkewMs: -1
},
(profile, cb) => {
cb(null, profile);
}
)
);
passport.serializeUser(function(user, done) {
done(null, userData);
});
passport.deserializeUser(function(obj, done) {
done(null, obj);
});
app.use(cookieParser());
app.use(
session({
secret: 'project',
secret: configSession.config.secret,
resave: false,
saveUninitialized: false,
store: mongoSessionStore,
sameSite: true,
rolling: true,
name: 'project',
cookie: {
secure: true,
domain: configCommon.cookie_domain
}
})
);
// Initialize Passport
app.use(passport.initialize());
app.use(passport.session());
app.use(bodyParser.json());
app.use(
bodyParser.urlencoded({
extended: false
})
);
// setting up security headers
app.use(Helmet());
app.get('/s/health-check', (req, res) => {
res.send(`I am alive!!!!`);
});
app.use('/s/api', apiRoute);
app.use('/s/auth', authRoute); // API integration
const isDev = process.env.NODE_ENV !== 'prod';
const apolloServer = new ApolloServer({
schema,
resolvers,
context: ({ req }) => {
console.log(req) //req.session don't have passport in it
return {
req
};
}
});
apolloServer.applyMiddleware({
app,
path: '/graphql'
});
apolloServer.installSubscriptionHandlers(app);
https
.createServer(
{
key: fs.readFileSync(`./keys/server.key`),
cert: fs.readFileSync(`./keys/server.crt`)
},
app
)
.listen(process.env.PORT, () => {
console.log('Express server is running on port 9091');
});
// Handle uncaughtException
process.on('uncaughtException', err => {
console.log(err.message);
process.exit(1);
});
Double check that the request is sending the session cookies. I had the same problem, and I was using Graphql Playground as the client, and it disables sending the cookies by default. You can fix it by going to the settings in the top right of the playground, and setting "request.credentials": "same-origin". After I did that I saw the passport session initialized properly.