-
-
Save neil-gebbie-smarterley/cd8356df4c786c4c9dacfc9d46e890ac to your computer and use it in GitHub Desktop.
const typeDefs = require('./schema/schema') | |
const someRestAPI = require('./someRestAPI') | |
const resolvers = require('./resolvers') | |
const apolloServer = { | |
typeDefs, | |
resolvers, | |
dataSources: () => ({ | |
someRestAPI: new someRestAPI(), | |
}), | |
context: ({ req }) => { | |
return { req } | |
} | |
} | |
module.exports = apolloServer |
import { ApolloClient } from 'apollo-client' | |
import { createHttpLink } from 'apollo-link-http' | |
import { InMemoryCache } from 'apollo-cache-inmemory' | |
import { setContext } from 'apollo-link-context' | |
import fetch from 'isomorphic-unfetch' | |
let apolloClient = null | |
// Polyfill fetch() on the server (used by apollo-client) | |
if (!process.browser) { | |
global.fetch = fetch | |
} | |
const graphqlEndpoint = | |
process.env.NODE_ENV !== 'production' | |
? 'http://localhost:3000' | |
: process.env.BASE_URL | |
const create = (initialState, cookie) => { | |
const httpLink = createHttpLink({ | |
uri: `${graphqlEndpoint}/graphql`, | |
credentials: 'include' | |
}) | |
const authLink = setContext((_, { headers }) => { | |
return { | |
headers: { | |
...headers, | |
Cookie: cookie ? cookie : '', | |
} | |
} | |
}) | |
return new ApolloClient({ | |
connectToDevTools: process.browser, | |
ssrMode: !process.browser, // Disables forceFetch on the server (so queries are only run once) | |
link: authLink.concat(httpLink), | |
cache: new InMemoryCache().restore(initialState || {}) | |
}) | |
} | |
const initApollo = (initialState, cookie) => { | |
// Make sure to create a new client for every server-side request so that data | |
// isn't shared between connections (which would be bad) | |
if (!process.browser) { | |
return create(initialState, cookie) | |
} | |
// Reuse client on the client-side | |
if (!apolloClient) { | |
apolloClient = create(initialState, document.cookie) | |
} | |
return apolloClient | |
} | |
export default initApollo |
const express = require('express') | |
const next = require('next') | |
const cors = require('cors') | |
const cookieParser = require('cookie-parser') | |
const connect = require('connect') | |
const { ApolloServer } = require('apollo-server-express') | |
const apolloServer = require('./apolloServer') | |
const dev = process.env.NODE_ENV !== 'production' | |
const port = process.env.PORT || 3000 | |
const app = next({ dev }) | |
const handle = app.getRequestHandler() | |
var corsOptions = { | |
origin: | |
process.env.NODE_ENV !== 'production' | |
? 'http://localhost:3000' | |
: process.env.BASE_URL, | |
credentials: true | |
} | |
app | |
.prepare() | |
.then(() => { | |
const server = express() | |
.use(connect()) | |
.use(cookieParser()) | |
.use(cors(corsOptions)) | |
new ApolloServer({ ...apolloServer }).applyMiddleware({ | |
app: server, | |
cors: corsOptions | |
}) | |
// your next config | |
server.get('*', (req, res) => { | |
return handle(req, res) | |
}) | |
server.listen(port, err => { | |
if (err) throw err | |
// eslint-disable-next-line no-console | |
console.log(`> Ready on http://localhost:${port} at ${Date.now()}`) | |
}) | |
}) | |
.catch(ex => { | |
// eslint-disable-next-line no-console | |
console.error(ex.stack) | |
process.exit(1) | |
}) |
class someRestAPI extends RESTDataSource { | |
constructor() { | |
super() | |
this.baseURL = getConfig.env.API_ENDPOINT | |
} | |
willSendRequest(request) { | |
request.headers.set('cookie', this.context.req.headers.cookie) | |
} | |
// your functions | |
} |
/* eslint-disable no-console */ | |
import React from 'react' | |
import PropTypes from 'prop-types' | |
import initApollo from './init-apollo' | |
import Head from 'next/head' | |
import idx from 'idx' | |
import { getDataFromTree } from 'react-apollo' | |
export default App => { | |
return class Apollo extends React.Component { | |
static displayName = 'withApollo(App)' | |
static async getInitialProps(ctx) { | |
const { Component, router } = ctx | |
// get the cookies sent on the initial request | |
const cookie = idx(ctx, _ => _.ctx.req.headers.cookie) | |
// Run all GraphQL queries in the component tree and extract the resulting data | |
const apollo = initApollo({}, cookie) | |
let appProps = {} | |
if (App.getInitialProps) { | |
appProps = await App.getInitialProps(ctx) | |
} | |
if (!process.browser) { | |
try { | |
// Run all GraphQL queries | |
await getDataFromTree( | |
<App | |
{...appProps} | |
Component={Component} | |
router={router} | |
apolloClient={apollo} | |
/> | |
) | |
} catch (error) { | |
// Prevent Apollo Client GraphQL errors from crashing SSR. | |
// Handle them in components via the data.error prop: | |
// https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error | |
console.error('Error while running `getDataFromTree`', error) | |
} | |
// getDataFromTree does not call componentWillUnmount | |
// head side effect therefore need to be cleared manually | |
Head.rewind() | |
} | |
// Extract query data from the Apollo store | |
const apolloState = apollo.cache.extract() | |
return { | |
...appProps, | |
apolloState, | |
cookie | |
} | |
} | |
static propTypes = { | |
apolloState: PropTypes.object, | |
cookie: PropTypes.string | |
} | |
constructor(props) { | |
super(props) | |
this.apolloClient = initApollo(props.apolloState, props.cookie) | |
} | |
render() { | |
return <App {...this.props} apolloClient={this.apolloClient} /> | |
} | |
} | |
} |
Good to hear - Unless your persisting and then rehydrating state somehow that is the correct behaviour. I can't really go too much into detail about our stack but if you can let me know a little bit about the stack your working on I might be able to offer a suggestion. @NinjaOnRails
I use the same stack on the FR as you mention in the Github thread (Next/Apollo Client/Apollo Server), on the backend I use GraphQL Prisma with Yoga server. So the cookie staying on localhost in the dev environment even on page refresh/tab close, but not in production (deployed to NOW) is the correct behaviour? This is what I got on Chrome and Firefox. On Safari the cookie doesn't even get saved for some reason. SSR luckily works. How is it working for you in production? There's another setup that allows the cookie to be passed properly (using the canary branch of the official Next.js with-apollo example available here on Github), but SSR breaks. Thanks
btw, my project is deployed here
This is strange, now it looks like the cookie is still there. It just doesn't get passed around...
Our local and production set up is very different, but I can't really talk about that.
Are you passing th cookie to a REST API? Where is it's final destination?
What happens when you go directly to the API Vs through Apollo/graphql?
Are you expecting your response to be different with or without cookies?
Add:
error: ({ error }) => {
console.log(error)
return { error }
}
To ApolloServer.js and see if you get any errors
Here's my latest FR server setup
on the backend I use GraphQL Prisma with Yoga server like this (so it's a graphql endpoint, not REST api):
const jwt = require('jsonwebtoken');
require('dotenv').config({ path: 'variables.env' });
const createServer = require('./createServer');
const db = require('./db');
const server = createServer();
// Handle cookies (JWT)
server.express.use(cookieParser());
// Decode JWT to get User ID on each request
server.express.use((req, res, next) => {
const { token } = req.cookies;
if (token) {
const { userId } = jwt.verify(token, process.env.APP_SECRET);
// Put userId onto req for future requests to access
req.userId = userId;
}
next();
});
// Populate user on each request
server.express.use(async (req, res, next) => {
if (!req.userId) return next();
const user = await db.query.user({ where: { id: req.userId } });
req.user = user;
next();
});
server.start(
{
cors: {
credentials: true,
origin: [
process.env.FRONTEND_URL,
process.env.FRONTEND_URL_2,
process.env.FRONTEND_URL_3,
],
},
},
deets => {
console.log(`Server is now running on port http://localhost:${deets.port}`);
}
);
Every request that comes to my backend from my frontend is supposed to contain headers with cookies. If there's token and it validates, user is set on the FR, if not, nothing happens. I'm only able to get this work with SSR broken. Or SSR work and cookie passing broken. Not both to work at once
I've fixed it for myself using the latest libraries [email protected], [email protected] and [email protected]. Cookies are passed with every request, SSR working as expected and no errors. Code is here. I had to remove .restore(initialState || {})
so it's just cache: new InMemoryCache()
and now it's fully working. Only thing still not working is Safari
I also had some difficulty with this until I found this gist from a variety of Googling.
I had started off with something along these lines from this repo, but was running into this issue.
https://github.com/brunocrosier/next-with-apollo-auth
After banging my head on various supposed solutions, what finally worked for me was setting prop.cookies in the HOC, e.g., enabling this line in particular.
this.apolloClient = initApollo(props.apolloState, props.cookie)
Also see:
lfades/next-with-apollo#26
apollographql/apollo-client#5089
@NinjaOnRails solution with the inmemorycache change didn't work, unfortunately, but YMMV as my code is slightly different from his (as I mentioned, it's based off the @brunocrosier repo)
Wow thanks a lot
God... why it is soooo complicated??
@josuevalrob Just take it one step it at a time, log values out along the way, and everything will fall into place. You only need to set it up once then everything will be fine.
Thanks
@josuevalrob Just take it one step it at a time, log values out along the way, and everything will fall into place. You only need to set it up once then everything will be fine.
But the problem is that I have a different server configuration. Can you check it, please?
Client: https://github.com/josuevalrob/CLE
Server: https://github.com/josuevalrob/cle_api
Just a quick look to know if this approach is fine for me. It means to refactor a lot of code.
So far it is impossible to send the cookie in the login and validate it in the context of my graphQl.
Please if you can give me a hand, contact me: [email protected]
@josuevalrob I'm giving you a hand by providing this gist.
What have you tried? I've looked at your repo and you haven't implemented any of the code above. I don't know if the approach will work for you, this gist is for NextJS and Apollo. I can't determine what's possible or not possible as I know nothing about your local, development or production environments. But it seems like a fairly common scenario to pass cookies, so I can't see why it would be an issue.
Best of luck.
Thanks @neil-gebbie-smarterley for share the repo and you help.
I had explain a little more about my issue in this question from StackOverflow.
My problem is that I need to understand what is happening, this is not magic you know(?).
Thanks to the comments from the StackOverflow questions, it looks like yes, the cookie is traveling in both direction, I have access to if I inspect the network request, but I am lost in the validation from the client and server-side.
I really appreciate your help, any comment is welcome.
@josuevalrob Are you expecting a different response based on the logged-in status of the user? How are you handling that on the server side? Do something simple like (this is pseudo code) - IF has cookie return a response {cookie: true} from the server. I suspect your trying to do many things at once and you need to just slow down and do one thing at a time.
Also add the authlink I have got here: https://gist.github.com/neil-gebbie-smarterley/cd8356df4c786c4c9dacfc9d46e890ac#file-initapollo-js-L25
This one worked for me: https://gist.github.com/lionelyoung/6f9020de23f257599bdabfdb0bf40bff found in another github thread
How do I pass getServerSideProp
contexts? i.e.
export async function getServerSideProps(context) {
const myVars = {
orderId: context["params"]["order"]
}
return {
props: {
myVars: myVars,
},
}
}
This is the only thing that works for me so far. At least on localhost it does. For some reason, once deployed to a custom domain, the token gets wiped with a refresh or when tab is closed. What about you? Thanks