Why use TypeScript unknown for API response types
I recently extracted and released kefir-ajax
, a fetch-like ajax library for Kefir. While I wrote the library in plain JavaScript, the library generates TypeScript typings from its JSDocs. As you can see here, the json
method of the ObsResponse
class returns TypeScript unknown
, rather than the much more common any
. Using unknown
instead of any
here in TypeScript may make the API more awkward to use, so I wanted to explain why I made this decision.
TypeScript unknown
makes the API more reliable
While this may make the API slightly more awkward, it provides a much sounder typing for your APIs. With any
, you have no guarantee that any of those API values are what TypeScript thinks they are. This pushes your errors away from their source, as they start appearing when you rely on guarantees you don’t have. This shows up in your error logs as property of ‘undefined’
errors and can be very difficult to debug. API responses aren’t the first place you will look unless they’re really close to the location of the error.
If the response body is unknown
, TypeScript either forces you to validate it before you can do anything with it or explicitly cast it to any
. For the former case, you can use a library like io-ts
or runtypes
to validate your unknown
types to concrete ones (I’m currently using the former). Both of these packages accept an unknown
API response and provide either a validated, strictly typed API response, or an error, which you can then handle as you choose.
What if you don’t want to validate?
If neither of those work for you, you can cast to any
and get on with your life. You’ve explicitly opted into any
, rather than having kefir-ajax
introduce that for you. Later on, when you decide to introduce strict type validation to you API responses, you can easily find where it’s needed. Search your codebase for any
and wrap those with your new API validations.
Kefir in particular makes this really nice, because you can push any errors down Kefir’s error channel:
The error type gets added to the downstream type, and TypeScript warns you if you haven’t handled it, t.Error
. TypeScript’s compiler helps you introduce the validation without adding new bugs at the same time.
But even if you don’t use Kefir, or kefir-ajax
, use unknown
for your API responses then validate them. This helps you build trust in what TypeScript is telling you and keeps your errors closer to their cause.