├── .editorconfig ├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── flask ├── .gitignore ├── __init__.py ├── app.py ├── requirements.in ├── schema.py ├── test_graphiqlview.py ├── test_graphqlview.py └── test_scripts │ ├── list.sh │ └── toggle.sh ├── package.json ├── public ├── favicon.ico └── index.html └── src ├── components ├── Todo.css ├── Todo.js ├── TodoField.js └── TodoList.js ├── containers ├── App.css ├── App.js ├── App.test.js ├── Footer.js ├── Header.js ├── Home │ └── HomeApp.js ├── RemoteTodo │ ├── AddTodo.js │ └── RemoteTodoApp.js └── Todo │ ├── AddTodo.js │ ├── TodoApp.js │ └── VisibleTodoList.js ├── ducks └── todos.js ├── index.css ├── index.js ├── logo.svg └── redux ├── reducer.js └── store.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{py,rst,txt}] 4 | indent_style = space 5 | trim_trailing_whitespace = true 6 | indent_size = 4 7 | end_of_line = LF 8 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "react-app" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://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 15 | npm-debug.log 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Masaki Yatsu 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 | react-flask-graphql-example 2 | =========================== 3 | 4 | A personal one-shot example app using React, Flask, and GraphQL. 5 | 6 | Setup 7 | ----- 8 | 9 | ```sh 10 | % npm install 11 | % pip install -r flask/requirements.in 12 | ``` 13 | 14 | Usage 15 | ----- 16 | 17 | Launch the GraphQL API server: 18 | 19 | ```sh 20 | % cd flask 21 | % python app.py 22 | ``` 23 | 24 | Launch the development web server which serves the static client-side app: 25 | 26 | ```sh 27 | % npm start 28 | ``` 29 | 30 | Frameworks/Libraries 31 | -------------------- 32 | 33 | ### Client-side (JavaScript) 34 | 35 | * React 36 | * Redux 37 | * react-router and react-router-redux 38 | * Apollo client (GraphQL integration) 39 | * Semantic-UI-React (CSS framework) 40 | 41 | The application code was generated by create-react-app. 42 | 43 | See package.json for more information. 44 | 45 | ### Server-side (Python) 46 | 47 | * Flask 48 | * Flask-Cors 49 | * Flask-GraphQL 50 | 51 | Related Work 52 | ------------ 53 | 54 | * [react-tornado-graphql-example](https://github.com/yatsu/react-tornado-graphql-example) 55 | -------------------------------------------------------------------------------- /flask/.gitignore: -------------------------------------------------------------------------------- 1 | /graphqlview.py 2 | /render_graphiql.py 3 | -------------------------------------------------------------------------------- /flask/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yatsu/react-flask-graphql-example/18a38b7602c81a85a3cc38c74440ce34d63fc32a/flask/__init__.py -------------------------------------------------------------------------------- /flask/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_cors import CORS 3 | from flask_graphql import GraphQLView 4 | from schema import Schema 5 | 6 | 7 | def create_app(**kwargs): 8 | app = Flask(__name__) 9 | app.debug = True 10 | app.add_url_rule( 11 | '/graphql', 12 | view_func=GraphQLView.as_view('graphql', schema=Schema, **kwargs) 13 | ) 14 | return app 15 | 16 | 17 | if __name__ == '__main__': 18 | app = create_app(graphiql=True) 19 | CORS(app, resources={r'/graphql': {'origins': '*'}}) 20 | app.run() 21 | -------------------------------------------------------------------------------- /flask/requirements.in: -------------------------------------------------------------------------------- 1 | flask>=0.11.1,<0.12 2 | flask_cors>=3.0.2,<3.1 3 | graphql-core>=1.0.1,<1.1 4 | -------------------------------------------------------------------------------- /flask/schema.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple, OrderedDict 2 | from graphql import ( 3 | GraphQLField, GraphQLNonNull, GraphQLArgument, 4 | GraphQLObjectType, GraphQLList, GraphQLBoolean, GraphQLString, 5 | GraphQLSchema 6 | ) 7 | 8 | 9 | Todo = namedtuple('Todo', 'id text completed') 10 | 11 | TodoList = namedtuple('TodoList', 'todos') 12 | 13 | TodoType = GraphQLObjectType( 14 | name='Todo', 15 | fields=lambda: { 16 | 'id': GraphQLField( 17 | GraphQLNonNull(GraphQLString), 18 | ), 19 | 'text': GraphQLField( 20 | GraphQLString 21 | ), 22 | 'completed': GraphQLField( 23 | GraphQLBoolean 24 | ) 25 | } 26 | ) 27 | 28 | TodoListType = GraphQLObjectType( 29 | name='TodoList', 30 | fields=lambda: { 31 | 'todos': GraphQLField( 32 | GraphQLList(TodoType), 33 | resolver=lambda todo_list, *_: get_todos(todo_list), 34 | ) 35 | } 36 | ) 37 | 38 | 39 | todo_data = OrderedDict({ 40 | '1': Todo(id='1', text='Make America Great Again', completed=False), 41 | '2': Todo(id='2', text='Quit TPP', completed=False) 42 | }) 43 | 44 | 45 | def get_todo_list(): 46 | return TodoList(todos=todo_data.keys()) 47 | 48 | 49 | def get_todo(id): 50 | return todo_data.get(id) 51 | 52 | 53 | def get_todos(todo_list): 54 | return map(get_todo, todo_list.todos) 55 | 56 | 57 | def get_todo_single(): 58 | return Todo(id=1, text='Make America Great Again', completed=False) 59 | 60 | 61 | def add_todo(text): 62 | todo = Todo(id=str(len(todo_data) + 1), text=text, completed=False) 63 | todo_data[todo.id] = todo 64 | return todo 65 | 66 | 67 | def toggle_todo(id): 68 | cur_todo = todo_data[id] 69 | todo = Todo(id=id, text=cur_todo.text, completed=not cur_todo.completed) 70 | todo_data[id] = todo 71 | return todo 72 | 73 | 74 | QueryRootType = GraphQLObjectType( 75 | name='Query', 76 | fields=lambda: { 77 | 'test': GraphQLField( 78 | GraphQLString, 79 | args={ 80 | 'who': GraphQLArgument(GraphQLString) 81 | }, 82 | resolver=lambda root, args, *_: 83 | 'Hello %s' % (args.get('who') or 'World') 84 | ), 85 | 'todo': GraphQLField( 86 | TodoType, 87 | resolver=lambda root, args, *_: get_todo_single(), 88 | ), 89 | 'todoList': GraphQLField( 90 | TodoListType, 91 | resolver=lambda root, args, *_: get_todo_list(), 92 | ) 93 | } 94 | ) 95 | 96 | 97 | MutationRootType = GraphQLObjectType( 98 | name='Mutation', 99 | fields=lambda: { 100 | 'addTodo': GraphQLField( 101 | TodoType, 102 | args={ 103 | 'text': GraphQLArgument(GraphQLString) 104 | }, 105 | resolver=lambda root, args, *_: add_todo(args.get('text')) 106 | ), 107 | 'toggleTodo': GraphQLField( 108 | TodoType, 109 | args={ 110 | 'id': GraphQLArgument(GraphQLString) 111 | }, 112 | resolver=lambda root, args, *_: toggle_todo(args.get('id')) 113 | ) 114 | } 115 | ) 116 | 117 | Schema = GraphQLSchema(QueryRootType, MutationRootType) 118 | -------------------------------------------------------------------------------- /flask/test_graphiqlview.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from .app import create_app 4 | from flask import url_for 5 | 6 | 7 | @pytest.fixture 8 | def app(): 9 | return create_app(graphiql=True) 10 | 11 | 12 | def test_graphiql_is_enabled(client): 13 | response = client.get(url_for('graphql'), headers={'Accept': 'text/html'}) 14 | assert response.status_code == 200 15 | -------------------------------------------------------------------------------- /flask/test_graphqlview.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import json 3 | 4 | try: 5 | from StringIO import StringIO 6 | except ImportError: 7 | from io import StringIO 8 | 9 | try: 10 | from urllib import urlencode 11 | except ImportError: 12 | from urllib.parse import urlencode 13 | 14 | from .app import create_app 15 | from flask import url_for 16 | 17 | 18 | @pytest.fixture 19 | def app(): 20 | return create_app() 21 | 22 | def url_string(**url_params): 23 | string = url_for('graphql') 24 | 25 | if url_params: 26 | string += '?' + urlencode(url_params) 27 | 28 | return string 29 | 30 | 31 | def response_json(response): 32 | return json.loads(response.data.decode()) 33 | 34 | 35 | j = lambda **kwargs: json.dumps(kwargs) 36 | 37 | def test_allows_get_with_query_param(client): 38 | response = client.get(url_string(query='{test}')) 39 | 40 | assert response.status_code == 200 41 | assert response_json(response) == { 42 | 'data': {'test': "Hello World"} 43 | } 44 | 45 | 46 | def test_allows_get_with_variable_values(client): 47 | response = client.get(url_string( 48 | query='query helloWho($who: String){ test(who: $who) }', 49 | variables=json.dumps({'who': "Dolly"}) 50 | )) 51 | 52 | assert response.status_code == 200 53 | assert response_json(response) == { 54 | 'data': {'test': "Hello Dolly"} 55 | } 56 | 57 | 58 | def test_allows_get_with_operation_name(client): 59 | response = client.get(url_string( 60 | query=''' 61 | query helloYou { test(who: "You"), ...shared } 62 | query helloWorld { test(who: "World"), ...shared } 63 | query helloDolly { test(who: "Dolly"), ...shared } 64 | fragment shared on QueryRoot { 65 | shared: test(who: "Everyone") 66 | } 67 | ''', 68 | operationName='helloWorld' 69 | )) 70 | 71 | assert response.status_code == 200 72 | assert response_json(response) == { 73 | 'data': { 74 | 'test': 'Hello World', 75 | 'shared': 'Hello Everyone' 76 | } 77 | } 78 | 79 | 80 | def test_reports_validation_errors(client): 81 | response = client.get(url_string( 82 | query='{ test, unknownOne, unknownTwo }' 83 | )) 84 | 85 | assert response.status_code == 400 86 | assert response_json(response) == { 87 | 'errors': [ 88 | { 89 | 'message': 'Cannot query field "unknownOne" on type "QueryRoot".', 90 | 'locations': [{'line': 1, 'column': 9}] 91 | }, 92 | { 93 | 'message': 'Cannot query field "unknownTwo" on type "QueryRoot".', 94 | 'locations': [{'line': 1, 'column': 21}] 95 | } 96 | ] 97 | } 98 | 99 | 100 | def test_errors_when_missing_operation_name(client): 101 | response = client.get(url_string( 102 | query=''' 103 | query TestQuery { test } 104 | mutation TestMutation { writeTest { test } } 105 | ''' 106 | )) 107 | 108 | assert response.status_code == 400 109 | assert response_json(response) == { 110 | 'errors': [ 111 | { 112 | 'message': 'Must provide operation name if query contains multiple operations.' 113 | } 114 | ] 115 | } 116 | 117 | 118 | def test_errors_when_sending_a_mutation_via_get(client): 119 | response = client.get(url_string( 120 | query=''' 121 | mutation TestMutation { writeTest { test } } 122 | ''' 123 | )) 124 | assert response.status_code == 405 125 | assert response_json(response) == { 126 | 'errors': [ 127 | { 128 | 'message': 'Can only perform a mutation operation from a POST request.' 129 | } 130 | ] 131 | } 132 | 133 | 134 | def test_errors_when_selecting_a_mutation_within_a_get(client): 135 | response = client.get(url_string( 136 | query=''' 137 | query TestQuery { test } 138 | mutation TestMutation { writeTest { test } } 139 | ''', 140 | operationName='TestMutation' 141 | )) 142 | 143 | assert response.status_code == 405 144 | assert response_json(response) == { 145 | 'errors': [ 146 | { 147 | 'message': 'Can only perform a mutation operation from a POST request.' 148 | } 149 | ] 150 | } 151 | 152 | 153 | def test_allows_mutation_to_exist_within_a_get(client): 154 | response = client.get(url_string( 155 | query=''' 156 | query TestQuery { test } 157 | mutation TestMutation { writeTest { test } } 158 | ''', 159 | operationName='TestQuery' 160 | )) 161 | 162 | assert response.status_code == 200 163 | assert response_json(response) == { 164 | 'data': {'test': "Hello World"} 165 | } 166 | 167 | 168 | def test_allows_post_with_json_encoding(client): 169 | response = client.post(url_string(), data=j(query='{test}'), content_type='application/json') 170 | 171 | assert response.status_code == 200 172 | assert response_json(response) == { 173 | 'data': {'test': "Hello World"} 174 | } 175 | 176 | 177 | def test_allows_sending_a_mutation_via_post(client): 178 | response = client.post(url_string(), data=j(query='mutation TestMutation { writeTest { test } }'), content_type='application/json') 179 | 180 | assert response.status_code == 200 181 | assert response_json(response) == { 182 | 'data': {'writeTest': {'test': 'Hello World'}} 183 | } 184 | 185 | 186 | def test_allows_post_with_url_encoding(client): 187 | response = client.post(url_string(), data=urlencode(dict(query='{test}')), content_type='application/x-www-form-urlencoded') 188 | 189 | assert response.status_code == 200 190 | assert response_json(response) == { 191 | 'data': {'test': "Hello World"} 192 | } 193 | 194 | 195 | def test_supports_post_json_query_with_string_variables(client): 196 | response = client.post(url_string(), data=j( 197 | query='query helloWho($who: String){ test(who: $who) }', 198 | variables=json.dumps({'who': "Dolly"}) 199 | ), content_type='application/json') 200 | 201 | assert response.status_code == 200 202 | assert response_json(response) == { 203 | 'data': {'test': "Hello Dolly"} 204 | } 205 | 206 | 207 | def test_supports_post_json_query_with_json_variables(client): 208 | response = client.post(url_string(), data=j( 209 | query='query helloWho($who: String){ test(who: $who) }', 210 | variables={'who': "Dolly"} 211 | ), content_type='application/json') 212 | 213 | assert response.status_code == 200 214 | assert response_json(response) == { 215 | 'data': {'test': "Hello Dolly"} 216 | } 217 | 218 | 219 | def test_supports_post_url_encoded_query_with_string_variables(client): 220 | response = client.post(url_string(), data=urlencode(dict( 221 | query='query helloWho($who: String){ test(who: $who) }', 222 | variables=json.dumps({'who': "Dolly"}) 223 | )), content_type='application/x-www-form-urlencoded') 224 | 225 | assert response.status_code == 200 226 | assert response_json(response) == { 227 | 'data': {'test': "Hello Dolly"} 228 | } 229 | 230 | 231 | def test_supports_post_json_quey_with_get_variable_values(client): 232 | response = client.post(url_string( 233 | variables=json.dumps({'who': "Dolly"}) 234 | ), data=j( 235 | query='query helloWho($who: String){ test(who: $who) }', 236 | ), content_type='application/json') 237 | 238 | assert response.status_code == 200 239 | assert response_json(response) == { 240 | 'data': {'test': "Hello Dolly"} 241 | } 242 | 243 | 244 | def test_post_url_encoded_query_with_get_variable_values(client): 245 | response = client.post(url_string( 246 | variables=json.dumps({'who': "Dolly"}) 247 | ), data=urlencode(dict( 248 | query='query helloWho($who: String){ test(who: $who) }', 249 | )), content_type='application/x-www-form-urlencoded') 250 | 251 | assert response.status_code == 200 252 | assert response_json(response) == { 253 | 'data': {'test': "Hello Dolly"} 254 | } 255 | 256 | 257 | def test_supports_post_raw_text_query_with_get_variable_values(client): 258 | response = client.post(url_string( 259 | variables=json.dumps({'who': "Dolly"}) 260 | ), 261 | data='query helloWho($who: String){ test(who: $who) }', 262 | content_type='application/graphql' 263 | ) 264 | 265 | assert response.status_code == 200 266 | assert response_json(response) == { 267 | 'data': {'test': "Hello Dolly"} 268 | } 269 | 270 | 271 | def test_allows_post_with_operation_name(client): 272 | response = client.post(url_string(), data=j( 273 | query=''' 274 | query helloYou { test(who: "You"), ...shared } 275 | query helloWorld { test(who: "World"), ...shared } 276 | query helloDolly { test(who: "Dolly"), ...shared } 277 | fragment shared on QueryRoot { 278 | shared: test(who: "Everyone") 279 | } 280 | ''', 281 | operationName='helloWorld' 282 | ), content_type='application/json') 283 | 284 | assert response.status_code == 200 285 | assert response_json(response) == { 286 | 'data': { 287 | 'test': 'Hello World', 288 | 'shared': 'Hello Everyone' 289 | } 290 | } 291 | 292 | 293 | def test_allows_post_with_get_operation_name(client): 294 | response = client.post(url_string( 295 | operationName='helloWorld' 296 | ), data=''' 297 | query helloYou { test(who: "You"), ...shared } 298 | query helloWorld { test(who: "World"), ...shared } 299 | query helloDolly { test(who: "Dolly"), ...shared } 300 | fragment shared on QueryRoot { 301 | shared: test(who: "Everyone") 302 | } 303 | ''', 304 | content_type='application/graphql') 305 | 306 | assert response.status_code == 200 307 | assert response_json(response) == { 308 | 'data': { 309 | 'test': 'Hello World', 310 | 'shared': 'Hello Everyone' 311 | } 312 | } 313 | 314 | 315 | @pytest.mark.parametrize('app', [create_app(pretty=True)]) 316 | def test_supports_pretty_printing(client): 317 | response = client.get(url_string(query='{test}')) 318 | 319 | assert response.data.decode() == ( 320 | '{\n' 321 | ' "data": {\n' 322 | ' "test": "Hello World"\n' 323 | ' }\n' 324 | '}' 325 | ) 326 | 327 | 328 | def test_supports_pretty_printing_by_request(client): 329 | response = client.get(url_string(query='{test}', pretty='1')) 330 | 331 | assert response.data.decode() == ( 332 | '{\n' 333 | ' "data": {\n' 334 | ' "test": "Hello World"\n' 335 | ' }\n' 336 | '}' 337 | ) 338 | 339 | 340 | def test_handles_field_errors_caught_by_graphql(client): 341 | response = client.get(url_string(query='{thrower}')) 342 | assert response.status_code == 200 343 | assert response_json(response) == { 344 | 'data': None, 345 | 'errors': [{'locations': [{'column': 2, 'line': 1}], 'message': 'Throws!'}] 346 | } 347 | 348 | 349 | def test_handles_syntax_errors_caught_by_graphql(client): 350 | response = client.get(url_string(query='syntaxerror')) 351 | assert response.status_code == 400 352 | assert response_json(response) == { 353 | 'errors': [{'locations': [{'column': 1, 'line': 1}], 354 | 'message': 'Syntax Error GraphQL request (1:1) ' 355 | 'Unexpected Name "syntaxerror"\n\n1: syntaxerror\n ^\n'}] 356 | } 357 | 358 | 359 | def test_handles_errors_caused_by_a_lack_of_query(client): 360 | response = client.get(url_string()) 361 | 362 | assert response.status_code == 400 363 | assert response_json(response) == { 364 | 'errors': [{'message': 'Must provide query string.'}] 365 | } 366 | 367 | 368 | def test_handles_invalid_json_bodies(client): 369 | response = client.post(url_string(), data='[]', content_type='application/json') 370 | 371 | assert response.status_code == 400 372 | assert response_json(response) == { 373 | 'errors': [{'message': 'POST body sent invalid JSON.'}] 374 | } 375 | 376 | 377 | def test_handles_incomplete_json_bodies(client): 378 | response = client.post(url_string(), data='{"query":', content_type='application/json') 379 | 380 | assert response.status_code == 400 381 | assert response_json(response) == { 382 | 'errors': [{'message': 'POST body sent invalid JSON.'}] 383 | } 384 | 385 | 386 | def test_handles_plain_post_text(client): 387 | response = client.post(url_string( 388 | variables=json.dumps({'who': "Dolly"}) 389 | ), 390 | data='query helloWho($who: String){ test(who: $who) }', 391 | content_type='text/plain' 392 | ) 393 | assert response.status_code == 400 394 | assert response_json(response) == { 395 | 'errors': [{'message': 'Must provide query string.'}] 396 | } 397 | 398 | 399 | def test_handles_poorly_formed_variables(client): 400 | response = client.get(url_string( 401 | query='query helloWho($who: String){ test(who: $who) }', 402 | variables='who:You' 403 | )) 404 | assert response.status_code == 400 405 | assert response_json(response) == { 406 | 'errors': [{'message': 'Variables are invalid JSON.'}] 407 | } 408 | 409 | 410 | def test_handles_unsupported_http_methods(client): 411 | response = client.put(url_string(query='{test}')) 412 | assert response.status_code == 405 413 | assert response.headers['Allow'] in ['GET, POST', 'HEAD, GET, POST, OPTIONS'] 414 | assert response_json(response) == { 415 | 'errors': [{'message': 'GraphQL only supports GET and POST requests.'}] 416 | } 417 | 418 | 419 | def test_passes_request_into_request_context(client): 420 | response = client.get(url_string(query='{request}', q='testing')) 421 | 422 | assert response.status_code == 200 423 | assert response_json(response) == { 424 | 'data': { 425 | 'request': 'testing' 426 | } 427 | } 428 | 429 | def test_post_multipart_data(client): 430 | query = 'mutation TestMutation { writeTest { test } }' 431 | response = client.post( 432 | url_string(), 433 | data= { 434 | 'query': query, 435 | 'file': (StringIO(), 'text1.txt'), 436 | }, 437 | content_type='multipart/form-data' 438 | ) 439 | 440 | assert response.status_code == 200 441 | assert response_json(response) == {'data': {u'writeTest': {u'test': u'Hello World'}}} 442 | -------------------------------------------------------------------------------- /flask/test_scripts/list.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo 'query TodoListQuery { todoList { todos { id text completed } } }' | http post http://localhost:5000/graphql content-type:application/graphql 4 | -------------------------------------------------------------------------------- /flask/test_scripts/toggle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo \ 4 | '{ 5 | "query": "mutation toggleTodo($id: String!) { toggleTodo(id: $id) { completed __typename } }", 6 | "variables": {"id": "2"}, 7 | "operationName": "toggleTodo" 8 | }' | http post http://localhost:5000/graphql content-type:application/json 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-flask-graphql-example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "babel-eslint": "7.0.0", 7 | "eslint": "3.8.1", 8 | "eslint-config-react-app": "0.3.0", 9 | "eslint-plugin-babel": "3.3.0", 10 | "eslint-plugin-flowtype": "2.21.0", 11 | "eslint-plugin-import": "2.0.1", 12 | "eslint-plugin-jsx-a11y": "2.2.3", 13 | "eslint-plugin-react": "6.4.1", 14 | "react-scripts": "0.7.0" 15 | }, 16 | "dependencies": { 17 | "apollo-client": "0.5.5", 18 | "classnames": "2.2.5", 19 | "graphql-anywhere": "0.2.4", 20 | "graphql-fragments": "0.1.0", 21 | "graphql-tag": "0.1.16", 22 | "immutable": "3.8.1", 23 | "react": "15.3.2", 24 | "react-apollo": "0.6.0", 25 | "react-dom": "15.3.2", 26 | "react-immutable-proptypes": "2.1.0", 27 | "react-redux": "4.4.5", 28 | "react-router": "3.0.0", 29 | "react-router-redux": "4.0.7", 30 | "redux": "3.6.0", 31 | "semantic-ui-react": "0.60.4" 32 | }, 33 | "scripts": { 34 | "start": "react-scripts start", 35 | "build": "react-scripts build", 36 | "test": "react-scripts test --env=jsdom", 37 | "eject": "react-scripts eject", 38 | "lint": "eslint src" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yatsu/react-flask-graphql-example/18a38b7602c81a85a3cc38c74440ce34d63fc32a/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 18 |Extra space for a call to action inside the footer that could help re-engage users.
40 |