Item 74: Know How to Reconstruct Types at Runtime
May 10, 2024 ยท View on GitHub
Things to Remember
- TypeScript types are erased before your code is run. You can't access them at runtime without additional tooling.
- Know your options for runtime types: using a distinct runtime type system (such as Zod), generating TypeScript types from values (
json-schema-to-typescript), and generating values from your TypeScript types (typescript-json-schema). - If you have another specification for your types (e.g., a schema), use that as the source of truth.
- If you need to reference external TypeScript types, use
typescript-json-schemaor an equivalent. - Otherwise, weigh whether you prefer another build step or another system for specifying types.
Code Samples
interface CreateComment {
postId: string;
title: string;
body: string;
}
app.post('/comment', (request, response) => {
const {body} = request;
if (
!body ||
typeof body !== 'object' ||
Object.keys(body).length !== 3 ||
!('postId' in body) || typeof body.postId !== 'string' ||
!('title' in body) || typeof body.title !== 'string' ||
!('body' in body) || typeof body.body !== 'string'
) {
return response.status(400).send('Invalid request');
}
const comment = body as CreateComment;
// ... application validation and logic ...
return response.status(200).send('ok');
});
const val = { postId: '123', title: 'First', body: 'That is all'};
type ValType = typeof val;
// ^? type ValType = { postId: string; title: string; body: string; }
import { z } from 'zod';
// runtime value for type validation
const createCommentSchema = z.object({
postId: z.string(),
title: z.string(),
body: z.string(),
});
// static type
type CreateComment = z.infer<typeof createCommentSchema>;
// ^? type CreateComment = { postId: string; title: string; body: string; }
app.post('/comment', (request, response) => {
const {body} = request;
try {
const comment = createCommentSchema.parse(body);
// ^? const comment: { postId: string; title: string; body: string; }
// ... application validation and logic ...
return response.status(200).send('ok');
} catch (e) {
return response.status(400).send('Invalid request');
}
});
// api.ts
export interface CreateComment {
postId: string;
title: string;
body: string;
}
import Ajv from 'ajv';
import apiSchema from './api.schema.json';
import {CreateComment} from './api';
const ajv = new Ajv();
app.post('/comment', (request, response) => {
const {body} = request;
if (!ajv.validate(apiSchema.definitions.CreateComment, body)) {
return response.status(400).send('Invalid request');
}
const comment = body as CreateComment;
// ... application validation and logic ...
return response.status(200).send('ok');
});