Data validation with REST and json-schemas
We have a strongly typed REST API interface with build-time type checking. Can we build runtime validation with a minimal effort? (Spoiler alert - yesss)
Prerequisites
Read the previous article about type-safe REST APIs, if you haven’t yet. It will give a huge boost on productivity. Seriously… go go go… So, glad you’ve back. Now let’s get down to business…
Some sad facts about Typescript interfaces - and a possible solution
Let’s face it - you have a well-defined REST API in a Typescript interface but you cannot use Typescript interfaces realtime as they does not exists realtime 😿 But there’s a solution that can be easily adopted - JSON Schemas
So the plan is:
- Design the API
- Create a JSON Schema from the API definitions
- Wire some validation logic on the Backend
Generating the Schema
FuryStack offers some help when it comes to defining / designing the API interface - as mentioned in the last article. There is a nice tool that will help us create the JSON Schema - ts-json-schema-generator So if we have an interface ready, we can generate the JSON schema with a simple command from an NPM script:
yarn ts-json-schema-generator -f tsconfig.json --no-type-check -p common/src/path/to/my/api.schema.ts -o common/src/path/to/my/api.schema.json
Once it is done, you can access / re-export it in your common
package - maybe you have to enable resolveJsonModule
in your tsconfig.
import * as mySchema from './path/to/my/api.schema.json'
export { mySchema }
Validation in the Service
Once you will be able to import your schema, you can simply use the Validate()
method in REST Api. Let’s take for example the endpoint from the previous post.
import { mySchema } from 'common'
import { Validate } from '@fuystack/rest-service'
const customHeadersEndpoint: RequestAction<CustomHeaders> = Validate({
schema: mySchema
schemaName: 'CustomHeaders'
})(async ({ headers }) => {
console.log(headers)
return JsonResult(headers)
})
The Gotchas
- All input data (query, headers, url, body) will be validated - in depth
- You can define nested types, string literals, optional parameters, type intersections, nearly everything that Typescript can offer. You can throw errors on additional parameters as well.
- You will automatically get nice 400 responses with detailed error messages if you miss something
- Schema can be re-generated as your API changes