Feathers is my go-to framework for building backends in Typescript. It is both powerful and easy-to-use, making it ideal for growing teams and products that need to build features quickly with a low learning curve. Generating a new endpoint is a breeze, but there's also a depth of additional features to beef things up as scope and scale increase.
One of the latest and greatest features are schemas, new to Feathers version 5.0
Why I Love Feathers Schema
- Data models: giving you a source of truth for your data without needing to hop into a database editor or rely on your database adapter
- TypeScript magic: all of your schemas can be automatically converted into TS types
- Validations: check, clean, and dismiss calls before any data reaches or leaves your API
- Resolvers: any extra or associated data can be easily populated before going to the user
- Documentation: all of the above serves as self-documentation for your API
We want to model several potentially different chunks of data for a Feathers service:
- Query parameters
- Create and update data
- Result data
The Feathers docs cover these basics, but don't capture the full context of a Feathers service complete with CRUD operations, associations, and typing. So let's walk through how Feathers Schema works with a service for musical artists, as an example.
Step One: Base Model
So, we'll start by creating a base schema that all of these will build upon. If you aren't populating any extra data, the base model may be very similar to your result payload.
Step Two: Query Model
Now that we have the foundation in place, let's make a schema that models the parameters that we're allowing the user to query by. Instead of angrily trying to filter artists by number of members and not getting any results, this schema will let you know "hey, that's not actually a thing you can do, dingus"
Step Three: Data Models
Next, let's work on "data models", or request payloads that will create or update a database record. Since the create, update, and patch methods could all have different data models, we'll make a separate schema for each.
Step Four: Result Models
The final set of schemas may in some cases be the most complex, but this is ultimately the data that the user of your API will receive, so we should definitely make sure it covers all possible properties and serves as good documentation of the service. In this example, the find's result will be extended for create, and for get (which will typically have the most additional data of any method)
Step Five: TypeScript Stuff
We use Feathers schema's Infer Typescript function to generate TS types for each schema, given the JSON Schema `type` properties that we've used to define them.
In order to leverage typing on the result payload of internal API calls to this Feathers services, we need to define a single, one-size-fits-all instance type, called Artist
We need to do a few more steps to define the artists service with the Artist type
1. Create a custom base class that overrides the find method, so that we can handle pesky paginated data typing issues:
2. Pass the Artist type to the artists class as a parameter:
Step Six: Resolvers
For our final order of business in the schemas.ts file, we're going to use resolvers to add associated and calculated data to the result payloads of find and get. This can also be done via after hooks, but resolvers tend to require less code and are recommended by the Feathers team for data population and calculation as version 5.0 is launched.
Populating data via reaching out to other services will get more efficient once the Feathers batch loader is released.
Step 7: Validating
Let's switch over to the services artists.hooks.ts file in order to plug our schemas in.
For the before hooks, validateData and validateQuery will be called first in each relevant method's hooks so that we can intercept calls before anything happens.
For the after hooks, resolveResult will be called last in each relevant method's hooks so that we can validate and resolve data before it is dispatched to the user.
Putting It All Together
With your validation and resolver functions in place, you can now test out API calls for validation errors! Try passing incorrect query parameters and result payload properties to test it out. The validation error messages aren't very readable by default, so check out this error message helper to enhance them.
To conclude, here's what your full schema file should look like, feel free to use it as a template!