Generate a TypeScript REST API client with Fern
Generate TypeScript client code easily with Fern, an automated tool that builds SDKs for major languages, Postman Collections, and OpenAPI definitions. Avoid the slow and brittle manual process, and publish directly to package repositories like NPM.
In this tutorial, we're going to generate TypeScript client code for an existing API to help users of that API avoid writing their own HTTP-level interaction code.
Why? It's slow and brittle, which often means they're not leveraging the full power of your API. The time a user spends this interaction code could be better used getting their product built and integrated, leading to more business for you.
Building client code (also known as Software Development Kits) is not much fun, especially after you've made a few hundred. Automated tooling can be brought in to help avoid this monotonous task, especially with the fantastic code generator Fern.
Fern can help us build SDKs in major programming languages, not just TypeScript, so we can set up one pipeline and get a bunch of SDKs out at once. It will also generate a Postman Collection and OpenAPI definition, keeping them automatically in sync. Fern can even release your code up to package repositories like NPM or Maven, so you don't have to.
1.) Install Fern
It's written in Node, like all the best API tools seem to be.
npm install -g fern-api
Pop into the directory of the API you want to build an SDK for, and generate the Fern config.
fern init
That sends you off to GitHub to create an account. If you don't have a GitHub account it's worth making one as lots of developer tools use it as a login. You don't have to host your code there.
Pop back to the terminal and when the command is done it will have created a directory of fern/
with some config, and an api/
directory (meaning you can have multiple APIs if you like).
2.) API definition formats
Fern can run off its own DSL called the Fern Definition, which you can write and maintain by hand. Fern also supports reading from OpenAPI.
Starting without OpenAPI
If you don't have an OpenAPI definition and you want to start with one, you can:
- generate it from HTTP traffic
- design it with a convenient visual editor
- generate it from your server code like FastAPI makes it easy to do
Starting with OpenAPI (my preference)
Since I always have OpenAPI defined for my APIs before I've even built them, it makes sense for me to point Fern at my openapi.yaml
. Delete the default fern/api
and make a new one from OpenAPI.
fern init --openapi api/openapi.bundled.yaml
I then "bundled" my OpenAPI via Redocly CLI so that Fern wouldn't have to worry about reading OpenAPI that's been split over multiple documents.
3.) Generate the TypeScript SDK
Fern will generate TypeScript by default so we can ignore config for now and just run the generate command.
fern generate
The TypeScript SDK is generated in the cloud and then downloaded to the local filesystem by default, which is handy for seeing how it looks before going any further. Have a click around, see if it looks about right, and if you're a fan, we can start using it in our client code.
It's a fairly safe guess to assume having the TypeScript SDK sat in the source code of the API itself is not very useful, so let's publish it off somewhere that your client application can use it.
Publishing it to the main NPM registry might feel premature at this stage, so Fern has their own NPM registry which you can publish to without everyone seeing it. To use this we can change fern/api/generators.yml
to look like this:
default-group: sdk
groups:
sdk:
generators:
- name: fernapi/fern-typescript-node-sdk
version: 0.7.2
output:
location: npm
url: npm.buildwithfern.com
package-name: "@green-turtle-fern/tree-tracker"
config:
namespaceExport: TreeTracker
That package-name
needs to be set to "@<your-organization>-fern/<hyphenated-namespace-export>"
, which is a little magic feeling at first, but when you've done that each time you generate the client it's going straight up to NPM. No messing around.
4.) Using the Node.js client code
In your client application - the application that will be using the SDK - you'll need to let npm
know you're trying to install code from a non-standard repository.
Create a .npmrc
with the following content.
# .npmrc
@green-turtle-fern:registry=https://npm.buildwithfern.com
Change @green-turtle-fern
to @<your-organization>-fern
and save the file. Now we can install the SDK via NPM.
npm install @green-turtle-fern/tree-tracker
Sorted. Now to use our sweet, idiomatic TypeScript SDK.
# src/run.ts
import { TreeTrackerClient } from "@green-turtle-fern/tree-tracker";
async function main() {
const treeTracker = new TreeTrackerClient({
token: `<redacted>`,
});
const uuid = "a40d4174-f8c6-486a-adff-69364e0c1d18";
try {
const response = await treeTracker.map.getUnitPins(uuid);
console.log({ response });
} catch (error) {
console.log("unknown error", error);
}
}
(async () => {
await main();
})();
Now if I run this simple TypeScript file, Fern should show me either an HTTP error, or a whole bunch of "pins", which are all trees my charity has been planting around the U.K.
npx ts-node run.ts
There they all are!
Next let's dig into those objects a bit and see what data we have available.
Type Safe JSON Bodies
One of the biggest problems I've had in the past working with APIs is that people will just grab random JSON from one place, bung it off to another, occasionally mix it in with other random JSON from somewhere else, and smash that off to some other third place. This litters the whole codebase with weakly typed data which can change at literally any moment, and applications break in wildly unexpected ways as that happens.
Having TypeScript power the SDK solves a lot of that, because it's looking at OpenAPI for what the JSON should be doing, and requiring developers to work with that data in a type-safe way. It's not going to solve all our problems, but it's already made me notice a few undefined properties in my own OpenAPI definitions.
The TypeScript generator makes handy type definitions like this:
export interface MapUnits {
features?: TreeTracker.MapUnitsFeaturesItem[];
type?: string;
}
export interface MapUnitsFeaturesItem {
properties?: TreeTracker.MapUnitsFeaturesItemProperties;
}
export interface MapUnitsFeaturesItemProperties {
id?: number;
name?: string;
species?: string;
what3Words?: string;
}
Fern's TypeScript generator will also case your properties accordingly to best practices in the language, which will often differ from the JSON. That's fine, because this is TypeScript not JSON. I did get a bit confused when I was told what3words
did not exist, but then my code editor suggested what3Words
and all was well again.
Using it is as simple as writing normal TypeScript, it even helped work with .features
and .properties
which are part of the GeoJSON standard and have nothing to do with Fern.
response.features?.forEach((pin) => {
const { id, species, what3Words } = pin.properties;
console.log(`Tree ${id} is a ${species} planted at ${what3Words}.`);
});
Really simple to work with, and it will last a lot longer (and fail a lot more noticeably) than if I was randomly passing JSON around.
Final thoughts
If you are looking for a client code generator, for TypeScript, Go, Python, or Java, you would be hard-pressed to find a better option than Fern.
Their combination of simple CLI interface and the speed of their generation in the cloud means you can build multiple excellent SDKs quickly. Plus, the ability to sync to Postman Collections means you can take care of a whole lot of your "SpecOps" all at once.