Repository files navigation

Graphy - a demo of Graphene-Django

Code and demonstation of the transition from Django REST Framework (DRF) to Graphene-Django.

Parts of the documentation of Graphene is currently a bit slim. The purpose of this repository is to show code examples compared against DRF, and help getting started with GraphQL on a Django backend, or transitioning from DRF.

Slides used for related Python meetup talk can be found here

Summary

  • Transition from DRF is surprisingly easy, and is done in quite few lines of code.
  • Documentation from Django help_text will be reused in schema generated from GraphQL.
  • You can reuse existing DRF serializers and validation
  • Addressing authentication and authorization is not a part of GraphQL spec and there's little help in Graphene or documentation about it. Own implementation can be found at graphy/utils/graphql.py
  • Graphene performs comparably to DRF even if you have "ideal" REST endpoints that avoid overfetching.

Examples

  • 3d55467 - Adding your first GraphQL endpoint and query.
  • c71967a - Adding your first GraphQL test.
  • e7a366c - Reusing DRF serializer to create your first mutation.
  • 3a72f29 - @auth_required wrapper added, returning a HTTP 401 or error object (Two different views).
  • ece2cf0 - Disallowing queries over a given depth.

Performance comparison

Since the nature of GraphQL and REST APIs are quite different, a performance comparision might not map to the differences in your real life situation.

While it makes perfect sense to prefetch (select_related with Django ORM) to a REST endpoint that needs the prefetched data, the same can not be said for GraphQL endpoints, since a single GraphQL Query (comparable to a View in DRF, or an endpoint in a typical REST API) can be used for quite different cases.

For an ideal implementation (performance wise), where a Graphene Query and a DRF endpoint is implemented to serve a single, given data request, it looks like DRF is performing better: 1-5% when the select_related is not used, and 10-50% when select_related is used. The relative difference seems to to be larger the more instances are returned.

For real life implementations, overfetching is a problem that GraphQL avoids to a much larger degree than REST, and we've seen (large) performance gains at Otovo when switching from DRF to GraphQL. But be aware that GraphQL list queries where nested models are fetched can be slow when using Graphene naively. Consider adding own endpoints using select_related for such queris, or implementing assistance that modifies the database query based on the GraphQL Query.

Details of tests can be found in

  • ./graphy/location/views.py (DRF)
  • ./graphy/location/gql_actions.py (Graphene)
  • ./graphy/location/tests/test_gql_vs_drf_performance.py (GraphQL Query / REST call)

Numbers below are an average of 500 requests done when using curl against a non-debug server running postgres.

WITHOUT select_related

TypeAvg timeReturned objects
Shallow GraphQL query19.2 ms1
DRF27.9 ms1
Deep GraphQL query28.1 ms1
Shallow GraphQL query29.8 ms100
DRF422.1 ms100
Deep GraphQL query441.6 ms100

WITH select_related

TypeAvg timeReturned objects
Shallow GraphQL query22.8 ms1
DRF24.5 ms1
Deep GraphQL query26.2 ms1
Shallow GraphQL query49.9 ms100
DRF50.0 ms100
Deep GraphQL query75.54 ms100

Setup

Install python

This repo should work out of the box with python 3.7, and probably most other python 3 versions.

The commands below install 3.7 specifically on Mac / Linux

# See https://github.com/pyenv/pyenv-installer if you got Linux
brew install pyenv
# Installs 3.7.0, specified in .python-version
pyenv install

Install requirements

# Create and activate virtualenv
$(pyenv which python) -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

Load test data

python manage.py migrate
python manage.py loaddata db.json

This db dump includes a few instances of each model and a superuser with username: admin and password admin (...)

Run server

python manage.py migrate
python manage.py runserver

Admin panel

Admin panel runs at localhost:8000/admin. If you need to, you can create a super user with python manage.py createsuperuser

GraphiQL

Interactive GraphiQL runs at localhost:8000/graphql.

Tests

# Runs all tests
pytest

Related reading

About

Demonstrating django-graphene when moving from Django Rest Framework

Topics

Resources

Stars

34 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Graphy - a demo of Graphene-Django

Code and demonstation of the transition from Django REST Framework (DRF) to Graphene-Django.

Parts of the documentation of Graphene is currently a bit slim. The purpose of this repository is to show code examples compared against DRF, and help getting started with GraphQL on a Django backend, or transitioning from DRF.

Slides used for related Python meetup talk can be found here

Summary

  • Transition from DRF is surprisingly easy, and is done in quite few lines of code.
  • Documentation from Django help_text will be reused in schema generated from GraphQL.
  • You can reuse existing DRF serializers and validation
  • Addressing authentication and authorization is not a part of GraphQL spec and there's little help in Graphene or documentation about it. Own implementation can be found at graphy/utils/graphql.py
  • Graphene performs comparably to DRF even if you have "ideal" REST endpoints that avoid overfetching.

Examples

  • 3d55467 - Adding your first GraphQL endpoint and query.
  • c71967a - Adding your first GraphQL test.
  • e7a366c - Reusing DRF serializer to create your first mutation.
  • 3a72f29 - @auth_required wrapper added, returning a HTTP 401 or error object (Two different views).
  • ece2cf0 - Disallowing queries over a given depth.

Performance comparison

Since the nature of GraphQL and REST APIs are quite different, a performance comparision might not map to the differences in your real life situation.

While it makes perfect sense to prefetch (select_related with Django ORM) to a REST endpoint that needs the prefetched data, the same can not be said for GraphQL endpoints, since a single GraphQL Query (comparable to a View in DRF, or an endpoint in a typical REST API) can be used for quite different cases.

For an ideal implementation (performance wise), where a Graphene Query and a DRF endpoint is implemented to serve a single, given data request, it looks like DRF is performing better: 1-5% when the select_related is not used, and 10-50% when select_related is used. The relative difference seems to to be larger the more instances are returned.

For real life implementations, overfetching is a problem that GraphQL avoids to a much larger degree than REST, and we've seen (large) performance gains at Otovo when switching from DRF to GraphQL. But be aware that GraphQL list queries where nested models are fetched can be slow when using Graphene naively. Consider adding own endpoints using select_related for such queris, or implementing assistance that modifies the database query based on the GraphQL Query.

Details of tests can be found in

  • ./graphy/location/views.py (DRF)
  • ./graphy/location/gql_actions.py (Graphene)
  • ./graphy/location/tests/test_gql_vs_drf_performance.py (GraphQL Query / REST call)

Numbers below are an average of 500 requests done when using curl against a non-debug server running postgres.

WITHOUT select_related

TypeAvg timeReturned objects
Shallow GraphQL query19.2 ms1
DRF27.9 ms1
Deep GraphQL query28.1 ms1
Shallow GraphQL query29.8 ms100
DRF422.1 ms100
Deep GraphQL query441.6 ms100

WITH select_related

TypeAvg timeReturned objects
Shallow GraphQL query22.8 ms1
DRF24.5 ms1
Deep GraphQL query26.2 ms1
Shallow GraphQL query49.9 ms100
DRF50.0 ms100
Deep GraphQL query75.54 ms100

Setup

Install python

This repo should work out of the box with python 3.7, and probably most other python 3 versions.

The commands below install 3.7 specifically on Mac / Linux

# See https://github.com/pyenv/pyenv-installer if you got Linux
brew install pyenv
# Installs 3.7.0, specified in .python-version
pyenv install

Install requirements

# Create and activate virtualenv
$(pyenv which python) -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

Load test data

python manage.py migrate
python manage.py loaddata db.json

This db dump includes a few instances of each model and a superuser with username: admin and password admin (...)

Run server

python manage.py migrate
python manage.py runserver

Admin panel

Admin panel runs at localhost:8000/admin. If you need to, you can create a super user with python manage.py createsuperuser

GraphiQL

Interactive GraphiQL runs at localhost:8000/graphql.

Tests

# Runs all tests
pytest

Related reading

About

Demonstrating django-graphene when moving from Django Rest Framework

Topics

Resources

Stars

34 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Graphy - a demo of Graphene-Django

Code and demonstation of the transition from Django REST Framework (DRF) to Graphene-Django.

Parts of the documentation of Graphene is currently a bit slim. The purpose of this repository is to show code examples compared against DRF, and help getting started with GraphQL on a Django backend, or transitioning from DRF.

Slides used for related Python meetup talk can be found here

Summary

  • Transition from DRF is surprisingly easy, and is done in quite few lines of code.
  • Documentation from Django help_text will be reused in schema generated from GraphQL.
  • You can reuse existing DRF serializers and validation
  • Addressing authentication and authorization is not a part of GraphQL spec and there's little help in Graphene or documentation about it. Own implementation can be found at graphy/utils/graphql.py
  • Graphene performs comparably to DRF even if you have "ideal" REST endpoints that avoid overfetching.

Examples

  • 3d55467 - Adding your first GraphQL endpoint and query.
  • c71967a - Adding your first GraphQL test.
  • e7a366c - Reusing DRF serializer to create your first mutation.
  • 3a72f29 - @auth_required wrapper added, returning a HTTP 401 or error object (Two different views).
  • ece2cf0 - Disallowing queries over a given depth.

Performance comparison

Since the nature of GraphQL and REST APIs are quite different, a performance comparision might not map to the differences in your real life situation.

While it makes perfect sense to prefetch (select_related with Django ORM) to a REST endpoint that needs the prefetched data, the same can not be said for GraphQL endpoints, since a single GraphQL Query (comparable to a View in DRF, or an endpoint in a typical REST API) can be used for quite different cases.

For an ideal implementation (performance wise), where a Graphene Query and a DRF endpoint is implemented to serve a single, given data request, it looks like DRF is performing better: 1-5% when the select_related is not used, and 10-50% when select_related is used. The relative difference seems to to be larger the more instances are returned.

For real life implementations, overfetching is a problem that GraphQL avoids to a much larger degree than REST, and we've seen (large) performance gains at Otovo when switching from DRF to GraphQL. But be aware that GraphQL list queries where nested models are fetched can be slow when using Graphene naively. Consider adding own endpoints using select_related for such queris, or implementing assistance that modifies the database query based on the GraphQL Query.

Details of tests can be found in

  • ./graphy/location/views.py (DRF)
  • ./graphy/location/gql_actions.py (Graphene)
  • ./graphy/location/tests/test_gql_vs_drf_performance.py (GraphQL Query / REST call)

Numbers below are an average of 500 requests done when using curl against a non-debug server running postgres.

WITHOUT select_related

TypeAvg timeReturned objects
Shallow GraphQL query19.2 ms1
DRF27.9 ms1
Deep GraphQL query28.1 ms1
Shallow GraphQL query29.8 ms100
DRF422.1 ms100
Deep GraphQL query441.6 ms100

WITH select_related

TypeAvg timeReturned objects
Shallow GraphQL query22.8 ms1
DRF24.5 ms1
Deep GraphQL query26.2 ms1
Shallow GraphQL query49.9 ms100
DRF50.0 ms100
Deep GraphQL query75.54 ms100

Setup

Install python

This repo should work out of the box with python 3.7, and probably most other python 3 versions.

The commands below install 3.7 specifically on Mac / Linux

# See https://github.com/pyenv/pyenv-installer if you got Linux
brew install pyenv
# Installs 3.7.0, specified in .python-version
pyenv install

Install requirements

# Create and activate virtualenv
$(pyenv which python) -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

Load test data

python manage.py migrate
python manage.py loaddata db.json

This db dump includes a few instances of each model and a superuser with username: admin and password admin (...)

Run server

python manage.py migrate
python manage.py runserver

Admin panel

Admin panel runs at localhost:8000/admin. If you need to, you can create a super user with python manage.py createsuperuser

GraphiQL

Interactive GraphiQL runs at localhost:8000/graphql.

Tests

# Runs all tests
pytest

Related reading

About

Demonstrating django-graphene when moving from Django Rest Framework

Topics

Resources

Stars

34 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Graphy - a demo of Graphene-Django

Code and demonstation of the transition from Django REST Framework (DRF) to Graphene-Django.

Parts of the documentation of Graphene is currently a bit slim. The purpose of this repository is to show code examples compared against DRF, and help getting started with GraphQL on a Django backend, or transitioning from DRF.

Slides used for related Python meetup talk can be found here

Summary

  • Transition from DRF is surprisingly easy, and is done in quite few lines of code.
  • Documentation from Django help_text will be reused in schema generated from GraphQL.
  • You can reuse existing DRF serializers and validation
  • Addressing authentication and authorization is not a part of GraphQL spec and there's little help in Graphene or documentation about it. Own implementation can be found at graphy/utils/graphql.py
  • Graphene performs comparably to DRF even if you have "ideal" REST endpoints that avoid overfetching.

Examples

  • 3d55467 - Adding your first GraphQL endpoint and query.
  • c71967a - Adding your first GraphQL test.
  • e7a366c - Reusing DRF serializer to create your first mutation.
  • 3a72f29 - @auth_required wrapper added, returning a HTTP 401 or error object (Two different views).
  • ece2cf0 - Disallowing queries over a given depth.

Performance comparison

Since the nature of GraphQL and REST APIs are quite different, a performance comparision might not map to the differences in your real life situation.

While it makes perfect sense to prefetch (select_related with Django ORM) to a REST endpoint that needs the prefetched data, the same can not be said for GraphQL endpoints, since a single GraphQL Query (comparable to a View in DRF, or an endpoint in a typical REST API) can be used for quite different cases.

For an ideal implementation (performance wise), where a Graphene Query and a DRF endpoint is implemented to serve a single, given data request, it looks like DRF is performing better: 1-5% when the select_related is not used, and 10-50% when select_related is used. The relative difference seems to to be larger the more instances are returned.

For real life implementations, overfetching is a problem that GraphQL avoids to a much larger degree than REST, and we've seen (large) performance gains at Otovo when switching from DRF to GraphQL. But be aware that GraphQL list queries where nested models are fetched can be slow when using Graphene naively. Consider adding own endpoints using select_related for such queris, or implementing assistance that modifies the database query based on the GraphQL Query.

Details of tests can be found in

  • ./graphy/location/views.py (DRF)
  • ./graphy/location/gql_actions.py (Graphene)
  • ./graphy/location/tests/test_gql_vs_drf_performance.py (GraphQL Query / REST call)

Numbers below are an average of 500 requests done when using curl against a non-debug server running postgres.

WITHOUT select_related

TypeAvg timeReturned objects
Shallow GraphQL query19.2 ms1
DRF27.9 ms1
Deep GraphQL query28.1 ms1
Shallow GraphQL query29.8 ms100
DRF422.1 ms100
Deep GraphQL query441.6 ms100

WITH select_related

TypeAvg timeReturned objects
Shallow GraphQL query22.8 ms1
DRF24.5 ms1
Deep GraphQL query26.2 ms1
Shallow GraphQL query49.9 ms100
DRF50.0 ms100
Deep GraphQL query75.54 ms100

Setup

Install python

This repo should work out of the box with python 3.7, and probably most other python 3 versions.

The commands below install 3.7 specifically on Mac / Linux

# See https://github.com/pyenv/pyenv-installer if you got Linux
brew install pyenv
# Installs 3.7.0, specified in .python-version
pyenv install

Install requirements

# Create and activate virtualenv
$(pyenv which python) -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

Load test data

python manage.py migrate
python manage.py loaddata db.json

This db dump includes a few instances of each model and a superuser with username: admin and password admin (...)

Run server

python manage.py migrate
python manage.py runserver

Admin panel

Admin panel runs at localhost:8000/admin. If you need to, you can create a super user with python manage.py createsuperuser

GraphiQL

Interactive GraphiQL runs at localhost:8000/graphql.

Tests

# Runs all tests
pytest

Related reading

About

Demonstrating django-graphene when moving from Django Rest Framework

Topics

Resources

Stars

34 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Graphy - a demo of Graphene-Django

Code and demonstation of the transition from Django REST Framework (DRF) to Graphene-Django.

Parts of the documentation of Graphene is currently a bit slim. The purpose of this repository is to show code examples compared against DRF, and help getting started with GraphQL on a Django backend, or transitioning from DRF.

Slides used for related Python meetup talk can be found here

Summary

  • Transition from DRF is surprisingly easy, and is done in quite few lines of code.
  • Documentation from Django help_text will be reused in schema generated from GraphQL.
  • You can reuse existing DRF serializers and validation
  • Addressing authentication and authorization is not a part of GraphQL spec and there's little help in Graphene or documentation about it. Own implementation can be found at graphy/utils/graphql.py
  • Graphene performs comparably to DRF even if you have "ideal" REST endpoints that avoid overfetching.

Examples

  • 3d55467 - Adding your first GraphQL endpoint and query.
  • c71967a - Adding your first GraphQL test.
  • e7a366c - Reusing DRF serializer to create your first mutation.
  • 3a72f29 - @auth_required wrapper added, returning a HTTP 401 or error object (Two different views).
  • ece2cf0 - Disallowing queries over a given depth.

Performance comparison

Since the nature of GraphQL and REST APIs are quite different, a performance comparision might not map to the differences in your real life situation.

While it makes perfect sense to prefetch (select_related with Django ORM) to a REST endpoint that needs the prefetched data, the same can not be said for GraphQL endpoints, since a single GraphQL Query (comparable to a View in DRF, or an endpoint in a typical REST API) can be used for quite different cases.

For an ideal implementation (performance wise), where a Graphene Query and a DRF endpoint is implemented to serve a single, given data request, it looks like DRF is performing better: 1-5% when the select_related is not used, and 10-50% when select_related is used. The relative difference seems to to be larger the more instances are returned.

For real life implementations, overfetching is a problem that GraphQL avoids to a much larger degree than REST, and we've seen (large) performance gains at Otovo when switching from DRF to GraphQL. But be aware that GraphQL list queries where nested models are fetched can be slow when using Graphene naively. Consider adding own endpoints using select_related for such queris, or implementing assistance that modifies the database query based on the GraphQL Query.

Details of tests can be found in

  • ./graphy/location/views.py (DRF)
  • ./graphy/location/gql_actions.py (Graphene)
  • ./graphy/location/tests/test_gql_vs_drf_performance.py (GraphQL Query / REST call)

Numbers below are an average of 500 requests done when using curl against a non-debug server running postgres.

WITHOUT select_related

TypeAvg timeReturned objects
Shallow GraphQL query19.2 ms1
DRF27.9 ms1
Deep GraphQL query28.1 ms1
Shallow GraphQL query29.8 ms100
DRF422.1 ms100
Deep GraphQL query441.6 ms100

WITH select_related

TypeAvg timeReturned objects
Shallow GraphQL query22.8 ms1
DRF24.5 ms1
Deep GraphQL query26.2 ms1
Shallow GraphQL query49.9 ms100
DRF50.0 ms100
Deep GraphQL query75.54 ms100

Setup

Install python

This repo should work out of the box with python 3.7, and probably most other python 3 versions.

The commands below install 3.7 specifically on Mac / Linux

# See https://github.com/pyenv/pyenv-installer if you got Linux
brew install pyenv
# Installs 3.7.0, specified in .python-version
pyenv install

Install requirements

# Create and activate virtualenv
$(pyenv which python) -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

Load test data

python manage.py migrate
python manage.py loaddata db.json

This db dump includes a few instances of each model and a superuser with username: admin and password admin (...)

Run server

python manage.py migrate
python manage.py runserver

Admin panel

Admin panel runs at localhost:8000/admin. If you need to, you can create a super user with python manage.py createsuperuser

GraphiQL

Interactive GraphiQL runs at localhost:8000/graphql.

Tests

# Runs all tests
pytest

Related reading

About

Demonstrating django-graphene when moving from Django Rest Framework

Topics

Resources

Stars

34 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Graphy - a demo of Graphene-Django

Code and demonstation of the transition from Django REST Framework (DRF) to Graphene-Django.

Parts of the documentation of Graphene is currently a bit slim. The purpose of this repository is to show code examples compared against DRF, and help getting started with GraphQL on a Django backend, or transitioning from DRF.

Slides used for related Python meetup talk can be found here

Summary

  • Transition from DRF is surprisingly easy, and is done in quite few lines of code.
  • Documentation from Django help_text will be reused in schema generated from GraphQL.
  • You can reuse existing DRF serializers and validation
  • Addressing authentication and authorization is not a part of GraphQL spec and there's little help in Graphene or documentation about it. Own implementation can be found at graphy/utils/graphql.py
  • Graphene performs comparably to DRF even if you have "ideal" REST endpoints that avoid overfetching.

Examples

  • 3d55467 - Adding your first GraphQL endpoint and query.
  • c71967a - Adding your first GraphQL test.
  • e7a366c - Reusing DRF serializer to create your first mutation.
  • 3a72f29 - @auth_required wrapper added, returning a HTTP 401 or error object (Two different views).
  • ece2cf0 - Disallowing queries over a given depth.

Performance comparison

Since the nature of GraphQL and REST APIs are quite different, a performance comparision might not map to the differences in your real life situation.

While it makes perfect sense to prefetch (select_related with Django ORM) to a REST endpoint that needs the prefetched data, the same can not be said for GraphQL endpoints, since a single GraphQL Query (comparable to a View in DRF, or an endpoint in a typical REST API) can be used for quite different cases.

For an ideal implementation (performance wise), where a Graphene Query and a DRF endpoint is implemented to serve a single, given data request, it looks like DRF is performing better: 1-5% when the select_related is not used, and 10-50% when select_related is used. The relative difference seems to to be larger the more instances are returned.

For real life implementations, overfetching is a problem that GraphQL avoids to a much larger degree than REST, and we've seen (large) performance gains at Otovo when switching from DRF to GraphQL. But be aware that GraphQL list queries where nested models are fetched can be slow when using Graphene naively. Consider adding own endpoints using select_related for such queris, or implementing assistance that modifies the database query based on the GraphQL Query.

Details of tests can be found in

  • ./graphy/location/views.py (DRF)
  • ./graphy/location/gql_actions.py (Graphene)
  • ./graphy/location/tests/test_gql_vs_drf_performance.py (GraphQL Query / REST call)

Numbers below are an average of 500 requests done when using curl against a non-debug server running postgres.

WITHOUT select_related

TypeAvg timeReturned objects
Shallow GraphQL query19.2 ms1
DRF27.9 ms1
Deep GraphQL query28.1 ms1
Shallow GraphQL query29.8 ms100
DRF422.1 ms100
Deep GraphQL query441.6 ms100

WITH select_related

TypeAvg timeReturned objects
Shallow GraphQL query22.8 ms1
DRF24.5 ms1
Deep GraphQL query26.2 ms1
Shallow GraphQL query49.9 ms100
DRF50.0 ms100
Deep GraphQL query75.54 ms100

Setup

Install python

This repo should work out of the box with python 3.7, and probably most other python 3 versions.

The commands below install 3.7 specifically on Mac / Linux

# See https://github.com/pyenv/pyenv-installer if you got Linux
brew install pyenv
# Installs 3.7.0, specified in .python-version
pyenv install

Install requirements

# Create and activate virtualenv
$(pyenv which python) -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

Load test data

python manage.py migrate
python manage.py loaddata db.json

This db dump includes a few instances of each model and a superuser with username: admin and password admin (...)

Run server

python manage.py migrate
python manage.py runserver

Admin panel

Admin panel runs at localhost:8000/admin. If you need to, you can create a super user with python manage.py createsuperuser

GraphiQL

Interactive GraphiQL runs at localhost:8000/graphql.

Tests

# Runs all tests
pytest

Related reading

About

Demonstrating django-graphene when moving from Django Rest Framework

Topics

Resources

Stars

34 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Graphy - a demo of Graphene-Django

Code and demonstation of the transition from Django REST Framework (DRF) to Graphene-Django.

Parts of the documentation of Graphene is currently a bit slim. The purpose of this repository is to show code examples compared against DRF, and help getting started with GraphQL on a Django backend, or transitioning from DRF.

Slides used for related Python meetup talk can be found here

Summary

  • Transition from DRF is surprisingly easy, and is done in quite few lines of code.
  • Documentation from Django help_text will be reused in schema generated from GraphQL.
  • You can reuse existing DRF serializers and validation
  • Addressing authentication and authorization is not a part of GraphQL spec and there's little help in Graphene or documentation about it. Own implementation can be found at graphy/utils/graphql.py
  • Graphene performs comparably to DRF even if you have "ideal" REST endpoints that avoid overfetching.

Examples

  • 3d55467 - Adding your first GraphQL endpoint and query.
  • c71967a - Adding your first GraphQL test.
  • e7a366c - Reusing DRF serializer to create your first mutation.
  • 3a72f29 - @auth_required wrapper added, returning a HTTP 401 or error object (Two different views).
  • ece2cf0 - Disallowing queries over a given depth.

Performance comparison

Since the nature of GraphQL and REST APIs are quite different, a performance comparision might not map to the differences in your real life situation.

While it makes perfect sense to prefetch (select_related with Django ORM) to a REST endpoint that needs the prefetched data, the same can not be said for GraphQL endpoints, since a single GraphQL Query (comparable to a View in DRF, or an endpoint in a typical REST API) can be used for quite different cases.

For an ideal implementation (performance wise), where a Graphene Query and a DRF endpoint is implemented to serve a single, given data request, it looks like DRF is performing better: 1-5% when the select_related is not used, and 10-50% when select_related is used. The relative difference seems to to be larger the more instances are returned.

For real life implementations, overfetching is a problem that GraphQL avoids to a much larger degree than REST, and we've seen (large) performance gains at Otovo when switching from DRF to GraphQL. But be aware that GraphQL list queries where nested models are fetched can be slow when using Graphene naively. Consider adding own endpoints using select_related for such queris, or implementing assistance that modifies the database query based on the GraphQL Query.

Details of tests can be found in

  • ./graphy/location/views.py (DRF)
  • ./graphy/location/gql_actions.py (Graphene)
  • ./graphy/location/tests/test_gql_vs_drf_performance.py (GraphQL Query / REST call)

Numbers below are an average of 500 requests done when using curl against a non-debug server running postgres.

WITHOUT select_related

TypeAvg timeReturned objects
Shallow GraphQL query19.2 ms1
DRF27.9 ms1
Deep GraphQL query28.1 ms1
Shallow GraphQL query29.8 ms100
DRF422.1 ms100
Deep GraphQL query441.6 ms100

WITH select_related

TypeAvg timeReturned objects
Shallow GraphQL query22.8 ms1
DRF24.5 ms1
Deep GraphQL query26.2 ms1
Shallow GraphQL query49.9 ms100
DRF50.0 ms100
Deep GraphQL query75.54 ms100

Setup

Install python

This repo should work out of the box with python 3.7, and probably most other python 3 versions.

The commands below install 3.7 specifically on Mac / Linux

# See https://github.com/pyenv/pyenv-installer if you got Linux
brew install pyenv
# Installs 3.7.0, specified in .python-version
pyenv install

Install requirements

# Create and activate virtualenv
$(pyenv which python) -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

Load test data

python manage.py migrate
python manage.py loaddata db.json

This db dump includes a few instances of each model and a superuser with username: admin and password admin (...)

Run server

python manage.py migrate
python manage.py runserver

Admin panel

Admin panel runs at localhost:8000/admin. If you need to, you can create a super user with python manage.py createsuperuser

GraphiQL

Interactive GraphiQL runs at localhost:8000/graphql.

Tests

# Runs all tests
pytest

Related reading

About

Demonstrating django-graphene when moving from Django Rest Framework

Topics

Resources

Stars

34 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Graphy - a demo of Graphene-Django

Code and demonstation of the transition from Django REST Framework (DRF) to Graphene-Django.

Parts of the documentation of Graphene is currently a bit slim. The purpose of this repository is to show code examples compared against DRF, and help getting started with GraphQL on a Django backend, or transitioning from DRF.

Slides used for related Python meetup talk can be found here

Summary

  • Transition from DRF is surprisingly easy, and is done in quite few lines of code.
  • Documentation from Django help_text will be reused in schema generated from GraphQL.
  • You can reuse existing DRF serializers and validation
  • Addressing authentication and authorization is not a part of GraphQL spec and there's little help in Graphene or documentation about it. Own implementation can be found at graphy/utils/graphql.py
  • Graphene performs comparably to DRF even if you have "ideal" REST endpoints that avoid overfetching.

Examples

  • 3d55467 - Adding your first GraphQL endpoint and query.
  • c71967a - Adding your first GraphQL test.
  • e7a366c - Reusing DRF serializer to create your first mutation.
  • 3a72f29 - @auth_required wrapper added, returning a HTTP 401 or error object (Two different views).
  • ece2cf0 - Disallowing queries over a given depth.

Performance comparison

Since the nature of GraphQL and REST APIs are quite different, a performance comparision might not map to the differences in your real life situation.

While it makes perfect sense to prefetch (select_related with Django ORM) to a REST endpoint that needs the prefetched data, the same can not be said for GraphQL endpoints, since a single GraphQL Query (comparable to a View in DRF, or an endpoint in a typical REST API) can be used for quite different cases.

For an ideal implementation (performance wise), where a Graphene Query and a DRF endpoint is implemented to serve a single, given data request, it looks like DRF is performing better: 1-5% when the select_related is not used, and 10-50% when select_related is used. The relative difference seems to to be larger the more instances are returned.

For real life implementations, overfetching is a problem that GraphQL avoids to a much larger degree than REST, and we've seen (large) performance gains at Otovo when switching from DRF to GraphQL. But be aware that GraphQL list queries where nested models are fetched can be slow when using Graphene naively. Consider adding own endpoints using select_related for such queris, or implementing assistance that modifies the database query based on the GraphQL Query.

Details of tests can be found in

  • ./graphy/location/views.py (DRF)
  • ./graphy/location/gql_actions.py (Graphene)
  • ./graphy/location/tests/test_gql_vs_drf_performance.py (GraphQL Query / REST call)

Numbers below are an average of 500 requests done when using curl against a non-debug server running postgres.

WITHOUT select_related

TypeAvg timeReturned objects
Shallow GraphQL query19.2 ms1
DRF27.9 ms1
Deep GraphQL query28.1 ms1
Shallow GraphQL query29.8 ms100
DRF422.1 ms100
Deep GraphQL query441.6 ms100

WITH select_related

TypeAvg timeReturned objects
Shallow GraphQL query22.8 ms1
DRF24.5 ms1
Deep GraphQL query26.2 ms1
Shallow GraphQL query49.9 ms100
DRF50.0 ms100
Deep GraphQL query75.54 ms100

Setup

Install python

This repo should work out of the box with python 3.7, and probably most other python 3 versions.

The commands below install 3.7 specifically on Mac / Linux

# See https://github.com/pyenv/pyenv-installer if you got Linux
brew install pyenv
# Installs 3.7.0, specified in .python-version
pyenv install

Install requirements

# Create and activate virtualenv
$(pyenv which python) -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

Load test data

python manage.py migrate
python manage.py loaddata db.json

This db dump includes a few instances of each model and a superuser with username: admin and password admin (...)

Run server

python manage.py migrate
python manage.py runserver

Admin panel

Admin panel runs at localhost:8000/admin. If you need to, you can create a super user with python manage.py createsuperuser

GraphiQL

Interactive GraphiQL runs at localhost:8000/graphql.

Tests

# Runs all tests
pytest

Related reading

About

Demonstrating django-graphene when moving from Django Rest Framework

Topics

Resources

Stars

34 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages