Skip to content

About

GraphQL for Django, powered by graphene and Pydantic. Define your API straight from your models — no Relay required.

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

django-graphex

Codecov PyPI - Python Version Django Versions PyPI PyPI - License Downloads Ruff

GraphQL for Django, powered by graphql-core and Pydantic. Define your GraphQL API straight from your Django models — no DRF, no graphene, no django-filter.

  • Model-first types & mutations — DjangoModelType / DjangoModelMutation give you query, list and create/update/delete from a single Meta.model, validated and persisted with Pydantic v2 + the Django ORM (FK existence, uniqueness, unique_together, partial updates, choices → Enum).
  • Logical filtering — one nested filter: argument with and / or / not, per-field lookups, relation descent and plain-pk/UUID filtering (no django-filter).
  • Pagination — limit/offset, page and keyset cursor paginators with a uniform results / totalCount shape (and an automatic N+1 query optimizer).
  • Custom validation — DRF-style inline validate_<field>() / validate() or a Meta.pydantic_model.
  • Permissions, security & directives — permission classes, depth & cost limits, introspection control, and string/number/date/list directives. Meta.only_fields / Meta.exclude_fields are a security boundary: a column a type hides is unreadable, unorderable and unfilterable through it.
  • Subscriptions — real-time GraphQL over Django Channels 4 (optional extra).

Coming from graphene-django or graphene-django-extras? See the Migration Guide for a step-by-step upgrade with before/after examples.

Upgrading from django-graphex 1.x? 2.0 removed the graphene backend entirely — see the Upgrade Guide and the scripts/migrate_2_0.py codemod.

Requirements

  • Python: 3.12+ (3.13, 3.14 supported)
  • Django: 5.2+ (5.2 LTS, 6.0 supported) — each Django version tested on the Python versions it officially supports
  • graphql-core: >=3.3.0,<3.4
  • pydantic: >=2,<3

django-graphex 4.0.0 requires graphql-core>=3.3.0,<3.4. The historical 3.1.1 release requires graphql-core>=3.2.13,<3.3. Follow the 4.0 upgrade guide when migrating custom executors, AST builders or subscription transports.

Installation

# uv (recommended)
uv add "django-graphex==4.0.0"
# real-time subscriptions (adds Django Channels 4):
uv add "django-graphex[subscriptions]==4.0.0"
# pip
pip install "django-graphex==4.0.0"
pip install "django-graphex[subscriptions]==4.0.0"

The base install never imports channels; only the subscriptions extra does.

Quick start

from django.contrib.auth import get_user_model
from django.urls import path
from django_graphex.core import BooleanField, CharField, Field, Mutation, ObjectType
from django_graphex.fields import DjangoListObjectField, DjangoObjectField
from django_graphex.paginations import LimitOffsetGraphqlPagination
from django_graphex.schema import DjangoGraphQLSchema
from django_graphex.types import DjangoListObjectType, DjangoObjectType
from django_graphex.views import AuthenticatedGraphQLView

User = get_user_model()


class UserType(DjangoObjectType):
    class Meta:
        model = User
        only_fields = ("id", "username", "first_name", "last_name")
        filter_fields = {"username": ("icontains", "exact")}


class UserListType(DjangoListObjectType):
    class Meta:
        model = User
        pagination = LimitOffsetGraphqlPagination()


class RegisterUser(Mutation):
    ok = BooleanField()
    user = Field(UserType)

    class Arguments:
        username = CharField(required=True)
        password = CharField(required=True)

    @classmethod
    def mutate(cls, root, info, username, password):
        user = User.objects.create_user(username=username, password=password)
        return cls(ok=True, user=user)


class Query(ObjectType):
    user = DjangoObjectField(UserType)
    users = DjangoListObjectField(UserListType)


class Mutation(ObjectType):
    register_user = RegisterUser.Field()


schema = DjangoGraphQLSchema(query=Query, mutation=Mutation)

urlpatterns = [
    path("graphql/", AuthenticatedGraphQLView.as_view(schema=schema, graphiql=True)),
]

User is deliberately not mounted through a generic model mutation: account creation must call create_user() so the password is hashed, and the client must never choose staff/superuser flags. Use generated mutations for ordinary application models; see the mutation guide.

Query it with the nested filter: argument (and / or / not):

{
  users(filter: { isActive: { exact: true }, username: { icontains: "jo" } }) {
    results(limit: 10, ordering: "-date_joined") { id username }
    totalCount
  }
}

Configuration

All settings live under a single DJANGO_GRAPHEX dict (every key is optional):

# settings.py
DJANGO_GRAPHEX = {
    "DEFAULT_PAGINATION_CLASS": "django_graphex.paginations.LimitOffsetGraphqlPagination",
    "DEFAULT_PAGE_SIZE": 20,
    "MAX_PAGE_SIZE": 50,
    # Response caching. Default is False (disabled).
    # WARNING: cache keys are identity-salted per user (v1.2.1+), but shared
    # caches can still leak data if misconfigured. Review the caching guide
    # before enabling in production: docs/usage/caching.md
    "CACHE_ACTIVE": False,
}

Two keys are not in that dict on purpose — both ship enabled, and pinning a key to its own default only hides that you depend on it:

  • REQUIRE_CSRF_HEADER (True) demands an X-Requested-With header on form-encoded and multipart/form-data POSTs, which a browser can send cross-site with no CORS preflight. application/json clients are unaffected; form-encoded and multipart clients get HTTP 403 without it.
  • MAX_SUBSCRIPTIONS_PER_CONNECTION (50) caps the concurrent operations one graphql-transport-ws socket may hold.

Both are documented in Settings, and both are breaking for clients written against 2.2.0.

To use directives, add the middleware and pass all_directives to the schema:

DJANGO_GRAPHEX = {"MIDDLEWARE": ["django_graphex.middleware.GraphQLDirectiveMiddleware"]}

from django_graphex.directives import all_directives
from django_graphex.schema import DjangoGraphQLSchema
schema = DjangoGraphQLSchema(query=Query, mutation=Mutation, directives=all_directives)

Playground

A fully wired example project lives in examples/playground/. It exercises every major feature end-to-end — types, paginators, filtering, mutations, permissions, subscriptions, file uploads on both paths, the projection boundary on all three axes, and the query optimizer — and installs the library from this repo checkout (editable, no PyPI release needed). make test runs the end-to-end suite; several tests assert the verbatim answer strings its README quotes.

Documentation

📚 Full documentation — including the Quick Start, Model backend, Filtering, Pagination, Subscriptions, Settings and the Migration Guide.

The current core33 comparison is derived from eight committed portable results in core33-4.0.0-bf6e1ed6ccb6d3940d253c9c83b19a436f01aa4e, measured from the prepared 4.0.0 source bf6e1ed6ccb6d3940d253c9c83b19a436f01aa4e. In the SQLite create-comment operation, GraphEx/Ariadne p50 was 0.57× at 50,000 comments and 0.51× at 100,000; these are whole-stack observations under accepted background load, not an equal-core or universal performance guarantee. Earlier 4.0.0 pre-fragment, 3.1.1-source core33 and 3.1.0 figures remain historical.

License

MIT License — see the LICENSE file.

About

GraphQL for Django, powered by graphene and Pydantic. Define your API straight from your models — no Relay required.

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages