SvelteKit introduced generated types a while ago.
SvelteKit would automatically generate types for data
and form
in +page.svelte/+layout.svelte
files and load functions and request handlers in +page.js/+layout.js, +page.server.js/+layout.server.js
and +server.js files. But you had to annotate the types yourself, which felt like a repetitive chore:
Yesterday, the SvelteKit team went one step further and introduced zero-effort type safety for crucial parts of a SvelteKit app. This improvement makes manual annotations of generated types obsolete. You get type safety for data flowing through your SvelteKit app without TypeScript annotations. I removed a big chunk of my JSDoc annotations from my website in this pull request without losing any type safety. You can read more about zero-effort type safety in the announcement post.
Zero-effort type safety is a massive improvement for developer happiness. However, it would be best to put in additional effort to achieve complete type safety for your SvelteKit app. Besides data flowing through your SvelteKit app, you also need to keep an eye on all the points where data enters your SvelteKit app.
Validation with Zod
There are three ways for data to enter your SvelteKit app:
- By fetching data from the local file system, e.g., reading a Markdown file with a blog post or reading a JSON file with permitted tags.
- By fetching data from an external API, e.g., a headless CMS or a third-party API.
- By processing data submitted through a web form.
All three scenarios have in common that type annotations of incoming data are moot when the data you get is not what you expected. What you need is proper validation.
Zod is a schema validation library with first-class TypeScript support. The first thing to note is that most data coming into your app is structured, i.e., a combination of objects and arrays that contain strings. E.g., on my website, every post is in a Markdown file. Its frontmatter has a structure that can be described with a Zod schema:
Every property is required by default, and you can nest schemes and add additional constraints.
E.g., z.array(z.string()).optional()
means that the schema expects an optional array of
strings.
Once you have a schema, you can validate incoming data against it. In this example, I validate the frontmatter of a post against the Zod schema in a helper function:
Nothing spectacular except that frontmatter
, which is the validated data, is now
typed:
Not only does Zod validate the data, but it also types it and gives you full type safety for whatever you do with the validated data.
Validating and typing data from an API with Zod
To drive this point home, let's look at another example in a form action:
This form action handles data from a newsletter subscription form. At this point in the handler, I
know that the subscriber already exists, and I try to look up their status with API helper get_subscriber
. I validate the API response against Zod schema EOSubscriberSchema
.
If the validation fails, the external API did not return subscriber info in the expected format,
and I throw a server error. If the validation succeeds, the subscriber
variable is typed:
Conclusion
SvelteKit's zero-effort types provide type safety when data flows through your app. You should complement zero-effort type safety with Zod schemas and validate data whenever it enters your app. Zod's type inference gives you complete type safety for validated data.