Consume OpenAPI in TypeScript Without Code Generation
Over the last few years, TypeScript, have been widely used to provide a type-safe development experience and de facto became a standard. However, generating types from an API typically requires a build step using a command-line interface (CLI) tool. Otherwise, you can write these types manually, but that means more work and can easily get outdated.
We, The Guild, are a group of developers mainly working on the GraphQL ecosystem, but one of the biggest pain points we see in our usersβ codebases is consuming REST APIs in a GraphQL BFF (backend-for-frontend).
Even when you provide a well-crafted schema for the consumer (frontend) in GraphQL, finding solutions to maintain the same type-safe experience behind the scenes when communicating with underlying REST-based services can be challenging. The individual teams developing these services often have varying levels of expertise in GraphQL and use different languages and frameworks, such as Java, Spring Boot, or .NET.
One common standard embraced by these teams is OpenAPI. Formerly known as Swagger, it become a standard within the REST community for describing APIs. It provides a comprehensive description of an APIβs capabilities, allowing consumers to understand its functionality without needing to know the details of its implementation.
π₯Β Introducing feTS Client π₯
Weβre thrilled to show you what weβve been building recently β a feTS Client that allows you to create an SDK-like client that infers types from an OpenAPI specification document.
feTS Client is not just another code generator that requires an additional build step. Instead, itβs a tool designed to work exclusively during build time, specifically during TypeScript transpilation. This means that it doesnβt add the entire schema to your final code bundle, as it doesnβt perform any runtime operations. As a result, feTS Client offers a lightweight and efficient solution for maintaining type safety in your code without impacting the size of your compiled assets.
In summary, feTS Client means:
πΒ Type-safety out of the box β no CLI, no build step
πΒ No runtime overhead β itβs super lightweight and performant
πͺΒ Support across different JavaScript environments, including Node.js, Deno, BUN, Cloudflare Workers, and AWS Lambda (it utilizes the web standard Fetch API)
πΒ IDE features like Go To Definition
to explore the API from TypeScript
Now, letβs see the code!
Quick Start
You can install feTS client with the following command:
pnpm add fets
# or yarn add fets
# or npm i fets
Using feTS Client is as simple as importing the createClient
function and providing the URL to
your API.
import { createClient, type NormalizeOAS } from 'fets'
import openAPIDoc from './openapi-doc'
const client = createClient<NormalizeOAS<typeof openAPIDoc>>({
endpoint: 'http://my-api.com/api'
})
Note: we have to use an NormalizeOAS
type here β it resolves all $refs
in the OpenAPI document
and normalizes the types for the createClient
βs generic parameter.
Once youβve done that, you can call the endpoints as defined in your OpenAPI Schema. The intuitive design allows you to take full advantage of the auto-completion features provided by your IDE, streamlining the development process and ensuring that youβre working with accurate, up-to-date types for your API calls.
const response = await client['/todo/:id'].get({
params: {
id: '1'
}
})
if (response.status === 404) {
console.error('Todo 1 not found')
return
}
const post = await response.json()
console.log('Todo 1', post)
What about Middlewares, Plugins, or Auth?
Similar to other HTTP clients, feTS Client offers a plugin system that allows you to hook into various stages of the HTTP connection. This flexibility enables you to customize and extend the functionality of the feTS Client, tailoring it to your specific needs and optimizing your applicationβs network interactions. Hereβs an example:
import { createClient, Plugin, type NormalizeOAS } from 'fets'
function useAuth(): Plugin {
return {
async onRequestInit({ requestInit }) {
const token = await getMyToken()
requestInit.headers.authorization = `Bearer ${token}`
}
}
}
const client = createClient<NormalizeOAS<typeof openAPIDoc>>({
endpoint: 'http://my-api.com/api',
plugins: [useAuth()]
})
What about the Server?
The motivation behind creating feTS Client stemmed from the desire to have an agnostic solution that could work seamlessly across various platforms and environments. The goal was to decouple client and server, enabling developers to maintain type safety and efficient communication between components without being constrained by specific technologies or frameworks.
That means you can use anything on the server side as long as it gives you an OpenAPI spec.
Usage with an Existing tRPC Router
You can use tRPC with an OpenAPI plugin as the provider and then create a client for it using feTS.
Hereβs an example:
// Setting up a tRPC router with OpenAPI support
import { initTRPC } from '@trpc/server';
import { OpenApiMeta } from 'trpc-openapi';
const t = initTRPC.meta<OpenApiMeta>().create();
export const appRouter = t.router({
sayHello: t.procedure
.meta({ /* π */ openapi: { method: 'GET', path: '/say-hello' } })
.input(z.object({ name: z.string() }))
.output(z.object({ greeting: z.string() }))
.query(({ input }) => {
return { greeting: `Hello ${input.name}!` };
});
});
// Starting the HTTP server
import { createOpenApiHttpHandler } from 'trpc-openapi';
const server = http.createServer(createOpenApiHttpHandler({ router: appRouter })); /* π */
server.listen(3000);
// Getting the OpenAPI spec
import { generateOpenApiDocument } from 'trpc-openapi';
export const openApiDocument = generateOpenApiDocument(appRouter, {
title: 'tRPC OpenAPI',
version: '1.0.0',
baseUrl: 'http://localhost:3000',
});
// save openApiDocument to a file, e.g. openapi.ts. Add `as const`:
export const oas = {...} as const;
// And finally β creating a FEST client
import { createClient, NormalizeOAS } from 'fets';
import { oas } from "./openapi";
const client = createClient<NormalizeOAS<typeof oas>>({
endpoint: 'http://my-api.com/api',
});
You can check out the full example in the examples folder.
feTS Server
Thereβs also a server part of the feTS project that you can use to create the provider part of your API β it allows you to create a fully type-safe REST API. You can check out this file to see how itβs being used and how to generate the OpenAPI spec: feTS server example.
Alternatives and Comparison
Feature | fets | got | node-fetch | ky | axios | superagent | undici | swagger-js |
---|---|---|---|---|---|---|---|---|
Type-safety out of the box | β | β | β | β | β | β | β | β |
Custom plugins support | β | β | β | β | β | β | β | β |
JavaScript environment support | Node.js, Deno, Bun, Cloudfare Workers, AWS Lambda | Node.js | Node.js, Browser | Node.js, Browser, Deno | Node.js, Browser | Node.js, Browser | Node.js | Node.js, Browser |
HTTP/2 support | β | β gi | β | β | β | β | β | β |
Promise API | β | β | β | β | β | β | β | β |
Stream API | β | β | Node.js only | β | β | β | β | β |
Pagination API | β | β | β | β | β | β | β | β |
Request cancellation | β | β | β | β | β | β | β | β |
RFC compliant caching | β | β | β | β | β | β | β | β |
Cookies (out-of-box) | β | β | β | β | β | β | β | β |
Follows redirects | β | β | β | β | β | β | β | β |
Retries on failure | β (via plugins) | β | β | β | β | β | β | β |
Progress events | β | β | β | β | Browser only | β | β | β |
Handles gzip/deflate | β | β | β | β | β | β | β | β |
Advanced timeouts | β | β | β | β | β | β | β | β |
Timings | β | β | β | β | β | β | β | β |
Hooks | β (via plugins) | β | β | β | β | β | β | β |
Summary
Key features of feTS Client include type safety out of the box, no runtime overhead, support for different JavaScript environments, and IDE TypeScript support. With a simple setup, you can call endpoints as defined in your OpenAPI schema with full confidence given by the type-safe client. And if you need more features, you can also extend and customize it with the plugin system.
Weβre super excited for you to try the feTS Client. As always, let us know your thoughts, so we can make it even better!
Join our newsletter
Want to hear from us when there's something new?
Sign up and stay up to date!
*By subscribing, you agree with Beehiivβs Terms of Service and Privacy Policy.