Skutopia-org/zod-http-schemas(新しいタブで開きます)
主要言語: TypeScript
Uses zod to define well typed API, generate a router that enforces and validates the api at runtime, & generate a well-typed client
- star 数
- 9
- watcher 数
- 10
- fork 数
- 2
- issue 数
- 3
- 最終更新
- 2026/06/09
主要言語: TypeScript
Uses zod to define well typed API, generate a router that enforces and validates the api at runtime, & generate a well-typed client
zod-http-schemas brings together the best parts of http-schemas and zod into one library that:
At compile time it statically checks:
At runtime, zod-http-schemas:
Use a shared schema for both client and server side code as a single source of truth, ensuring the client and server always agree on the API.
zod-http-schemas uses the zod library for specifying and enforcing schema types.
npm install zod-http-schemas
import {createHttpSchema} from 'zod-http-schemas'; import * as z from 'zod'; // Declare the http schema to be used by both client and server export const apiSchema = createHttpSchema({ 'POST /sum': { requestBody: z.array(z.number()), responseBody: z.number(), }, 'GET /greet/:name': { responseBody: z.string(), }, });
import {createHttpClient} from 'zod-http-schemas/client'; import {apiSchema} from '<path-to-shared-schema>'; // Create a strongly-typed http client. These are cheap to create - it's fine to have many of them. const client = createHttpClient(apiSchema, {baseURL: '/api'}); // Some valid request examples let res1 = client.post('/sum', {body: [1, 2]}); // res1: Promise<number> let res2 = client.get('/greet/:name', {params: {name: 'Bob'}}); // res2: Promise<string> // Some invalid request examples let res3 = client.get('/sum', {body: [1, 2]}); // tsc build error & runtime error let res4 = client.post('/sum', {body: 'foo'}); // tsc build error & runtime error let res5 = client.post('/blah'); // tsc build error & runtime error
zod-http-schemas uses Axios under the hood. Use the same config options with createHttpClient as you would with Axios.
However zod-http-schemas uses its own default validateStatus option that will only reject status codes >= 500. This lets you include common error responses in your schema, without losing typing.
For example, for a post endpoint you might specify
export const apiSchema = createHttpSchema({ 'POST /article': { requestBody: NewArticle, responseBody: z.union([Article, MyGenericApiErrorType]), }, });
Now your clientside code might have a type-guard function that asserts:
import {AxiosResponse} from "axios"; export const isNotErrorResponse = <T, E>( response: AxiosResponse<T> | AxiosResponse<MyGenericApiErrorType> ): response is AxiosResponse<T> => { return response.status < 400; };
and used as such:
const result = await apiClient.post('/article', {body: {title: 'Hello world'}}); if (isNotErrorResponse(result)) { // result is AxiosResponse<Article> in here console.log(result.body); } else { // Some error occured, so result is typed AxiosResponse<MyGenericApiErrorType> console.error(result.body.myGenericErrorProperty) }
import * as express from 'express'; import {createRequestHandler, decorateExpressRouter} from 'http-schemas/server'; import {apiSchema} from '<path-to-shared-schema>'; // Create a strongly-typed express router. const apiRouter = decorateExpressRouter({schema: apiSchema}); // Create a normal express app and mount the strongly-typed router. const app = express(); app.use(express.json()); // it's a normal express app; mount whatever middleware you want app.use('/api', apiRouter); // `apiRouter` is just middleware; mount it wherever you want // Add a request handler directly to the router apiRouter.post('/sum', (req, res) => { let result = req.body.reduce((sum, n) => sum + n, 0); res.send(result); }); // Declare a request handler separately, then add it to the router const greetHandler = createRequestHandler(apiSchema, 'GET', '/greet/:name', (req, res) => { res.send(`Hello, ${req.params.name}!`); }); apiRouter.get('/greet/:name', greetHandler); // Some invalid route handler examples apiRouter.post('/blah', (req, res) => {/*...*/}); // tsc build error & runtime error apiRouter.post('/sum', (req, res) => { req.body.foo[0] }); // tsc build error & runtime error apiRouter.post('/sum', (req, res) => { res.send('foo') }); // tsc build error & runtime error app.listen(8000);
The best way to see http-schemas in action is to see it in a real demonstration with documentation. Take a look at http-schemas-webserver-demo, read the docs, run it and play with it yourself.