Jul 9, 2024

[Orval] Generate API Client and SWR Hooks via TypeSpec

In the previous post I generated an OpenAPI YAML from a TypeSpec API definition.

Now that we have an OpenAPI spec, let’s generate client code and even React hooks for data fetching from the frontend.

With Orval, we can generate hooks for SWR, so let’s try it.

What is SWR?

SWR is a data‑fetching hooks library for React providing caching, error handling, and more.

It makes implementing API requests in the frontend easy. Example from the docs:

function Profile() {
  const { data, error, isLoading } = useSWR('/api/user', fetcher)

  if (error) return <div>failed to load</div>
  if (isLoading) return <div>loading...</div>
  return <div>hello {data.name}!</div>
}

What is Orval?

Orval generates TypeScript clients from OpenAPI (Swagger). Features:

  1. Generate TypeScript models
  2. Generate HTTP calls
  3. Generate MSW (Mock Service Worker) mocks

It can also generate SWR hooks, React Query, Angular clients, etc.

Install

npm i orval -D

Prepare files

We’ll need the TypeSpec tsp file and Orval config.

main.tsp

Define a simple API with TypeSpec:

using TypeSpec.Http;

model User {
	id: string;
	name: string;
	email: string;
}

@route("/users")
interface Users {
	@get
	getUsers(): User[];

	@get
	@route("/{id}")
	getUser(@path id: string): User;
}

Generate openapi.yml as in the previous post:

tsp compile main.tsp --emit @typespec/openapi3

orval.config.ts

We want SWR hooks; docs:

Create orval.config.ts:

module.exports = {
  userstore: {
    input: {
      target: "./openapi.yml",
    },
    output: {
      mode: "split",
      target: "./api/endpoints",
      schemas: "./api/models",
      client: "swr",
    },
  },
};

This places model types under api/models and SWR files under api/endpoints.

Run prettier on generated files

Use afterAllFilesWrite hook:

module.exports = {
  userstore: {
    input: {
      target: "./openapi.yml",
    },
    output: {
      mode: "split",
      target: "./api/endpoints",
      schemas: "./api/models",
      client: "swr",
    },
    hooks: {
      afterAllFilesWrite: "prettier --write",
    },
  },
};

Generate

npx orval

Warnings may appear, but files should be generated under api.

models/user.ts

Contains data models used by the API, reflecting the TypeSpec definitions:

export interface User {
  email: string;
  id: string;
  name: string;
}

endpoints/userAPI.ts

SWR hooks for each API endpoint are generated, e.g.:

export const useUsersGetUsers = (...) => { /* ... */ }
export const useUsersGetUser = (id: string, ...) => { /* ... */ }

Usage:

const { data, error, isLoading } = useUsersGetUsers();

You can pass SWR options; e.g., disable caching with enabled: false:

const { data, error, isLoading } = useUsersGetUsers({
  swr: {
    enabled: false,
  },
});

Set API base URL

By default the generated client requests the same origin as the frontend. To target a different domain, set axios’s baseURL (Orval uses axios under the hood):

axios.defaults.baseURL = '<BACKEND URL>';

See also: Base URL guide.

Summary

Workflow:

  1. Generate OpenAPI from TypeSpec tsp files.
  2. Use Orval to generate SWR hooks for the frontend.

Since Orval can also generate MSW mocks, you can proceed with frontend dev using a TypeSpec definition even before the backend is ready.