This guide explains how to generate a typed HTTP client from a Swagger/OpenAPI specification, how to use it in your project, and why Zod is used for validation.
This script reads a swagger.json file and generates:
- A class-based HTTP client (
HttpClient) with one method per endpoint. - Zod schemas for:
queryparameterspathparametersrequestbodyresponsevalidation
- Auto-validation on both inputs and outputs.
Each endpoint becomes a method in a reusable client instance:
import { httpClient } from '@modules/core/services/httpClient';
await httpClient.getV1Hotels({ query: { destination: '123' } });You can generate the client using:
yarn generate:sdkThis command runs the generator with the following parameters:
--input=https://api-gateway-dev.travelagentadmin.net/v1/swagger/swagger.json- The Swagger/OpenAPI endpoint URL--output=./- The output directory (defaults to current directory)
You can override the default parameters by running the script directly:
# Custom Swagger URL and output directory
ts-node --project scripts/tsconfig.scripts.json scripts/generateHttpClient.ts \
--input=https://your-api.com/swagger.json \
--output=../src/generated/
# Just custom Swagger URL (uses default output directory)
ts-node --project scripts/tsconfig.scripts.json scripts/generateHttpClient.ts \
--input=https://your-api.com/swagger.jsonNote: The output filename is always HttpClientFactory.ts and cannot be changed.
The generated client will be written to:
scripts/HttpClientFactory.ts (or your specified output directory)
This file is automatically overwritten each time you run the command.
You can see an example of a generated HttpClientFactory.ts file at:
./demo/src/core/factories/HttpClientFactory.ts
This file was generated from a real Swagger/OpenAPI specification and shows the structure and methods that will be created for your API endpoints.
Import and call the generated httpClient methods like this:
import { httpClient } from '@modules/core/services/httpClient';
await httpClient.getV1Hotels({
query: {
destination: '123',
check_in: '2025-05-01',
check_out: '2025-05-05',
},
});
// ✅ Call a health check with no arguments:
await httpClient.getV1Health();
// 📦 Call an endpoint with a request body:
await httpClient.postV1Bookings({
body: {
property_id: 'abc',
rooms: 1,
guest_rooms: [
{
adults: 2,
children: 0,
children_ages: [],
},
],
},
});All parameters are validated using Zod. If they don't match the expected schema, an error will be thrown before sending the request.
If the backend returns a failed response (e.g. 400 or 500), the client:
- Parses the error body (JSON or plain text)
- Throws it directly — no Zod parsing is applied to failed responses
try {
await httpClient.getV1Something();
} catch (err) {
console.error('API Error:', err);
// err can be a string, an object with `message`, or any format returned by the backend
}This approach preserves useful backend messages like:
"Bad Request""Missing required field: property_id"Custom error formats
Using Zod adds runtime validation to the client, which provides several advantages over relying solely on TypeScript types:
- ✅ Runtime safety: TypeScript types are erased at runtime. Zod ensures that what you send and receive actually matches the defined structure.
- 🧪 Immediate feedback: If the Swagger schema changes, or if your usage is incorrect, Zod will throw helpful validation errors during development.
- 🧱 Defensive programming: Protects your app from malformed inputs or unexpected backend responses.
- 🧼 Centralized validation: The same Zod schema is used for request inputs and for validating server responses.
- 📐 Autocompletion & type inference: You get strong IDE support, with full type inference based on Zod schemas.
This makes the client more robust and reduces the chances of subtle bugs due to backend/frontend contract mismatches.