├── public ├── favicon.ico ├── octicons.eot ├── octicons.ttf ├── octicons.woff ├── octicons.woff2 ├── application.css ├── octicons.min.css └── octicons.svg ├── .babelrc ├── .gitignore ├── prettier.config.js ├── webpack.config.js ├── .eslintrc.json ├── src ├── Layout.js ├── index.js ├── template.html ├── createRelayEnvironment.js ├── RepositoryIcon.js ├── App.js ├── RepositoryListItem.js ├── RepositoryStar.js └── Dashboard.js ├── .graphqlconfig ├── package.json ├── README.md └── LICENSE /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react"], 3 | "plugins": ["relay"] 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __generated__/ 2 | dist/ 3 | node_modules/ 4 | schema.graphql 5 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('eslint-plugin-github/prettier.config') 2 | -------------------------------------------------------------------------------- /public/octicons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/github/github-graphql-relay-example/HEAD/public/octicons.eot -------------------------------------------------------------------------------- /public/octicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/github/github-graphql-relay-example/HEAD/public/octicons.ttf -------------------------------------------------------------------------------- /public/octicons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/github/github-graphql-relay-example/HEAD/public/octicons.woff -------------------------------------------------------------------------------- /public/octicons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/github/github-graphql-relay-example/HEAD/public/octicons.woff2 -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const config = require('webpack-config-github') 2 | 3 | module.exports = env => config(env, {template: 'src/template.html'}) 4 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true 4 | }, 5 | "extends": ["plugin:github/es6", "plugin:github/browser", "plugin:github/react"] 6 | } 7 | -------------------------------------------------------------------------------- /src/Layout.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function Layout({children}) { 4 | return
{children}
5 | } 6 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | 5 | ReactDOM.render(, document.getElementById('root')) 6 | -------------------------------------------------------------------------------- /.graphqlconfig: -------------------------------------------------------------------------------- 1 | { 2 | "schemaPath": "schema.graphql", 3 | "extensions": { 4 | "endpoints": { 5 | "production": { 6 | "url": "https://api.github.com/graphql", 7 | "headers": { 8 | "Authorization": "Bearer ${env:API_TOKEN}" 9 | } 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /src/createRelayEnvironment.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable github/no-then */ 2 | 3 | import {Environment, Network, RecordSource, Store} from 'relay-runtime' 4 | 5 | function fetchQuery(operation, variables) { 6 | return fetch('/graphql', { 7 | method: 'POST', 8 | headers: { 9 | Accept: 'application/json', 10 | 'Content-Type': 'application/json' 11 | }, 12 | body: JSON.stringify({query: operation.text, variables}) 13 | }).then(response => { 14 | return response.json() 15 | }) 16 | } 17 | 18 | const network = Network.create(fetchQuery) 19 | const source = new RecordSource() 20 | const store = new Store(source) 21 | 22 | export default new Environment({network, store}) 23 | -------------------------------------------------------------------------------- /src/RepositoryIcon.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {createFragmentContainer, graphql} from 'react-relay' 3 | 4 | export default createFragmentContainer( 5 | RepositoryIcon, 6 | graphql` 7 | fragment RepositoryIcon_repository on Repository { 8 | isFork 9 | isMirror 10 | isPrivate 11 | } 12 | ` 13 | ) 14 | 15 | function RepositoryIcon({repository}) { 16 | if (repository.isFork) { 17 | return 18 | } else if (repository.isPrivate) { 19 | return 20 | } else if (repository.isMirror) { 21 | return 22 | } else { 23 | return 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import {QueryRenderer, graphql} from 'react-relay' 4 | 5 | import environment from './createRelayEnvironment' 6 | 7 | import Layout from './Layout' 8 | import Dashboard from './Dashboard' 9 | 10 | function App() { 11 | const query = graphql` 12 | query AppQuery($count: Int!, $cursor: String) { 13 | ...Dashboard 14 | } 15 | ` 16 | 17 | const variables = { 18 | count: 10 19 | } 20 | 21 | return 22 | } 23 | 24 | function RenderApp({error, props}) { 25 | if (error) { 26 | return
{error.message}
27 | } else if (props) { 28 | return ( 29 | 30 | 31 | 32 | ) 33 | } else { 34 | return
Loading
35 | } 36 | } 37 | 38 | export default App 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "github-graphql-relay-example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "babel-preset-react": "^6.24.1", 7 | "react": "^16.1.1", 8 | "react-dom": "^16.1.1", 9 | "react-relay": "^1.4.1" 10 | }, 11 | "devDependencies": { 12 | "babel-core": "^6.26.0", 13 | "babel-loader": "^7.1.2", 14 | "babel-plugin-relay": "^1.4.1", 15 | "babel-runtime": "^6.26.0", 16 | "eslint": "^4.11.0", 17 | "eslint-plugin-github": "^0.19.1", 18 | "graphql": "^0.11.7", 19 | "graphql-cli": "^1.1.2", 20 | "node-fetch": "^1.7.3", 21 | "relay-compiler": "^1.4.1", 22 | "relay-runtime": "^1.4.1", 23 | "webpack": "^3.8.1", 24 | "webpack-config-github": "^0.2.2", 25 | "webpack-dev-server": "^2.9.4" 26 | }, 27 | "scripts": { 28 | "start": "yarn install && graphql get-schema && webpack-dev-server" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/RepositoryListItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {createFragmentContainer, graphql} from 'react-relay' 3 | 4 | import RepositoryIcon from './RepositoryIcon' 5 | import RepositoryStar from './RepositoryStar' 6 | 7 | export default createFragmentContainer( 8 | RepositoryListItem, 9 | graphql` 10 | fragment RepositoryListItem_repository on Repository { 11 | name 12 | owner { 13 | login 14 | } 15 | url 16 | ...RepositoryIcon_repository 17 | ...RepositoryStar_repository 18 | } 19 | ` 20 | ) 21 | 22 | function RepositoryListItem({repository}) { 23 | return ( 24 |
  • 25 | 26 | 27 | 28 | 29 | {repository.owner.login}/{repository.name} 30 | 31 |
  • 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitHub GraphQL Relay example application 2 | 3 | Demonstrates how to use [Relay Modern](https://facebook.github.io/relay/docs/relay-modern.html) to build a simple 4 | repository listing web view against the GitHub GraphQL API. 5 | 6 | screenshot 7 | 8 | ### Running locally 9 | 10 | First, you'll need a GitHub API access token to make GraphQL API requests. You can get that 11 | [here](https://github.com/settings/tokens/new). 12 | 13 | ``` 14 | $ git clone https://github.com/github/github-graphql-relay-example 15 | $ cd github-graphql-relay-example/ 16 | $ API_TOKEN=abc123 yarn start 17 | ``` 18 | 19 | Once your server is running, you can open http://localhost:3000. 20 | 21 | ## See also 22 | 23 | * [Ruby on Rails example using graphql-client](https://github.com/github/github-graphql-rails-example) 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Joshua Peek 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /public/application.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 20px; 3 | padding-bottom: 20px; 4 | } 5 | 6 | .header { 7 | margin-top: 0; 8 | margin-bottom: 0; 9 | } 10 | 11 | .header h3 { 12 | margin-top: 0; 13 | margin-bottom: 0; 14 | line-height: 40px; 15 | } 16 | 17 | .octicon { 18 | vertical-align: text-top; 19 | } 20 | 21 | .repositories { 22 | width: 350px; 23 | } 24 | 25 | .star-badge { 26 | float: right; 27 | color: #777; 28 | } 29 | 30 | .star-badge .octicon { 31 | margin-left: 3px; 32 | color: #777; 33 | } 34 | 35 | .star-badge .highlight { 36 | color: #e36209; 37 | } 38 | 39 | .nav-pills .badge { 40 | background-color: #ccc; 41 | } 42 | 43 | .show-more .spinner { 44 | display: none; 45 | } 46 | 47 | .show-more.loading .spinner { 48 | display: inline-block; 49 | float: right; 50 | animation-name: spin; 51 | animation-duration: 2000ms; 52 | animation-iteration-count: infinite; 53 | animation-timing-function: linear; 54 | padding-left: 4px; 55 | } 56 | 57 | @keyframes spin { 58 | from { 59 | transform: rotate(0deg); 60 | } to { 61 | transform: rotate(360deg); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/RepositoryStar.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/anchor-is-valid */ 2 | 3 | import React from 'react' 4 | import environment from './createRelayEnvironment' 5 | import {commitMutation, createFragmentContainer, graphql} from 'react-relay' 6 | 7 | function starMutation(starrableId) { 8 | const variables = { 9 | input: { 10 | starrableId 11 | } 12 | } 13 | 14 | commitMutation(environment, { 15 | variables, 16 | mutation: graphql` 17 | mutation RepositoryStarStarMutation($input: AddStarInput!) { 18 | addStar(input: $input) { 19 | starrable { 20 | ...RepositoryStar_repository 21 | } 22 | } 23 | } 24 | ` 25 | }) 26 | } 27 | 28 | function unstarMutation(starrableId) { 29 | const variables = { 30 | input: { 31 | starrableId 32 | } 33 | } 34 | 35 | commitMutation(environment, { 36 | variables, 37 | mutation: graphql` 38 | mutation RepositoryStarUnstarMutation($input: RemoveStarInput!) { 39 | removeStar(input: $input) { 40 | starrable { 41 | ...RepositoryStar_repository 42 | } 43 | } 44 | } 45 | ` 46 | }) 47 | } 48 | 49 | export default createFragmentContainer( 50 | RepositoryStar, 51 | graphql` 52 | fragment RepositoryStar_repository on Repository { 53 | id 54 | viewerHasStarred 55 | stargazers { 56 | totalCount 57 | } 58 | } 59 | ` 60 | ) 61 | 62 | function RepositoryStar({repository}) { 63 | const octiconClassName = repository.viewerHasStarred ? 'octicon octicon-star highlight' : 'octicon octicon-star' 64 | 65 | return ( 66 | 67 | {repository.stargazers.totalCount} 68 | { 71 | e.preventDefault() 72 | repository.viewerHasStarred ? unstarMutation(repository.id) : starMutation(repository.id) 73 | }} 74 | > 75 | 76 | 77 | 78 | ) 79 | } 80 | -------------------------------------------------------------------------------- /src/Dashboard.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/anchor-is-valid */ 2 | 3 | import React from 'react' 4 | import {createPaginationContainer, graphql} from 'react-relay' 5 | 6 | import RepositoryListItem from './RepositoryListItem' 7 | 8 | class Dashboard extends React.Component { 9 | loadMoreRepositories() { 10 | this.props.relay.loadMore(10, () => {}) 11 | } 12 | 13 | render() { 14 | const {viewer} = this.props.data 15 | 16 | return ( 17 |
    18 |
      19 |
    • 20 | Your repositories 21 | {viewer.repositories.totalCount} 22 |
    • 23 | 24 | {viewer.repositories.edges.map(edge => )} 25 | 26 | { 29 | event.preventDefault() 30 | this.loadMoreRepositories() 31 | }} 32 | /> 33 |
    34 |
    35 | ) 36 | } 37 | } 38 | 39 | function ShowMore({repositories, onClick}) { 40 | if (repositories.pageInfo.hasNextPage) { 41 | return ( 42 |
  • 43 | 44 | Show more repositories... 45 | 46 | 47 | 48 |
  • 49 | ) 50 | } else { 51 | return