DEV Community

Cover image for Generate API Clients: A new way to consume REST APIs and GraphQL
Hồng Phát
Hồng Phát

Posted on

Generate API Clients: A new way to consume REST APIs and GraphQL

Integrating HTTP APIs often involves tedious double-checking:

  • Was the endpoint GET /sheep or GET /sheeps?
  • Should the parameters be put in the URL query string, request body, or even headers?
  • Is the API server expecting to receive parameters in the Content-Type application/json or x-www-form-url-encoded?

By generating HTTP API clients from existing API endpoints using the approaches below, we can make the development more enjoyable while improving the UX by reducing bundle size and startup time.

Why generate API clients?

  • Absolute accuracy: Generated clients follow specifications such as GraphQL SDL or Swagger OpenAPI to ensure your server receives what it demands. Things can’t go wrong unless you choose to disregard TypeScript declarations by unleashing the power of the any keyword.
  • Long-term productivity: It takes only a few minutes to generate your API client, which then saves you hours and hours writing functions for each endpoint, manually defining their request params and response payloads. What's more, it saves you the effort of fixing any potential errors by doing so manually. I've been there!
  • Outstanding IDE support: Witness yourself in the GraphQL section below!

How to generate API Clients?

RESTful APIs via swagger-typescript-api

As the library name suggests, using this approach requires your RESTful APIs documented using the OpenAPI Specification (formerly Swagger). Server-side frameworks like Java Spring can often generate this documentation for you effortlessly.

If you use other specifications, there might be an equivalent library available.

Steps:

  1. Install swagger-typescript-api:

    npm install -D swagger-typescript-api
    
  2. Generate your client:
    You can either execute the command directly or add a script entry to your package.json file for easy regeneration when your backend APIs change:

    "scripts": {
      "gen:rest": "swagger-typescript-api -p https://your-server.com/rest-api-endpoint/docs -o ./src/services -n rest.ts"
    }
    

    Make sure the -p parameter points to your Swagger schema, usually a JSON/YML endpoint (not the Web UI endpoint). Run npm run gen:rest to generate your client into src/services/rest.ts.

  3. Use generated client in your code:

    import { Api } from "services/rest";
    
    const client = new Api({
      baseUrl,
      securityWorker, // Use this to set up Bearer tokens in request headers
    });
    
    const { data } = await client.api.getProducts({
      page: 1,
      limit: 10,
      ... // TypeScript will let you know which params can be passed in
    });
    

The best part is that your response data is always strongly typed. This means that whenever you type data. and press Ctrl+Space or + Esc, your IDE will let you know precisely what lives inside your data, fields that you didn't even know existed and fields that you think you could use but, in fact, could not. No verbose code is required!

GraphQL via genql

I actually discovered this mechanism and decided to write a blog about it while working with GraphQL. As someone who embraces the KISS principle, I found working with Apollo clients to be quite daunting: writing GraphQL queries within large JavaScript strings with limited extension support, intricate approach to customizing outgoing requests (such as placing tokens in headers and retrying after token refreshes), or caching behaviour that often differed from expectations.

On the other hand, generating clients for GraphQL is much simpler because all endpoints already adhere to a single specification. Honestly, I believe genql should be the official solution for web GraphQL clients.

Steps:

  1. Install @genql/cli:

    npm install -D @genql/cli
    
  2. Generate your client:

    "scripts": {
      "gen:gql": "genql --endpoint https://your-server.com/graphql/ --output ./src/services/graphql"
    }
    

    Then, you can simply run npm run gen:gql to generate your client.

  3. Use generated client in your code:

    import { createClient } from "services/graphql";
    
    const client = createClient({
      fetch, // Customize the fetch function to add JWT tokens or implement retry logic
    });
    
    const data = await client.query({
      // IDEs with TypeScript support will assist you with typing all of the code below effortlessly
      products: {
        name: true, // Pick only the fields you want
        __scalar: true, // Or pick all the fields with primitive values
        reviews: {
          rating: true, // Nested picking allowed
        },
      },
    });
    

What is truly impressive: you get a subtype with only the fields you requested. Your IDE will discourage you from typing .content after data.products[0].reviews[0] because it wasn't queried, even though it exists in the user schema. This would require much more verbose code in Apollo clients. Kudos to the genql and TypeScript developers!

Conclusion

Generated TypeScript API clients offer a powerful and efficient way to interact with HTTP APIs, whether you are working with GraphQL or REST. If you're looking to improve your API consumption workflow, I highly recommend giving them a try!

Top comments (0)