├── .github └── FUNDING.yml ├── .gitignore ├── .prettierrc ├── .travis.yml ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json └── src ├── components ├── Account │ └── index.js ├── Admin │ └── index.js ├── App │ └── index.js ├── Error │ └── index.js ├── Landing │ └── index.js ├── Loading │ └── index.js ├── Message │ ├── MessageCreate │ │ └── index.js │ ├── MessageDelete │ │ └── index.js │ ├── Messages │ │ └── index.js │ └── index.js ├── Navigation │ └── index.js ├── Session │ ├── queries.js │ ├── withAuthorization.js │ └── withSession.js ├── SignIn │ └── index.js ├── SignOut │ └── index.js └── SignUp │ └── index.js ├── constants ├── history.js └── routes.js └── index.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: rwieruch 4 | patreon: # rwieruch 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with a single custom sponsorship URL 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "all", 4 | "singleQuote": true, 5 | "printWidth": 70, 6 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - stable 5 | 6 | install: 7 | - npm install 8 | 9 | script: 10 | - npm test 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Robin Wieruch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fullstack-apollo-react-boilerplate 2 | 3 | [![Build Status](https://travis-ci.org/the-road-to-graphql/fullstack-apollo-react-boilerplate.svg?branch=master)](https://travis-ci.org/the-road-to-graphql/fullstack-apollo-react-boilerplate) [![Slack](https://slack-the-road-to-learn-react.wieruch.com/badge.svg)](https://slack-the-road-to-learn-react.wieruch.com/) [![Greenkeeper badge](https://badges.greenkeeper.io/the-road-to-graphql/fullstack-apollo-react-boilerplate.svg)](https://greenkeeper.io/) 4 | 5 | A full-fledged Apollo Server with Apollo Client starter project with React and Express. [Read more about it in this tutorial to build it yourself](https://www.robinwieruch.de/graphql-apollo-server-tutorial/). 6 | 7 | **Family of universal fullstack repositories:** 8 | 9 | Server Applications: 10 | 11 | * [Node.js with Express + MongoDB](https://github.com/the-road-to-graphql/fullstack-apollo-express-mongodb-boilerplate) 12 | * [Node.js with Express + PostgreSQL](https://github.com/the-road-to-graphql/fullstack-apollo-express-postgresql-boilerplate) 13 | 14 | Client Applications: 15 | 16 | * [React Client](https://github.com/the-road-to-graphql/fullstack-apollo-react-boilerplate) 17 | 18 | ## Features of Client + Server 19 | 20 | * React (create-react-app) with Apollo Client 21 | * Queries, Mutations, Subscriptions 22 | * Node.js with Express and Apollo Server 23 | * cursor-based Pagination 24 | * PostgreSQL Database with Sequelize or MongoDB 25 | * entities: users, messages 26 | * Authentication 27 | * powered by JWT and local storage 28 | * Sign Up, Sign In, Sign Out 29 | * Authorization 30 | * protected endpoint (e.g. verify valid session) 31 | * protected resolvers (e.g. e.g. session-based, role-based) 32 | * protected routes (e.g. session-based, role-based) 33 | * performance optimizations 34 | * example of using Facebook's dataloader 35 | * E2E testing 36 | 37 | ## Installation 38 | 39 | * `git clone git@github.com:the-road-to-graphql/fullstack-apollo-react-boilerplate.git` 40 | * `cd fullstack-apollo-react-boilerplate` 41 | * `npm install` 42 | * `npm start` 43 | * visit `http://localhost:3000` 44 | 45 | See Server Installation instructions in the other GitHub repository. 46 | 47 | ## Want to learn more about React + GraphQL + Apollo? 48 | 49 | * Don't miss [upcoming Tutorials and Courses](https://www.getrevue.co/profile/rwieruch) 50 | * Check out current [React Courses](https://roadtoreact.com) 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fullstack-apollo-react-boilerplate-project", 3 | "version": "0.1.0", 4 | "author": "Robin Wieruch (https://www.robinwieruch.de)", 5 | "dependencies": { 6 | "apollo-cache-inmemory": "^1.3.7", 7 | "apollo-client": "^2.4.4", 8 | "apollo-link": "^1.2.3", 9 | "apollo-link-error": "^1.1.1", 10 | "apollo-link-http": "^1.5.5", 11 | "apollo-link-ws": "^1.0.9", 12 | "apollo-utilities": "^1.0.24", 13 | "graphql": "^14.0.2", 14 | "graphql-tag": "^2.10.0", 15 | "history": "^4.7.2", 16 | "react": "^16.6.0", 17 | "react-apollo": "^2.2.4", 18 | "react-dom": "^16.6.0", 19 | "react-router-dom": "^4.3.1", 20 | "react-scripts": "3.1.0", 21 | "subscriptions-transport-ws": "^0.9.15" 22 | }, 23 | "scripts": { 24 | "start": "react-scripts start", 25 | "build": "react-scripts build", 26 | "test": "react-scripts test --env=jsdom --passWithNoTests", 27 | "eject": "react-scripts eject" 28 | }, 29 | "browserslist": [ 30 | ">0.2%", 31 | "not dead", 32 | "not ie <= 11", 33 | "not op_mini all" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-road-to-graphql/fullstack-apollo-react-boilerplate/f2350ff2dcdab4487ba6f2e5f056efc5ca280758/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/components/Account/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import withAuthorization from '../Session/withAuthorization'; 4 | 5 | const AccountPage = () => ( 6 |
7 |

Account Page

8 |
9 | ); 10 | 11 | export default withAuthorization(session => session && session.me)( 12 | AccountPage, 13 | ); 14 | -------------------------------------------------------------------------------- /src/components/Admin/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import withAuthorization from '../Session/withAuthorization'; 4 | 5 | const AdminPage = () => ( 6 |
7 |

Admin Page

8 |
9 | ); 10 | 11 | export default withAuthorization( 12 | session => session && session.me && session.me.role === 'ADMIN', 13 | )(AdminPage); 14 | -------------------------------------------------------------------------------- /src/components/App/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Router, Route } from 'react-router-dom'; 3 | 4 | import Navigation from '../Navigation'; 5 | import LandingPage from '../Landing'; 6 | import SignUpPage from '../SignUp'; 7 | import SignInPage from '../SignIn'; 8 | import AccountPage from '../Account'; 9 | import AdminPage from '../Admin'; 10 | import withSession from '../Session/withSession'; 11 | 12 | import * as routes from '../../constants/routes'; 13 | import history from '../../constants/history'; 14 | 15 | const App = ({ session, refetch }) => ( 16 | 17 |
18 | 19 | 20 |
21 | 22 | } 26 | /> 27 | } 31 | /> 32 | } 36 | /> 37 | } 41 | /> 42 | } 46 | /> 47 |
48 |
49 | ); 50 | 51 | export default withSession(App); 52 | -------------------------------------------------------------------------------- /src/components/Error/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const ErrorMessage = ({ error }) => ( 4 |
5 | {error.message} 6 |
7 | ); 8 | 9 | export default ErrorMessage; 10 | -------------------------------------------------------------------------------- /src/components/Landing/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import withSession from '../Session/withSession'; 4 | 5 | import { MessageCreate, Messages } from '../Message'; 6 | 7 | const Landing = ({ session }) => ( 8 |
9 |

Landing Page

10 | 11 | {session && session.me && } 12 | 13 |
14 | ); 15 | 16 | export default withSession(Landing); 17 | -------------------------------------------------------------------------------- /src/components/Loading/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Loading = () =>
Loading ...
; 4 | 5 | export default Loading; 6 | -------------------------------------------------------------------------------- /src/components/Message/MessageCreate/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Mutation } from 'react-apollo'; 3 | import gql from 'graphql-tag'; 4 | 5 | import ErrorMessage from '../../Error'; 6 | 7 | const CREATE_MESSAGE = gql` 8 | mutation($text: String!) { 9 | createMessage(text: $text) { 10 | id 11 | text 12 | createdAt 13 | user { 14 | id 15 | username 16 | } 17 | } 18 | } 19 | `; 20 | 21 | class MessageCreate extends Component { 22 | state = { 23 | text: '', 24 | }; 25 | 26 | onChange = event => { 27 | const { name, value } = event.target; 28 | this.setState({ [name]: value }); 29 | }; 30 | 31 | onSubmit = async (event, createMessage) => { 32 | event.preventDefault(); 33 | 34 | try { 35 | await createMessage(); 36 | this.setState({ text: '' }); 37 | } catch (error) {} 38 | }; 39 | 40 | render() { 41 | const { text } = this.state; 42 | 43 | return ( 44 | { 50 | // const data = cache.readQuery({ 51 | // query: GET_ALL_MESSAGES_WITH_USERS, 52 | // }); 53 | 54 | // cache.writeQuery({ 55 | // query: GET_ALL_MESSAGES_WITH_USERS, 56 | // data: { 57 | // ...data, 58 | // messages: { 59 | // ...data.messages, 60 | // edges: [createMessage, ...data.messages.edges], 61 | // pageInfo: data.messages.pageInfo, 62 | // }, 63 | // }, 64 | // }); 65 | // }} 66 | > 67 | {(createMessage, { data, loading, error }) => ( 68 |
this.onSubmit(event, createMessage)} 70 | > 71 |