Integrating HTTP APIs often involves tedious double-checking:
- Was the endpoint
GET /sheep
orGET /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
orx-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:
-
Install
swagger-typescript-api
:
npm install -D swagger-typescript-api
-
Generate your client:
You can either execute the command directly or add a script entry to yourpackage.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). Runnpm run gen:rest
to generate your client intosrc/services/rest.ts
. -
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:
-
Install
@genql/cli
:
npm install -D @genql/cli
-
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. -
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)