Gebna

How to Generate a Typesafe REST API Client Using Swagger & Node.js

Written by Bahaa Zidan

Most backend frameworks nowadays come with OpenAPI specification support baked in or provide a very simple way to add it. All an OpenAPI spec (formerly Swagger spec) does is describe the REST API endpoints of your app. The types/shapes of request, response, headers, …etc.

Assuming our backend exposes this spec. We can use it in JavaScript land to generate a fully typesafe API client. This can work in any modern JS framework. I use Svelte Kit but the exact code can work in React & Next, Vue & Nuxt, or any other framework. All you need is Node.js and the ability to copy and paste.

Install required packages

pnpm add -D dotenv swagger-typescript-api valibot

Wherever you want, create a codegen-swagger.ts file and paste the following code in

import "dotenv/config";
 
import path from "path";
import { generateApi } from "swagger-typescript-api";
import { nonEmpty, object, parse, pipe, string, url } from "valibot";
 
const envSchema = object({
  API_BASE_URL: pipe(string(), url()),
  API_TOKEN: pipe(string(), nonEmpty()),
});
 
const env = parse(envSchema, process.env);
 
const SWAGGER_SCHEMA_URL = `${env.API_BASE_URL}/swagger/doc.json`;
 
async function main() {
  const response = await fetch(SWAGGER_SCHEMA_URL, {
    headers: {
      "X-API-Token": env.API_TOKEN,
    },
  });
  const json = await response.json();
 
  generateApi({
    name: "api",
    spec: json,
    output: path.resolve(process.cwd(), "./src/lib/__generated__"),
  });
}
 
await main();

Let’s break it down:

  1. We use dotenv to load the environment variables
  2. We use valibot to verify the environment variables have the correct shape and type. Feel free to use zod or joi here. I just prefer valibot
  3. Inside an async main function, we fetch the swagger schema. You can add any required credentials in this step.
  4. Lastly we use swagger-typescript-api’s generateApi to generate the typesafe API client.

All that’s left is to add a script in package.json

{
  "scripts": {
    "codegen:swagger": "node --experimental-strip-types ./scripts/codegen-swagger.ts"
  }
}

Note that in future Node.js versions you may not need the --experimental-strip-types flag to run typescript files. I’m running Node version 22.13 and I need it here

Run the script and you should see the client generated in src/lib/__generated__

Here’s a simple demonstration:

Create an API instance

import { API_BASE_URL } from "$env/static/private";
 
import { Api } from "./__generated__/api";
 
export const api = new Api({
  baseUrl: API_BASE_URL,
});

Use that instance to call any endpoint you want

import { API_TOKEN } from "$env/static/private";
import { api } from "$lib";
 
export async function companyDetails(id: string) {
  const response = (
    await api.company.companyDetail(id, {
      headers: {
        "X-API-Token": API_TOKEN,
      },
    })
  ).data.data;
 
  return response;
}