Comments (3)
Thanks a lot for the fast answer Jack!
Keeping things separated is what I would have prefered, but I found the next js setup is way faster on my laptop, and I thought that, despite being still quite new, the future improvements of the next js team would mainly go to their api routes system.
That's almost as what I came up with, I had something like:
import { CONFIRM_PASSWORD } from "@graphql/user/queries"
import { initializeApollo } from '@services/apollo/apolloClient'
const Confirm = (props) => {
if (props.userConfirmed) {
return <div>User account has been confirmed</div>;
} else {
return <div>Something went wrong</div>;
}
};
export async function getServerSideProps(context) {
const apolloClient = initializeApollo()
const { token } = context.query;
const response = await apolloClient.mutate({
mutation: CONFIRM_PASSWORD,
variables: {
token: token as string
}
})
const userConfirmed = response.data.confirmUser
// Or can redirect here directly to the login page
return {
props: {
initialApolloState: apolloClient.cache.extract(),
userConfirmed
}
}
}
export default Confirm
What I liked about the setup client side is that the auto generated code already took care of the apollo initialisation, thus not having to call initializeApollo
on each component. But I guess there is no better way for now.
At first I had similar to the next js example:
function createIsomorphLink() {
if (typeof window === 'undefined') {
const { SchemaLink } = require('@apollo/client/link/schema')
const { schema } = require('@services/apollo/schema');
return new SchemaLink({ schema })
} else {
const { HttpLink } = require('@apollo/client/link/http')
return new HttpLink({
uri: GRAPHQL_URI,
credentials: 'same-origin'
})
}
}
but had problem with the schema (getting entities not found after first request : due to HMR + typeorm problem) so went just like you did by creating a new link each time:
function createIsomorphLink() {
const { HttpLink } = require('@apollo/client/link/http')
return new HttpLink({
uri: GRAPHQL_URI,
credentials: 'same-origin'
})
}
I decided to go with the session vs jwt token so will have to adapt the auth part.
I will definitly keep an eye on new best practices in the fullstack boilerplate repo ;)
from split.
Hi @trompx
Thanks for the kind words, glad that it helped you!
This project is slightly old now and we have updated the stack to use Next.js.
We've been quite busy with client projects so haven't had much time to do too much open source work. I would like to update our fullstack-boilerplate though with some new things that we have added, so keep an eye on that!
We still use Apollo Client with Next.js using getServerSideProps, quick example:
// pages/index.tsx
export const getServerSideProps: GetServerSideProps = async () => {
const client = initializeApollo()
await client.query({ query: MeQueryDocument, errorPolicy: "ignore" })
return {
props: { initialApolloState: client.cache.extract() },
}
}
then in your _app.tsx file:
// pages/_app.tsx
export default function MyApp(props: AppProps<{initialApolloState?: any}>) {
const { Component, pageProps } = props
const apolloClient = useApollo(pageProps.initialApolloState)
return (
<ApolloProvider client={apolloClient}>
<Component {...pageProps} />
</ApolloProvider>
)
}
Related Apollo Client stuff including hook in _app
// lib/apolloClient.ts
import React from "react"
import { ApolloClient, InMemoryCache, NormalizedCacheObject, createHttpLink } from "@apollo/client"
import { setContext } from "@apollo/client/link/context"
import { API_URL, AUTH_TOKEN_NAME } from "lib/config"
import { parseCookies } from "lib/utils/helpers"
type Callback = () => string
type Options = {
getToken: Callback
}
let apolloClient: ApolloClient<NormalizedCacheObject> | null = null
const httpLink = createHttpLink({ uri: API_URL })
function createApolloClient(options?: Options) {
const authLink = setContext((_, { headers }) => {
const token = options?.getToken()
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
},
}
})
return new ApolloClient({
ssrMode: typeof window === "undefined",
ssrForceFetchDelay: 100,
link: authLink.concat(httpLink),
defaultOptions: {
mutate: { errorPolicy: "all" },
query: { errorPolicy: "all" },
},
cache: new InMemoryCache(),
})
}
export function initializeApollo(initialState?: null | Record<string, any>, options?: Options) {
const _apolloClient = apolloClient ?? createApolloClient(options)
// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// gets hydrated here
if (initialState) {
const existingCache = _apolloClient.extract()
// Merge existing cache with props passed from each page
_apolloClient.cache.restore({ ...existingCache, ...initialState })
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === "undefined") return _apolloClient
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient
return _apolloClient
}
export function useApollo(initialState: any = null) {
const store = React.useMemo(
() => initializeApollo(initialState, { getToken: () => parseCookies()[AUTH_TOKEN_NAME] }),
[initialState],
)
return store
}
In the example above, the me query is run on the server (you can do as many queries as you want) and the cache is passed into page props, then a client side Apollo Client is instantiated using those props. Your components can now use this. So if there was a useMeQuery somewhere in the component tree, the value would have already been loaded so you won't get loading states!
For now, I think we will continue to use a separate next.js server + express Apollo server, mainly because it allows us to keep things separated in case we want to switch the frontend or use the api for non-next.js related things (app, other apis etc)
Hope this has been useful, let me know if you have more questions!
from split.
Hey @JClackett,
I have additional questions if you don't mind, concerning how you handle user authentification on client pages with redis session.
In my app, the graphql api authentification part is handled by an auth middleware (similar to your authChecker, except that I use @UseMiddleware(isAuth)
instead). But for some pages, I wish to not display the page at all and redirect the user if he is not authenticated.
For now, in a page that you want to protect (like pages/Costs.tsx), you have:
import useAppContext from "../lib/hooks/useAppState"
...
const { user } = useAppContext()
if (!user.groupId) return <Redirect to="/" noThrow={true} />
which call the lib/hooks/useAppState.ts where the context is pulled from the application/context.tsx with:
export interface StateContext {
user: MeQuery["me"]
group: GetGroupQuery["group"]
}
export const StateContext = React.createContext<StateContext>({
user: null,
group: null,
})
export const StateProvider = StateContext.Provider
which itself exports a StateContext which I believe is populated with the user data pulled from the database inside the components/providers/StateProvider.tsx component.
It is then used in every pages/components needing to check if a user is auth, Is that how this works?
- I guess in next.js I should implement that AppProvider (including the StateProvider) in the _app.tsx file so every page have the user data in the context?
- I am wondering if you still have the same approach of handling protected content on the client in next js, or maybe you use an HOC now instead?
- And does it mean that each time that a page, or a component, using useAppContext is called, the MeQuery is run? So if I have a complex page with multiple components checking with
const { user } = useAppContext()
, does it mean that the page would query as many times the redis database as the useAppContext function is called or there is some sort of cache mechanism?
from split.
Related Issues (2)
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from split.