Skip to main content

Generate a typed TypeScript client from your Django REST Framework serializers

Project description

Tether

CI

Generate a typed TypeScript client from your Django REST Framework serializers — and keep the frontend from drifting away from the backend.

The problem

You write a serializer on the backend. Then you write it again on the frontend: the interface, the fetch call, the query hook. Two sources of truth for the same shape. The backend changes, the frontend doesn't hear about it, and something breaks in production that nothing warned you about.

The idea

Tether reads your serializers and routers directly and writes a typed client into your frontend — types, fetch functions, and query hooks — from a single command. No hand-maintained OpenAPI file in the middle. When the backend changes, you regenerate and your TypeScript compiler tells you exactly what no longer lines up.

The point isn't just generating types once. It's that the frontend can't silently drift from the API.

Install

pip install drf-tether

Add tether to your INSTALLED_APPS.

Configure

Point Tether at your serializers and (optionally) your DRF routers through a TETHER setting:

TETHER = {
    "output": "frontend/src/api",
    "serializers": [
        "shop.serializers.ProductSerializer",
    ],
    "routers": [
        "shop.urls.router",
    ],
    "hooks": True,  # emit TanStack Query hooks — optional
}
Key What it does
output Directory the client is written into (as index.ts).
serializers Import paths of the serializers to turn into interfaces.
routers Import paths of DRF routers to turn into fetch functions.
hooks When True, also emit @tanstack/react-query hooks.

Generate

python manage.py tether

Or override the output directory from the command line:

python manage.py tether --output frontend/src/api

What you get

For a ProductSerializer and a router registering a ProductViewSet, Tether writes:

// Generated by Tether. Do not edit by hand.

export interface Product {
  id: number;
  name: string;
  price: string;
}

export interface ProductWrite {
  name: string;
  price: string;
}

export function listProducts(): Promise<Product[]> {
  return request<Product[]>("GET", `/products/`);
}

export function getProduct(id: string | number): Promise<Product> {
  return request<Product>("GET", `/products/${id}/`);
}

export function createProduct(body: ProductWrite): Promise<Product> {
  return request<Product>("POST", `/products/`, body);
}

// ...update, patch, and delete too

With "hooks": True, each endpoint also gets a TanStack Query hook (useProducts, useProduct, useCreateProduct, ...). Mutation hooks invalidate the resource's queries on success.

Read and write shapes are split automatically — read_only fields drop out of the *Write interface and write_only fields out of the response. Paginated list endpoints return a Paginated<T> envelope, and custom @action routes get their own functions.

Custom actions

Tether can't see what a custom action's view body responds with, so by default it returns unknown. Point the action at a serializer and it gets typed:

@action(detail=False, methods=["get"], serializer_class=ProductSerializer)
def featured(self, request):
    ...
export function featuredProducts(): Promise<Product[]>;

detail=False is typed as an array, detail=True as a single object. If your action doesn't fit that, leave serializer_class off and it stays unknown.

Field types

Each serializer field maps to the closest TypeScript type:

DRF field TypeScript
CharField, EmailField, URLField, SlugField, UUIDField, IPAddressField, RegexField string
IntegerField, FloatField number
DecimalField string (or number if COERCE_DECIMAL_TO_STRING is off)
BooleanField boolean
DateTimeField, DateField, TimeField, DurationField string
FileField, ImageField, FilePathField string
ChoiceField a union of its choices, e.g. "draft" | "published"
MultipleChoiceField an array of that union
ListField T[]
DictField, HStoreField Record<string, T>
Nested serializer the referenced interface
many=True nested or relations T[]
PrimaryKeyRelatedField number or string, taken from the related model's key
StringRelatedField, SlugRelatedField, HyperlinkedRelatedField string
SerializerMethodField inferred from the method's return annotation
JSONField, ReadOnlyField, ModelField unknown
HiddenField left out

Where a type can't be known statically — a JSONField, or a SerializerMethodField with no return annotation — the field becomes unknown instead of a guess.

Keeping the client in sync

Run the generator in CI with --check. It regenerates the client in memory, compares it against what's on disk, and exits non-zero if they differ — so a backend change that wasn't regenerated fails the build instead of drifting silently.

python manage.py tether --check

Status

Working today: types for the full range of DRF serializer fields, fetch functions and TanStack Query hooks from your routers, pagination, custom actions, and the --check mode. It's still pre-1.0, so configuration and output can change between versions.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

drf_tether-0.1.0.tar.gz (22.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

drf_tether-0.1.0-py3-none-any.whl (13.5 kB view details)

Uploaded Python 3

File details

Details for the file drf_tether-0.1.0.tar.gz.

File metadata

  • Download URL: drf_tether-0.1.0.tar.gz
  • Upload date:
  • Size: 22.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for drf_tether-0.1.0.tar.gz
Algorithm Hash digest
SHA256 737a0e5138d6247f36ab2288fc9765560042b979fbf65277dd3713531b045f8e
MD5 20bb2c49122b8216dc156841c9e8538c
BLAKE2b-256 d21ca736a58271b040dfdfc637304ff79e107774633d704d08d0eb096e72cf21

See more details on using hashes here.

File details

Details for the file drf_tether-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: drf_tether-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 13.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for drf_tether-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d4332ac3e7e2866a300b15db823530684d109ef8c3d2cb892166d3ac1e1ac769
MD5 380848c054140dd10e48d792a851d3d9
BLAKE2b-256 cc6dc82154e6bc8a6c4f5f381c4fb2875952da49c99921d19ec447d199a1940c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page