Skip to content

Instantly share code, notes, and snippets.

@neil-gebbie-smarterley
Last active December 19, 2020 17:02
Show Gist options
  • Save neil-gebbie-smarterley/cd8356df4c786c4c9dacfc9d46e890ac to your computer and use it in GitHub Desktop.
Save neil-gebbie-smarterley/cd8356df4c786c4c9dacfc9d46e890ac to your computer and use it in GitHub Desktop.
Apollo client, apollo server, next js with cookies
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} />
}
}
}
@NinjaOnRails
Copy link

I've fixed it for myself using the latest libraries next@9.0.3, next-with-apollo@4.2.0 and react-apollo@3.0.0. 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

@louisrli
Copy link

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)

@Akumzy
Copy link

Akumzy commented Jan 24, 2020

Wow thanks a lot

@josuevalrob
Copy link

God... why it is soooo complicated??

@neil-gebbie-smarterley
Copy link
Author

@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.

@josuevalrob
Copy link

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: josue.valrob@gmail.com

@neil-gebbie-smarterley
Copy link
Author

@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.

@josuevalrob
Copy link

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.

Screenshot 2020-04-12 at 16 54 15

I really appreciate your help, any comment is welcome.

@neil-gebbie-smarterley
Copy link
Author

@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

@lionelyoung
Copy link

This one worked for me: https://gist.github.com/lionelyoung/6f9020de23f257599bdabfdb0bf40bff found in another github thread

@lionelyoung
Copy link

How do I pass getServerSideProp contexts? i.e.

export async function getServerSideProps(context) {
  const myVars = {
    orderId: context["params"]["order"]
  }

  return {
    props: {
      myVars: myVars,
    },
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment