- Notifications
You must be signed in to change notification settings - Fork40
Generate a headless Typescript API client from an OpenAPI spec - optionally with a @tanstack/react-query client using queryOptions
License
astahmer/typed-openapi
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
Generate a Typescript API client from an OpenAPI spec
- Headless API client,bring your own fetcher (fetch, axios, ky, etc...) ! (You can generate that file with
--default-fetcher) - Generates a fully typesafe API client with just types by default (instant suggestions)
- Type-safe error handling: with discriminated unions and configurable success/error status codes
- withResponse & throwOnStatusError: Get a union-style response object or throw on configured error status codes, with full type inference
- TanStack Query integration: with
withResponseandselectFnoptions for advanced success/error handling - Or you can also generate a client with runtime validation using one of the following runtimes:
The generated client is a single file that can be used in the browser or in node. Runtime validation schemas areprovided by the excellenttypebox-codegen
pnpm add typed-openapi
It exports a bunch of functions that can be used to build your own tooling on top of it. You can look at theCLI code so see how to use them.
npx typed-openapi -h
typed-openapi/2.0.0Usage: $ typed-openapi<input>Commands:<input> GenerateFor more info, run anycommand with the`--help` flag: $ typed-openapi --helpOptions: -o, --output<path> Output pathfor the api client ts file (defaults to`<input>.<runtime>.ts`) -r, --runtime<n> Runtime to usefor validation; defaults to`none`; available: Type<"arktype"|"io-ts"|"none"|"typebox"|"valibot"|"yup"|"zod"> (default: none) --schemas-only Only generate schemas, skipping client generation (defaults to false) (default: false) --include-client Include API client types and implementation (defaults to true) (default: true) --success-status-codes<codes> Comma-separated list of success status codes (defaults to 2xx and 3xx ranges) --error-status-codes<codes> Comma-separated list of error status codes (defaults to 4xx and 5xx ranges) --tanstack [name] Generate tanstack client, defaults to false, can optionally specify a name (will be generated next to the main file) or absolute pathfor the generated file --default-fetcher [name] Generate default fetcher, defaults to false, can optionally specify a name (will be generated next to the main file) or absolute pathfor the generated file -h, --help Display this message -v, --version Display version number
Caring too much about the runtime validation code. If that works (thanks totypebox-codegen), that's great, otherwise I'm not really interestedin fixing it. If you are, feel free to open a PR.
Supporting all the OpenAPI spec. Regex, dates, files, whatever, that's not the point here.openapi-zod-client does a great job at that, but it's slow togenerate the client and the suggestions in the IDE are not instant. I'm only interested in supporting the subset ofthe spec that makes the API client typesafe and fast to provide suggetions in the IDE.
Splitting the generated client into multiple files. Nope. Been there, done that. Let's keep it simple.
Basically, let's focus on having a fast and typesafe API client generation instead.
The generated client is headless - you need to provide your own fetcher. Here are ready-to-use examples:
- Basic API Client - Simple, dependency-free wrapper
- Validating API Client - With request/response validation
You can choose between two response styles:
Direct data return (default):
constuser=awaitapi.get("/users/{id}",{path:{id:"123"}});// Throws TypedResponseError on error status (default)
Union-style response (withResponse):
constresult=awaitapi.get("/users/{id}",{path:{id:"123"},withResponse:true});if(result.ok){// result.data is typed as User}else{// result.data is typed as your error schema for that status}
You can also control error throwing withthrowOnStatusError.
All errors thrown by the client are instances ofTypedResponseError and include the parsed error data.
For dynamic endpoint calls or when you need more control:
// Type-safe generic request methodconstresponse=awaitapi.request("GET","/users/{id}",{path:{id:"123"},query:{include:["profile","settings"]}});constuser=awaitresponse.json();// Fully typed based on endpoint
Generate TanStack Query wrappers for your endpoints with:
npx typed-openapi api.yaml --tanstack
You get:
- Type-safe queries and mutations with full error inference
withResponseandselectFnfor advanced error and response handling- All mutation errors are Response-like and type-safe, matching your OpenAPI error schemas
// Basic queryconstaccessiblePagesQuery=useQuery(tanstackApi.get('/authorization/accessible-pages').queryOptions);// Query with query parametersconstmembersQuery=useQuery(tanstackApi.get('/authorization/organizations/:organizationId/members/search',{path:{organizationId:'org123'},query:{searchQuery:'john'}}).queryOptions);// With additional query optionsconstdepartmentCostsQuery=useQuery({ ...tanstackApi.get('/organizations/:organizationId/department-costs',{path:{organizationId:params.orgId},query:{period:selectedPeriod},}).queryOptions,staleTime:30*1000,// placeholderData: keepPreviousData,// etc});
or if you need it in a routerbeforeLoad /loader:
import{tanstackApi}from'#api';awaitqueryClient.fetchQuery(tanstackApi.get('/:organizationId/remediation/accounting-lines/metrics',{path:{organizationId:params.orgId},}).queryOptions,);
The mutation API supports both basic usage and advanced error handling withwithResponse and custom transformations withselectFn.Note: All mutation errors are Response-like objects with type-safe error inference based on your OpenAPI error schemas.
// Basic mutation (returns data only)constbasicMutation=useMutation({// Will throws TypedResponseError on error status ...tanstackApi.mutation("post",'/authorization/organizations/:organizationId/invitations').mutationOptions,onError:(error)=>{// error is a Response-like object with typed data based on OpenAPI specconsole.log(errorinstanceofResponse);// trueconsole.log(error.status);// 400, 401, etc. (properly typed)console.log(error.data);// Typed error response body}});// With error handling using withResponseconstmutationWithErrorHandling=useMutation(tanstackApi.mutation("post",'/users',{// Returns union-style result, never throwswithResponse:true}).mutationOptions);// With custom response transformationconstcustomMutation=useMutation(tanstackApi.mutation("post",'/users',{selectFn:(user)=>({userId:user.id,userName:user.name})}).mutationOptions);// Advanced: withResponse + selectFn for comprehensive error handlingconstadvancedMutation=useMutation(tanstackApi.mutation("post",'/users',{withResponse:true,selectFn:(response)=>({success:response.ok,user:response.ok ?response.data :null,error:response.ok ?null :response.data,statusCode:response.status})}).mutationOptions);
// Basic usagebasicMutation.mutate({body:{emailAddress:'user@example.com',department:'engineering',roleName:'admin'}});// With error handling// All errors thrown by mutations are type-safe and Response-like, with parsed error data attached.mutationWithErrorHandling.mutate({body:userData},{onSuccess:(response)=>{if(response.ok){toast.success(`User${response.data.name} created!`);}else{if(response.status===400){toast.error(`Validation error:${response.data.message}`);}elseif(response.status===409){toast.error('User already exists');}}}});// Advanced usage with custom transformationadvancedMutation.mutate({body:userData},{onSuccess:(result)=>{if(result.success){console.log('Created user:',result.user.name);}else{console.error(`Error${result.statusCode}:`,result.error);}}});
If you need to make a custom mutation you could use theapi directly:
const{mutate:login, isPending}=useMutation({mutationFn:async(type:'google'|'microsoft')=>{returnapi.post(`/authentication/${type}`,{body:{redirectUri:search.redirect}});},onSuccess:(data)=>{window.location.replace(data.url);},onError:(error,type)=>{console.error(error);toast({title:t(`toast.login.${type}.error`),icon:'warning',variant:'critical',});},});
openapi-zod-client, which generates azodios client but can be slow to provide IDE suggestions when the OpenAPI spec islarge. Also, you might not always want to use zod or even runtime validation, hence this project.
pnpm ipnpm buildpnpm test
When you're done with your changes, please runpnpm changeset in the root of the repo and follow the instructionsdescribedhere.
About
Generate a headless Typescript API client from an OpenAPI spec - optionally with a @tanstack/react-query client using queryOptions
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Uh oh!
There was an error while loading.Please reload this page.
