Typed Fastify
This package brings strongly typed request handlers to fastify by forcing developers to write
service schema which is used to validate request params and replies. From this schema it does two
things:
- static typechecking against TS Schema
request.body
request.headers
request.querystring
request.params
reply
is always based on status, developer won't be able to use plain reply.send()
but
forced to explicitly set status first, based on which response type will be inferred
- JSON schema generation from TS Schema (using typescript-json-schema with custom
transforms, all
@tjs
annotations can be used to fine-tune output) - Runtime validation using generated JSON schema (optional but strongly recommended as it brings extra safety to runtime and ensures that code assumptions about data are correct)
demo video
Usage
npm i @coobaha/typed-fastify
yarn add @coobaha/typed-fastify
Example of service we want to build
GET / => Hello ($querystring.name || world)
Simple implementation without schema generation will be following
import addSchema, { Schema } from '@coobaha/typed-fastify';
import fastify from 'fastify';
export interface ExampleSchema extends Schema {
paths: {
'GET /': {
request: {
querystring: {
name?: string;
};
};
response: {
200: {
content: string;
};
};
};
};
}
const exampleService: Service<ExampleSchema> = {
'GET /': (req, reply) => {
const name = req.query.name ?? 'World';
return reply.status(200).send(`Hello ${name}`);
},
};
const app = fastify();
addSchema(app, {
jsonSchema: {},
service: exampleService,
});
app.listen(3000, (err: any) => {
if (err) {
app.log.error(err);
process.exit(1);
}
});
Complex examples can be found typescript tests and
in integration.test.ts.
JSON schema generation
You can generate json schema from your TS types by using typed-fastify-schema
or tfs
bins
npx tfs gen
tfs gen [files]
Generates json schemas next to corresponding ts files
Positionals:
files glob pattern of files [string] [required]
Options:
--help Show help [boolean]
--version Show version number [boolean]
npx tfs gen example_schema.ts
When schema is generated - just pass it to plugin to have runtime validations 🎉
import jsonSchema from './example_schema.gen.json';
addSchema(app, {
jsonSchema,
service,
});
Writing service
- Handlers in one object
Type inference will work nicely in this case, you just make TS happy and things are working 🥳
- Handlers in a different file or separate functions - you will need to hint TS with exact type of
handler
The Easiest way to do it is
import { RequestHandler, Schema } from '@coobaha/typed-fastify';
interface MySchema extends Schema {}
const myHandler: RequestHandler<MySchema, 'GET /hello'>['AsRoute'] = (req, reply) => {};
- When you want to have complex shared handler for multiple endpoints that intersect (share same
props)
import { RequestHandler, Schema } from '@coobaha/typed-fastify';
interface MySchema extends Schema {}
const myHandlers: RequestHandler<MySchema, 'GET /hello' | `GET /hello2`>['AsRoute'] = (req, reply) => {};
- Sometimes properties won't be the same (for instance GET never has body and POST will). In this
case you will probably be asked to add types to function params
import { RequestHandler, Schema } from '@coobaha/typed-fastify';
interface MySchema extends Schema {}
type MyHandlers = RequestHandler<MySchema, 'GET /hello' | `POST /hello`>;
const myHandlers = (req: MyHandlers['Request'], reply: MyHandlers['Reply']): MyHandlers['Return'] => {};
const myHandlersAsync = async (req: MyHandlers['Request'], reply: MyHandlers['Reply']): MyHandlers['ReturnAsync'] => {};
addSchema(app, {
jsonSchema: {},
service: {
'GET /hello': myHandlers,
'GET /hello2': myHandlers,
},
});
It might be that TS can't infer exact type of complex handler when passed to addSchema
so you'll
need to do it manually
addSchema(app, {
jsonSchema: {},
service: {
'GET /hello': myHandlers,
'GET /hello2': myHandlers as RequestHandler<ExtendedSchema, 'GET /hello2'>['AsRoute'],
},
});
Note about request.params
Route path params (string tokens) are not validated on type level. This means that it is possible to
make a typo:
interface InvalidParams extends Schema {
paths: {
'GET /params/:ANOTHER_ID': {
request: {
params: {
id: number;
};
};
response: {
200: {};
};
};
};
}
As our schema expects params to be { id: number }
- typo will result in validation error if
generated JSON schemas are used or invalid runtime assumptions about data if plain types are used