This year, Fastify became my go-to framework for building Node.js APIs.
If the word sounds new to you, Fastify is a web framework for Node.js. It is used to build APIs and services the same way Express does.
Fastify comes with great features that really speed up the process of making applications. Among those features, my favorite one is the fact that the framework is schema-based (I'll explain).
In this post, I will share a few tricks on how you can leverage Fastify's schema capabilities to build APIs at a fast clip.
Fastify adopts the JSON Schema format on its core. Many of its features and libraries are built around the popular standard. Ajv, a library to compile and validate JSON Schemas, is a direct dependency of the framework.
By adopting JSON Schema, Fastify opens doors to an entire ecosystem of tools built around it. Below, let's see how to combine all these tools and libraries together with the framework.
One of the ways Fastify uses JSON Schema is to validate data coming from clients. It lets you add input schemas to your routes. For example:
In this example, any incoming data to
POST /movie that doesn't conform to the
PostMovieBody schema will throw a validation error.
This way, we are making sure that the handler function doesn't process any invalid or unexpected payloads.
Invalid objects will result in a validation error that looks like this:
Note: the content on
messagecomes from Ajv.
Serialization is the process of converting an object to a format that can be transferred over a network.
With Fastify, you can also define output schemas for JSON payloads. When you do so, any data returned to clients will be serialized and validated according to that definition.
More specifically, defining output schemas helps you in two ways:
- Fastify serializes the data with fast-json-stringify. In many cases, it is faster than
- Ajv validates the response. This will prevent sensitive fields from being exposed.
When declaring output schemas in your routes, each possible status code accepts a definition. For example, you can have schemas defined for
Tip: to use the same schema for a family of status codes, you can use the
Here's how to define an output schema to responses with a
200 status code:
In this example, any object returned by the handler that doesn't match the
Movie schema will result in an error. By default, the client receives a
400 response - similar to the example #2.
Documentation is an essential piece in any REST API.
There are many ways to document your application. One of them is manually, where you write routes and definitions by hand in a common format like YAML or JSON.
You can already guess this approach has many problems: outdated schemas, inconsistent validations, type discrepancies, etc.
Another approach is automating your documentation. A tool will automatically generate all the routes and definitions based on an existing schema.
One popular specification for writing documentation is Swagger. Thanks to the official fastify-swagger plugin, you can transform your existing JSON Schema definitions into Swagger ones and expose a beautiful documentation page in a flick.
fastify-swagger to a Fastify application should be straightforward:
Now, when you start your Fastify application and navigate to
/documentation in a browser this page will pop up:
Note: The more metadata you add to your routes, the more complete your page will look. Check out the route options documentation.
When testing services or endpoints, many times you will need to provide a fake or simulated input. These inputs are called mock objects. They replicate the structure and behavior of real objects.
You can create mock objects dynamically with the schemas you already have by using json-schema-faker. The library converts existing JSON Schemas into dummy objects that you can use in your tests. Let's see an example.
First, create a helper function (just a wrapper for
schemaToObject function does exactly what the name says: given a JSON Schema definition, it returns a matching mock object.
Now let's put it to use. You can call this function whenever you need to create fake objects for your tests. For example, when sending requests to routes:
In this example, we are creating a mock object,
POST-ing it to the
POST /movie route, and checking the status code.
schemaToObject function gives you a nice and clean way to test the "happy path" in your tests (when everything meets the expectations).
One of these matchers is jest-json-schema. This package adds a new assertion to Jest:
toMatchSchema. It lets you validate an object against an existing JSON Schema definition - it's like Ajv was integrated to Jest.
Note: If you are using another testing framework, there is likely an equivalent to
Instead of manually asserting the values of each property in an object like this:
You can simplify things using
Notice I am using the
Movie schema defined in example #3.
Of course, this is just simplifying type-checking in your tests. There are still other aspects of your code that need to be tested. Still, based on how easy it is to implement, I believe it's a good addition.
Let's do a quick recap.
In examples #1 and #3, we have declared two schemas using the JSON Schema format -
Movie. These schemas are used for:
- Validating objects sent to the route.
- Serializing and validating objects returned to the clients.
- Generating documentation.
- Creating mock objects.
- Asserting objects on tests.
Now here's the fun part!
Suppose you need to start tracking a new property in your movie objects. For example, you need to save and display the movie poster URL. Let's name the new field
If you were not using a schema-based framework, you would need to go through all your code and update the existing objects to include the new property. This is far from ideal. The chances of missing an assertion in your tests or forgetting to update the documentation are high.
But thanks to the magic of schemas, this process is a breeze. Your definitions are your source of truth. Anything based on the schemas will change once the schema changes.
So, now let's see how we can add the
The first step is to change the input schema (
PostMovieBody) to include the new property:
posterUrl must also be serialized and returned to the client, we also add it to the output schema (
And that's pretty much it!
Here is what will happen once you restart your server:
- Fastify will start checking for
- The Swagger file will be updated. The
posterUrlproperty will start showing on the documentation page.
- Mock objects in your tests will start being generated with a string value for
- Tests using the
toMatchSchemamatcher will start checking for the
...and you got all that just by changing two lines in your code. How cool is that?
To overcome that feeling, you can use fluent-schema. It gives you the same compact and programmable interface present in other tools.
For example, we could rewrite the
Movie schema in example #3 using
Looks neat, huh?
And that's a wrap! I hope you have enjoyed it. Stay tuned for more Fastify articles. ✌️