Add Apollo Server and Client Example App (#8195)
* Add Apollo Server and Client Example * Update Apollo Client to use previous example setup
This commit is contained in:
parent
e78324a742
commit
6c01bbd6df
10 changed files with 286 additions and 0 deletions
49
examples/api-routes-apollo-server-and-client/README.md
Normal file
49
examples/api-routes-apollo-server-and-client/README.md
Normal file
|
@ -0,0 +1,49 @@
|
|||
# Apollo Server and Client Example
|
||||
|
||||
## How to use
|
||||
|
||||
### Using `create-next-app`
|
||||
|
||||
Execute [`create-next-app`](https://github.com/segmentio/create-next-app) with [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) or [npx](https://github.com/zkat/npx#readme) to bootstrap the example:
|
||||
|
||||
```bash
|
||||
npx create-next-app --example api-routes-apollo-server-and-client api-routes-apollo-server-and-client-app
|
||||
# or
|
||||
yarn create next-app --example api-routes-apollo-server-and-client api-routes-apollo-server-and-client-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
||||
Download the example:
|
||||
|
||||
```bash
|
||||
curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/api-routes-apollo-server-and-client
|
||||
cd api-routes-apollo-server-and-client
|
||||
```
|
||||
|
||||
Install it and run:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
# or
|
||||
yarn
|
||||
yarn dev
|
||||
```
|
||||
|
||||
Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download)):
|
||||
|
||||
```bash
|
||||
now
|
||||
```
|
||||
|
||||
## The idea behind the example
|
||||
|
||||
[Apollo](https://www.apollographql.com/client/) is a GraphQL client that allows you to easily query the exact data you need from a GraphQL server. In addition to fetching and mutating data, Apollo analyzes your queries and their results to construct a client-side cache of your data, which is kept up to date as further queries and mutations are run, fetching more results from the server.
|
||||
|
||||
In this simple example, we integrate Apollo seamlessly with Next by wrapping our _pages/\_app.js_ inside a [higher-order component (HOC)](https://facebook.github.io/react/docs/higher-order-components.html). Using the HOC pattern we're able to pass down a central store of query result data created by Apollo into our React component hierarchy defined inside each page of our Next application.
|
||||
|
||||
On initial page load, while on the server and inside `getInitialProps`, we invoke the Apollo method, [`getDataFromTree`](https://www.apollographql.com/docs/react/api/react-ssr/#getdatafromtree). This method returns a promise; at the point in which the promise resolves, our Apollo Client store is completely initialized.
|
||||
|
||||
Note: Do not be alarmed that you see two renders being executed. Apollo recursively traverses the React render tree looking for Apollo query components. When it has done that, it fetches all these queries and then passes the result to a cache. This cache is then used to render the data on the server side (another React render).
|
||||
https://www.apollographql.com/docs/react/api/react-ssr/#getdatafromtree
|
|
@ -0,0 +1,7 @@
|
|||
export const resolvers = {
|
||||
Query: {
|
||||
viewer (_parent, _args, _context, _info) {
|
||||
return { name: 'John Smith', id: 1 }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import { makeExecutableSchema } from 'graphql-tools'
|
||||
import { typeDefs } from './type-defs'
|
||||
import { resolvers } from './resolvers'
|
||||
|
||||
export const schema = makeExecutableSchema({
|
||||
typeDefs,
|
||||
resolvers
|
||||
})
|
|
@ -0,0 +1,12 @@
|
|||
import gql from 'graphql-tag'
|
||||
|
||||
export const typeDefs = gql`
|
||||
type User {
|
||||
id: ID!
|
||||
name: String!
|
||||
}
|
||||
|
||||
type Query {
|
||||
viewer: User
|
||||
}
|
||||
`
|
|
@ -0,0 +1,56 @@
|
|||
import ApolloClient from 'apollo-client'
|
||||
import { SchemaLink } from 'apollo-link-schema'
|
||||
import { InMemoryCache } from 'apollo-cache-inmemory'
|
||||
import { HttpLink } from 'apollo-link-http'
|
||||
import { schema } from '../apollo/schema'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
let apolloClient = null
|
||||
|
||||
function create (initialState) {
|
||||
// Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient
|
||||
const isBrowser = typeof window !== 'undefined'
|
||||
|
||||
const link = isBrowser
|
||||
? new HttpLink({
|
||||
uri: '/api/graphql',
|
||||
credentials: 'same-origin'
|
||||
})
|
||||
: new SchemaLink({ schema })
|
||||
|
||||
const cache = new InMemoryCache().restore(initialState || {})
|
||||
|
||||
return new ApolloClient({
|
||||
connectToDevTools: isBrowser,
|
||||
ssrMode: !isBrowser, // Disables forceFetch on the server (so queries are only run once)
|
||||
link,
|
||||
typeDefs: gql`
|
||||
extend type User {
|
||||
status: String!
|
||||
}
|
||||
`,
|
||||
resolvers: {
|
||||
User: {
|
||||
status () {
|
||||
return 'cached'
|
||||
}
|
||||
}
|
||||
},
|
||||
cache
|
||||
})
|
||||
}
|
||||
|
||||
export default function initApollo (initialState) {
|
||||
// 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 (typeof window === 'undefined') {
|
||||
return create(initialState)
|
||||
}
|
||||
|
||||
// Reuse client on the client-side
|
||||
if (!apolloClient) {
|
||||
apolloClient = create(initialState)
|
||||
}
|
||||
|
||||
return apolloClient
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
import React from 'react'
|
||||
import initApollo from './init-apollo'
|
||||
import Head from 'next/head'
|
||||
import { getDataFromTree } from '@apollo/react-ssr'
|
||||
import { ApolloProvider } from '@apollo/react-hooks'
|
||||
|
||||
export default App => {
|
||||
return class Apollo extends React.Component {
|
||||
static displayName = 'withApollo(App)'
|
||||
static async getInitialProps (ctx) {
|
||||
const { AppTree } = ctx
|
||||
|
||||
let appProps = {}
|
||||
// Run all GraphQL queries in the component tree
|
||||
// and extract the resulting data
|
||||
const client = initApollo()
|
||||
|
||||
if (App.getInitialProps) {
|
||||
ctx.client = client
|
||||
appProps = await App.getInitialProps(ctx)
|
||||
}
|
||||
|
||||
if (typeof window === 'undefined') {
|
||||
try {
|
||||
// Run all GraphQL queries
|
||||
await getDataFromTree(
|
||||
<ApolloProvider client={client}>
|
||||
<AppTree {...appProps} />
|
||||
</ApolloProvider>
|
||||
)
|
||||
} 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 = client.cache.extract()
|
||||
|
||||
return {
|
||||
...appProps,
|
||||
apolloState
|
||||
}
|
||||
}
|
||||
|
||||
client = initApollo(this.props.apolloState)
|
||||
|
||||
render () {
|
||||
return (
|
||||
<ApolloProvider client={this.client}>
|
||||
<App {...this.props} />
|
||||
</ApolloProvider>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
28
examples/api-routes-apollo-server-and-client/package.json
Normal file
28
examples/api-routes-apollo-server-and-client/package.json
Normal file
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"name": "with-apollo",
|
||||
"version": "2.0.0",
|
||||
"scripts": {
|
||||
"dev": "next",
|
||||
"build": "next build",
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/react-common": "3.0.1",
|
||||
"@apollo/react-hooks": "3.0.1",
|
||||
"@apollo/react-ssr": "3.0.1",
|
||||
"apollo-cache-inmemory": "1.6.3",
|
||||
"apollo-client": "2.6.4",
|
||||
"apollo-link-http": "1.5.15",
|
||||
"apollo-link-schema": "1.2.3",
|
||||
"apollo-server-micro": "2.9.0",
|
||||
"apollo-utilities": "^1.3.2",
|
||||
"graphql": "^14.0.2",
|
||||
"graphql-tag": "2.10.1",
|
||||
"next": "latest",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.7.0",
|
||||
"react-dom": "^16.7.0"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
11
examples/api-routes-apollo-server-and-client/pages/about.js
Normal file
11
examples/api-routes-apollo-server-and-client/pages/about.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import Link from 'next/link'
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
This is a static page goto{' '}
|
||||
<Link href='/'>
|
||||
<a>dynamic</a>
|
||||
</Link>{' '}
|
||||
page.
|
||||
</div>
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
import { ApolloServer } from 'apollo-server-micro'
|
||||
import { typeDefs } from '../../apollo/type-defs'
|
||||
import { resolvers } from '../../apollo/resolvers'
|
||||
|
||||
const apolloServer = new ApolloServer({ typeDefs, resolvers })
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: false
|
||||
}
|
||||
}
|
||||
|
||||
export default apolloServer.createHandler({ path: '/api/graphql' })
|
40
examples/api-routes-apollo-server-and-client/pages/index.js
Normal file
40
examples/api-routes-apollo-server-and-client/pages/index.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
import withApolloClient from '../lib/with-apollo-client'
|
||||
import gql from 'graphql-tag'
|
||||
import Link from 'next/link'
|
||||
import { useQuery } from '@apollo/react-hooks'
|
||||
|
||||
const ViewerQuery = gql`
|
||||
query ViewerQuery {
|
||||
viewer {
|
||||
id
|
||||
name
|
||||
status @client
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const Index = () => {
|
||||
const {
|
||||
data: { viewer }
|
||||
} = useQuery(ViewerQuery)
|
||||
|
||||
if (viewer) {
|
||||
return (
|
||||
<div>
|
||||
You're signed in as {viewer.name} and you're {viewer.status} goto{' '}
|
||||
<Link href='/about'>
|
||||
<a>static</a>
|
||||
</Link>{' '}
|
||||
page.
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
Index.getInitialProps = async ({ client }) => {
|
||||
await client.query({ query: ViewerQuery })
|
||||
}
|
||||
|
||||
export default withApolloClient(Index)
|
Loading…
Reference in a new issue