See Part II: Consuming Metadata for how to create a typed form in Vue.js using the metadata generated in this article!
When developing a web application, a common design pattern is to divide the project into two repositories: 1) the API, or “back end”, which handles database or service interaction (“back end”), and 2) the client, or “front end”, which handles user interaction.
But before we look at how to share metadata from the API to the client, let’s dig into why it’s worth the effort.
Resolving one API-client discrepancy is a time sink. Resolving multiple discrepancies is a time bathtub.
Maintaining distinct API and client repositories is good for separating server-side and presentation logic, but those repos can be difficult to keep in sync.
If your application is form-heavy, there are a number of aspects of client and API code that need to match:
- Field names, like
- Variable casing, like
- Which fields are required
- Which options to show in select elements
- Default field values
- Min/max length requirements for text fields
- Min/max value requirements for number fields
- Regex patterns for text fields
- Which fields are read-only
Whew. Look at everything that can get out of whack. Resolving one API-client discrepancy is a time sink. Resolving multiple discrepancies is a time bathtub.
What if the client knew what the API expected? If the API could tell the client ahead of time about its data structures, it would be simple to code the client to match those API requirements.
In other words, the client needs data about what data the API expects.
Data about data? We’re talking metadata.
Django REST Framework (DRF) has built-in support for generating metadata about API endpoints.
The journey of metadata from model to
OPTIONS response is:
Note: metadata is generated for each field specified on the serializer. If your serializer specifies only a subset of a model’s fields, the metadata will represent only those fields and not the entire model.
To see what metadata DRF gives us for free, here’s a sample
OPTIONS response for an API endpoint that lists and creates Users:
The first four attributes relate to the endpoint itself. For metadata about the User model,
actions is where the action is.
We can see that in a
POST request, this API endpoint expects a number of fields, only two of which are required:
password. Each field contains some or all of the following metadata:
type: the expected value type (string)
required: whether the field is required (it is)
read_only: whether the field is read-only (it isn’t)
label: a user-facing name (“title”)
max_length: the value’s maximum number of characters (100)
We can’t see this in the User model’s metadata above, but additional information will be included if applicable:
choices: model field choices as a list of objects with
child: metadata about a
tomodel or an
ArrayField’s base field
min_value: other model field validations
Not bad! Out of the box, we can get useful information about an API endpoint’s data expectations, and this information corresponds to quite a few items in the above list of time sinks to sync. That means we should be able to use this metadata in our client to help keep our client code in sync with API expectations.
To use this metadata while developing a client, however, we need this metadata offline (i.e., without doing an
OPTIONS request), and we need more.
Means and motive to muster more metadata.
DRF supports custom metadata classes, which can generate the metadata to show in an
OPTIONS response — or offline, as we’ll see in a bit.
Django’s default metadata class extracts a good amount of information from the view set’s
ModelSerializer, as we saw above, but we can be greedy and get even more.
Take a look:
APIMetadata class adds:
initial: a field’s default value
pattern: a Regex pattern a field’s value must match
format: the format of a string value, like
field_name: a field’s name
write_only: whether a field should be written and not read
Although we didn’t add very many pieces of metadata, the attributes we added are useful in a client:
initialcan populate fields with a default value
patterncan be used with HTML5 input constraint validation for input Regex validation
formatcan help determine when to use an
field_nameensures that all field metadata is in the field metadata object instead of distributed across the key and value
write_onlycan help determine when to use an
Now that we’ve got means and motive to muster more metadata, let’s get it out of the API and into the client.
To export metadata in a format that’s useful in a client code base, we’ll use
APIMetadata to generate metadata for a User serializer, then write it to a JSON file.
I recommend writing a management command to export JSON metadata for each model you plan to use in your client, but to keep things moving, here’s a quick way to do that from a Django shell (
In contrast to the
OPTIONS response, this metadata has a shorter journey from model to JSON:
Model → Serializer → Metadata class → JSON file
And here’s the resulting
Look at all that delicious metadata!
By extending DRF’s default metadata class, we unlocked even more information about API models and exported that data into a client-friendly JSON format.
Now that we’ve got all this metadata, we should do something with it.
In Part II we’ll start using this metadata for client development, with a little help from TypeScript.