How-to guidesGenerate a client and a server
Generate a client and a server
This guide generates both sides of an API from its OpenAPI document, openapi.yaml: a TypeScript
client for the Fetch API and an Express server. The same steps work for the other targets of
apigenie-generator, in Java, Go, Python, C# and Elixir, and for
AsyncAPI, GraphQL and Protobuf documents.
You need the CLI installed; the Quickstart shows how. Lint the document first (Lint and format API descriptions): a clean contract gives cleaner code.
Generate the client
apigenie-generator client fetch openapi.yaml -o client --package-name shipping-client
The output is an npm package with no runtime dependencies: requests go through the global
fetch(), so the client runs in browsers and in Node.js 18 or later. Each operation is an async
function named after its operationId, with its parameters and its result typed from the document:
import { configure, ApiError, listShipments, getShipment } from "shipping-client";
configure({
baseUrl: "https://api.example.com/v2",
auth: { apiKey: "ce_live_123" },
});
try {
const page = await listShipments({ status: ["in_transit"], limit: 20 });
const shipment = await getShipment({ shipmentId: "shp_01J9Z" });
} catch (error) {
if (error instanceof ApiError) {
console.error(error.status, error.body);
}
}
To compile the client as part of your own project instead of as a package, set layout: sources
for client_fetch in the configuration (see Configure the generators
below): the generator then writes only the sources.
Generate the server
apigenie-generator server express openapi.yaml -o server --package-name shipping-server
The generator writes typed handler interfaces and a router that decodes and validates every request. Your code implements the handlers in files of its own, so regenerating never overwrites it:
import express from "express";
import { BASE_PATH, createRouter, HttpError, type ShipmentsHandlers } from "shipping-server";
const shipments: Partial<ShipmentsHandlers> = {
async getShipment(request) {
const shipment = await db.shipments.find(request.params.shipmentId);
if (shipment === undefined) {
throw new HttpError(404, "No such shipment");
}
return { status: 200, body: shipment };
},
};
const app = express();
app.use(BASE_PATH, createRouter({ shipments }));
app.listen(8080);
Operations are grouped by their first tag (ShipmentsHandlers), and the status, body and headers
a handler returns are typed per status from the document. A change to the contract that the
handlers don’t follow is a compile error.
Configure the generators
Generator settings go in the commands.generate section of apigenie.yaml, one entry per target,
so the command lines stay short and the settings are reviewed like code. Here the client’s imports
suit a bundler, and an overlay adjusts the document for the server:
version: '1.0'
commands:
generate:
client_fetch:
import_extension: ''
server_express:
overlay:
- overlays/express.overlay.yaml
Each target’s page lists its settings and the vendor extensions it reads, such as
client fetch and
server express. Extensions that matter to one generator
only can live in an OpenAPI Overlay of that generator, listed under
overlay or given with --overlay, instead of in the API description.
Regenerate in CI
Generate into the repository and let CI check that the generated code matches the contract, so nobody edits it by hand and nobody forgets to regenerate:
apigenie-generator client fetch openapi.yaml -o client --package-name shipping-client
apigenie-generator server express openapi.yaml -o server --package-name shipping-server
git diff --exit-code client server
Once the server runs, check that it keeps the contract with a validating proxy: Mock and contract-test an API.
Next steps
- apigenie-generator: every target, by standard.
- Overlays: settings outside the API description.
- Generate code: what the generator covers.