Skip to content

Commit

Permalink
Example: Static Generation of local Apollo GraphQL schema (#13202)
Browse files Browse the repository at this point in the history
## What

An example to show how you can create a static generated build of Next.js (using the new `getStaticProps` and `getStaticPaths` functions) with data fetched from a local GraphQL schema. The same schema is used to host a GraphQL endpoint using the API Route `/api/graphql`.

## Why

I was setting up a static hosted website whereby data is stored in a set of local JSON files (pulled from a backend server at build time). I wanted to consume the content using the developer benefits that GraphQL provides and using a consistent interface the GraphQL API consumers would be using.

Within my page components I initially made GraphQL `fetch` calls to the locally running `/api/graphql` endpoint which worked well with `npm run dev`, however when it came to `npm run build` that endpoint was inaccessible (I tried both VERCEL_URL and localhost:3000) - which is now understandable given I read further the "[Write server-side code directly](https://nextjs.org/docs/basic-features/data-fetching#write-server-side-code-directly)" documentation.

This example may or may not be useful for others so please feel free to close, but I thought I would contribute incase it assisted others.

## Related Discussions

It looks as though this PR may help with some of the following discussions:
  - #12785
  - #10946
  - #12182
  - #11285

## Related Examples

The end solution for this PR was a combination of web discussions and seeing the `api-routes-graphql` example. Perhaps this PR should just update that example to use the new methods? Or maybe it is worth having both?
- https://github.com/zeit/next.js/blob/canary/examples/examples/api-routes-graphql

This is also similar to this example, although that example has more code in order to provide isomorphic Apollo Client for client side code use.
- https://github.com/zeit/next.js/blob/canary/examples/api-routes-apollo-server-and-client

## Errors Recieved
I've included the below errors as they are the ones I faced when figuring out the end solution. These may just help with SEO for others googling the same issue.

### Error when trying to use localhost:3000
```
> Build error occurred
{ FetchError: request to http://localhost:3000/api/graphql failed, reason: connect ECONNREFUSED 127.0.0.1:3000
    at ClientRequest.<anonymous> (/vercel/7cf60e2b/.next/serverless/pages/content.js:1999:147829)
    at ClientRequest.emit (events.js:198:13)
    at Socket.socketErrorListener (_http_client.js:401:9)
    at Socket.emit (events.js:198:13)
    at emitErrorNT (internal/streams/destroy.js:91:8)
    at emitErrorAndCloseNT (internal/streams/destroy.js:59:3)
    at process._tickCallback (internal/process/next_tick.js:63:19) type: 'system', errno: 'ECONNREFUSED', code: 'ECONNREFUSED' }
```

### Error when trying to use VERCEL_URL
```
> Build error occurred
{ FetchError: invalid json response body at https://vercel.com/login?next=%2Fdeployments%2Fprojectnamehere.now.sh%3Fhost%3Dprojectnamehere.now.sh%26redirect%3D1%26section%3D reason: Unexpected token < in JSON at position 0
    at /vercel/3d91c11/node_modules/next/dist/compiled/node-fetch/index.js:1:133590
    at process._tickCallback (internal/process/next_tick.js:68:7) type: 'invalid-json' }
```
  • Loading branch information
benjaminpearson committed May 27, 2020
1 parent ce0a32c commit aef7f27
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 0 deletions.
50 changes: 50 additions & 0 deletions examples/api-routes-apollo-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Consume local Apollo GraphQL schema to create Static Generation export

Next.js ships with two forms of pre-rendering: [Static Generation](https://nextjs.org/docs/basic-features/pages#static-generation-recommended) and [Server-side Rendering](https://nextjs.org/docs/basic-features/pages#server-side-rendering). This example shows how to perform Static Generation using a local [Apollo GraphQL](https://www.apollographql.com/docs/apollo-server/) schema within [getStaticProps](https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation) and [getStaticPaths](https://nextjs.org/docs/basic-features/data-fetching#getstaticpaths-static-generation). The end result is a Next.js application that uses one Apollo GraphQL schema to generate static pages at build time and also serve a GraphQL [API Route](https://nextjs.org/docs/api-routes/introduction) at runtime.

## Deploy your own

Deploy the example using [Vercel](https://vercel.com):

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/project?template=https://github.com/zeit/next.js/tree/canary/examples/api-routes-apollo-server)

## How to use

### Using `create-next-app`

Execute [`create-next-app`](https://github.com/zeit/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:

```bash
npm init next-app --example api-routes-apollo-server api-routes-apollo-server-app
# or
yarn create next-app --example api-routes-apollo-server api-routes-apollo-server-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
cd api-routes-apollo-server
```

Install it and run:

```bash
npm install
npm run dev
# or
yarn
yarn dev
```

Deploy it to the cloud with [Vercel](https://vercel.com/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).

## Notes

### Static Export

If you wish to export a static HTML + JS version of the site you need to first change the setting in this example in `./pages/[username].js` where `getStaticPaths` has `fallback: true` - this needs to be `false` for static export to work. You can then run `npm run build` and `npm run export` to export the site as a static folder in `./out` directory.

[Read more about fallback option](https://nextjs.org/docs/basic-features/data-fetching#the-fallback-key-required)
18 changes: 18 additions & 0 deletions examples/api-routes-apollo-server/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "api-routes-apollo-server",
"version": "1.0.0",
"scripts": {
"dev": "next",
"build": "next build",
"export": "next export",
"start": "next start"
},
"dependencies": {
"apollo-server-micro": "2.13.1",
"graphql": "15.0.0",
"next": "latest",
"react": "16.13.1",
"react-dom": "16.13.1"
},
"license": "ISC"
}
45 changes: 45 additions & 0 deletions examples/api-routes-apollo-server/pages/[username].js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import queryGraphql from '../shared/query-graphql'

export default function UserProfile({ user }) {
if (!user) {
return <h1>User Not Found</h1>
}
return (
<h1>
{user.username} is {user.name}
</h1>
)
}

export async function getStaticProps(context) {
const { params } = context
const { username } = params
const { user = null } = await queryGraphql(
`
query($username: String) {
user(username: $username) {
name
username
}
}
`,
{ username }
)
return { props: { user } }
}

export async function getStaticPaths() {
const { users } = await queryGraphql(`
query {
users {
username
}
}
`)
return {
paths: users.map(({ username }) => ({
params: { username },
})),
fallback: true,
}
}
39 changes: 39 additions & 0 deletions examples/api-routes-apollo-server/pages/api/graphql.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ApolloServer, gql, makeExecutableSchema } from 'apollo-server-micro'

const typeDefs = gql`
type Query {
users: [User!]!
user(username: String): User
}
type User {
name: String
username: String
}
`
const users = [
{ name: 'Leeroy Jenkins', username: 'leeroy' },
{ name: 'Foo Bar', username: 'foobar' },
]

const resolvers = {
Query: {
users() {
return users
},
user(parent, { username }) {
return users.find((user) => user.username === username)
},
},
}

export const schema = makeExecutableSchema({ typeDefs, resolvers })

export const config = {
api: {
bodyParser: false,
},
}

export default new ApolloServer({ schema }).createHandler({
path: '/api/graphql',
})
32 changes: 32 additions & 0 deletions examples/api-routes-apollo-server/pages/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Link from 'next/link'

import queryGraphql from '../shared/query-graphql'

export default function UserListing({ users }) {
return (
<div>
<h1>User Listing</h1>
<ul>
{users.map((user) => (
<li key={user.username}>
<Link href="/[username]" as={`/${user.username}`}>
<a>{user.name}</a>
</Link>
</li>
))}
</ul>
</div>
)
}

export async function getStaticProps() {
const { users } = await queryGraphql(`
query {
users {
name
username
}
}
`)
return { props: { users } }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { graphql } from 'graphql'

import { schema } from '../../pages/api/graphql'

export default async function queryGraphql(query, variableValues = {}) {
const { data } = await graphql({ schema, source: query, variableValues })
return data || {}
}

0 comments on commit aef7f27

Please sign in to comment.