Skip to content

Commit

Permalink
New example: with-relay-modern-typescript
Browse files Browse the repository at this point in the history
  • Loading branch information
bdombro committed Mar 2, 2020
1 parent 8f01a4a commit 1285a64
Show file tree
Hide file tree
Showing 16 changed files with 365 additions and 0 deletions.
8 changes: 8 additions & 0 deletions examples/with-relay-modern-typescript/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"presets": [
"next/babel"
],
"plugins": [
"relay"
]
}
1 change: 1 addition & 0 deletions examples/with-relay-modern-typescript/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
RELAY_ENDPOINT=https://api.graph.cool/relay/v1/next-js-with-relay-modern-example
2 changes: 2 additions & 0 deletions examples/with-relay-modern-typescript/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__generated__/
schema/schema.graphql
8 changes: 8 additions & 0 deletions examples/with-relay-modern-typescript/.graphqlconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"schemaPath": "schema/schema.graphql",
"extensions": {
"endpoints": {
"dev": "https://api.graph.cool/relay/v1/next-js-with-relay-modern-example"
}
}
}
66 changes: 66 additions & 0 deletions examples/with-relay-modern-typescript/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Relay Modern Typescript Example

One of the strengths of GraphQL is [enforcing data types on runtime](https://graphql.github.io/graphql-spec/June2018/#sec-Value-Completion). Further, TypeScript and [Relay Compiler](https://www.npmjs.com/package/relay-compiler) make it safer by typing data statically, so you can write truly type-protected code with rich IDE assists.

This template extends [Relay Modern Example](https://github.com/zeit/next.js/tree/canary/examples/with-relay-modern) by rewriting in TypeScript and integrating type generation care of [Relay Compiler](https://www.npmjs.com/package/relay-compiler) under the hood.

## Deploy your own

Deploy the example using [ZEIT Now](https://zeit.co/now):

[![Deploy with ZEIT Now](https://zeit.co/button)](https://zeit.co/import/project?template=https://github.com/zeit/next.js/tree/canary/examples/with-relay-modern)

## 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 with-relay-modern with-relay-modern-app
# or
yarn create next-app --example with-relay-modern with-relay-modern-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/with-relay-modern-typescript
cd with-relay-modern-typescript
```

Install it:

```bash
npm install
# or
yarn
```

Download schema introspection data from configured Relay endpoint

```bash
npm run schema
# or
yarn schema
```

Run Relay ahead-of-time compilation (should be re-run after any edits to components that query data with Relay)

```bash
npm run relay
# or
yarn relay
```

Run the project

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

Deploy it to the cloud with [ZEIT Now](https://zeit.co/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react'
import { createFragmentContainer, graphql } from 'react-relay'
import { BlogPostPreview_post } from './__generated__/BlogPostPreview_post.graphql'

const BlogPostPreview = ({ post }: { post: BlogPostPreview_post }) => (
<li>{post.title}</li>
)

export default createFragmentContainer(BlogPostPreview, {
post: graphql`
fragment BlogPostPreview_post on BlogPost {
id
title
}
`,
})
30 changes: 30 additions & 0 deletions examples/with-relay-modern-typescript/components/BlogPosts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react'
import { createFragmentContainer, graphql } from 'react-relay'
import BlogPostPreview from './BlogPostPreview'
import { BlogPosts_viewer } from './__generated__/BlogPosts_viewer.graphql'

const BlogPosts = ({ viewer }: { viewer: BlogPosts_viewer }) => (
<div>
<h1>Blog posts</h1>
<ul>
{viewer.allBlogPosts.edges?.map(
e => e?.node && <BlogPostPreview key={e.node.id} post={e.node} />
)}
</ul>
</div>
)

export default createFragmentContainer(BlogPosts, {
viewer: graphql`
fragment BlogPosts_viewer on Viewer {
allBlogPosts(first: 10, orderBy: createdAt_DESC) {
edges {
node {
...BlogPostPreview_post
id
}
}
}
}
`,
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {
Environment,
Network,
RecordSource,
Store,
FetchFunction,
} from 'relay-runtime'
import fetch from 'isomorphic-unfetch'

let relayEnvironment: Environment | null = null

// Define a function that fetches the results of an operation (query/mutation/etc)
// and returns its results as a Promise:
const fetchQuery: FetchFunction = (operation, variables) => {
return fetch(process.env.RELAY_ENDPOINT as string, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
}, // Add authentication and other headers here
body: JSON.stringify({
query: operation.text, // GraphQL text from input
variables,
}),
}).then(response => response.json())
}

export default function initEnvironment({ records = {} } = {}): Environment {
// Create a network layer from the fetch function
const network = Network.create(fetchQuery)
const store = new Store(new RecordSource(records))

// Make sure to create a new Relay environment for every server-side request so that data
// isn't shared between connections (which would be bad)
if (typeof window === 'undefined') {
return new Environment({
network,
store,
})
}

// reuse Relay environment on client-side
if (!relayEnvironment) {
relayEnvironment = new Environment({
network,
store,
})
}

return relayEnvironment
}
65 changes: 65 additions & 0 deletions examples/with-relay-modern-typescript/lib/withData.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React from 'react'
import { NextPage, NextPageContext } from 'next'
import { fetchQuery, ReactRelayContext } from 'react-relay'
import { Environment, GraphQLTaggedNode } from 'relay-runtime'
import initEnvironment from './createRelayEnvironment'

export default (
ComposedComponent: NextPage,
options: { query?: GraphQLTaggedNode } = {}
) => {
return class WithData extends React.Component {
static displayName = `WithData(${ComposedComponent.displayName})`
environment: Environment

static async getInitialProps(ctx: NextPageContext) {
// Evaluate the composed component's getInitialProps()
let composedInitialProps = {}
if (ComposedComponent.getInitialProps) {
composedInitialProps = await ComposedComponent.getInitialProps(ctx)
}

let queryProps = {}
let queryRecords = {}
const environment = initEnvironment()

if (options.query) {
// Provide the `url` prop data in case a graphql query uses it
// const url = { query: ctx.query, pathname: ctx.pathname }
const variables = {}
// TODO: Consider RelayQueryResponseCache
// https://github.com/facebook/relay/issues/1687#issuecomment-302931855
queryProps = (await fetchQuery(
environment,
options.query,
variables
)) as any
queryRecords = environment
.getStore()
.getSource()
.toJSON()
}

return {
...composedInitialProps,
...queryProps,
queryRecords,
}
}

constructor(props: any) {
super(props)
this.environment = initEnvironment({
records: props.queryRecords,
})
}

render() {
return (
<ReactRelayContext.Provider value={{ environment: this.environment }}>
<ComposedComponent {...this.props} />
</ReactRelayContext.Provider>
)
}
}
}
2 changes: 2 additions & 0 deletions examples/with-relay-modern-typescript/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
22 changes: 22 additions & 0 deletions examples/with-relay-modern-typescript/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
require('dotenv').config()

const path = require('path')
const Dotenv = require('dotenv-webpack')

module.exports = {
webpack: config => {
config.plugins = config.plugins || []

config.plugins = [
...config.plugins,

// Read the .env file
new Dotenv({
path: path.join(__dirname, '.env'),
systemvars: true,
}),
]

return config
},
}
38 changes: 38 additions & 0 deletions examples/with-relay-modern-typescript/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "with-relay-modern-typescript",
"version": "3.0.4",
"description": "Example of Next.js with Relay Modern SSR and Typescript",
"scripts": {
"graphcool-init": "graphcool init --schema schema/init-schema.graphql",
"dev": "next",
"build": "next build",
"start": "next start",
"relay": "relay-compiler --language typescript --src ./ --exclude '**/.next/**' '**/node_modules/**' '**/test/**' '**/__generated__/**' --exclude '**/schema/**' --schema ./schema/schema.graphql",
"schema": "graphql get-schema -e dev"
},
"author": "",
"license": "ISC",
"dependencies": {
"dotenv": "^8.2.0",
"dotenv-webpack": "^1.7.0",
"graphql": "^14.5.8",
"isomorphic-unfetch": "^3.0.0",
"next": "latest",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-relay": "^8.0.0"
},
"devDependencies": {
"@types/node": "13.7.7",
"@types/react": "16.9.23",
"@types/react-dom": "16.9.5",
"@types/react-relay": "7.0.3",
"@types/relay-runtime": "8.0.6",
"babel-plugin-relay": "^8.0.0",
"graphcool": "^1.4.0",
"graphql-cli": "^3.0.14",
"relay-compiler": "^8.0.0",
"relay-compiler-language-typescript": "^12.0.0",
"typescript": "3.8.3"
}
}
16 changes: 16 additions & 0 deletions examples/with-relay-modern-typescript/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react'
import { NextPage, NextPageContext } from 'next'
import withData from '../lib/withData'
import BlogPosts from '../components/BlogPosts'
import indexPageQuery from '../queries/indexPage'
import { indexPage_indexQueryResponse } from '../queries/__generated__/indexPage_indexQuery.graphql'

const Index = ({ viewer }: NextPageContext & indexPage_indexQueryResponse) => (
<div>
<BlogPosts viewer={viewer} />
</div>
)

export default withData(Index as NextPage, {
query: indexPageQuery,
})
9 changes: 9 additions & 0 deletions examples/with-relay-modern-typescript/queries/indexPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { graphql } from 'react-relay'

export default graphql`
query indexPage_indexQuery {
viewer {
...BlogPosts_viewer
}
}
`
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
type BlogPost implements Node {
content: String!
createdAt: DateTime!
id: ID! @isUnique
title: String!
updatedAt: DateTime!
}
24 changes: 24 additions & 0 deletions examples/with-relay-modern-typescript/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"compilerOptions": {
"allowJs": true,
"alwaysStrict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"jsx": "preserve",
"lib": ["dom", "es2017"],
"module": "esnext",
"moduleResolution": "node",
"noEmit": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
"target": "esnext",
"allowSyntheticDefaultImports": true
},
"exclude": ["node_modules"],
"include": ["**/*.ts", "**/*.tsx"]
}

0 comments on commit 1285a64

Please sign in to comment.