Consume OpenAPI in TypeScript Without Code Generation
Tags: openapi, typescript
Published: 2023-05-08 14:40:41

> This article was published on Monday, April 24, 2023 by [Aleksandra Sikora](https://twitter.com/aleksandrasays) @ [The Guild Blog](https://the-guild.dev/)

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](https://the-guild.dev/blog/fetch-for-servers))

🔎 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:

```jsx
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.

```tsx
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.

```jsx
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:

```tsx
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](https://trpc.io/docs) with an
[OpenAPI plugin](https://github.com/jlalmes/trpc-openapi) as the provider and then create a client
for it using feTS.

Here’s an example:

```tsx
// 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](https://github.com/ardatan/fets/tree/master/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](https://github.com/ardatan/feTS/blob/master/examples/todolist/src/index.ts).

## Alternatives and Comparison

| Feature                        | fets                                              | [got](https://github.com/sindresorhus/got/) | [node-fetch](https://github.com/node-fetch/node-fetch/) | [ky](https://github.com/sindresorhus/ky/) | [axios](https://github.com/axios/axios/) | [superagent](https://github.com/visionmedia/superagent/) | [undici](https://github.com/nodejs/undici/) | [swagger-js](https://github.com/swagger-api/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!