Server Integration
Learn how to integrate the client with your Altstack server.
Overview
To use the client with your server, you need:
- Generate an OpenAPI spec from your server router
- Generate
RequestandResponsetypes from the OpenAPI spec - Create a client instance with these types
Step 1: Generate OpenAPI Spec
On your server, generate the OpenAPI spec:
// server.ts
import { init, createServer, generateOpenAPISpec } from "@alt-stack/server-hono";
import { z } from "zod";
const factory = init();
const router = factory.router()
.get("/users/{id}", {
input: {
params: z.object({ id: z.string() }),
},
output: z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
}),
})
.handler((ctx) => {
return {
id: ctx.input.id,
name: "Alice",
email: "alice@example.com",
};
});
const app = createServer({ api: router });
// Generate OpenAPI spec
const openApiSpec = generateOpenAPISpec(
{ api: router },
{
title: "My API",
version: "1.0.0",
}
);
export { openApiSpec };
export default app;
Step 2: Generate Request and Response Types
Use the @alt-stack/zod-openapi package to generate TypeScript types:
// generate-types.ts
import { openApiToZodTsCode } from "@alt-stack/zod-openapi";
import { openApiSpec } from "./server.js";
import { writeFileSync } from "fs";
const generatedCode = openApiToZodTsCode(openApiSpec, undefined, {
includeRoutes: true,
});
writeFileSync("./src/generated-types.ts", generatedCode);
This generates a file with:
- Zod schemas for all request parameters, query strings, bodies, and responses
Requestobject with lookup for request schemasResponseobject with lookup for response schemas organized by status code
Example generated output:
// generated-types.ts
import { z } from "zod";
export const GetUsersIdParamsSchema = z.object({
id: z.string(),
});
export const GetUsersId200ResponseSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
});
export const GetUsersId404ErrorResponseSchema = z.object({
error: z.object({
code: z.literal("NOT_FOUND"),
message: z.string(),
}),
});
export const Request = {
"/users/{id}": {
GET: {
params: GetUsersIdParamsSchema,
},
},
} as const;
export const Response = {
"/users/{id}": {
GET: {
"200": GetUsersId200ResponseSchema,
"404": GetUsersId404ErrorResponseSchema,
},
},
} as const;
Step 3: Create Client
Now create the client using the generated types:
// client.ts
import { createApiClient } from "@alt-stack/http-client-fetch";
import { Request, Response } from "./generated-types.js";
const client = createApiClient({
baseUrl: "http://localhost:3000",
Request,
Response,
});
export { client };
Step 4: Use the Client
Now you can make type-safe API calls:
import { client } from "./client.js";
// Type-safe GET request
const result = await client.get("/users/{id}", {
params: { id: "123" },
});
if (result.success) {
// result.body is typed based on your output schema
console.log(result.body.name); // ✅ Type-safe
}
Keeping Types in Sync
It's recommended to regenerate types whenever you change your server routes. You can:
- Manual regeneration: Run your type generation script when routes change
- Watch mode: Use a file watcher to regenerate on route changes
- Build step: Include type generation in your build process
Sharing Types Between Projects
If your client is in a separate project from your server:
- Export the OpenAPI spec from your server project
- Share it via npm package, git submodule, or API endpoint
- Generate types in your client project from the shared spec
Example: Export spec as JSON endpoint:
// server.ts
const docsRouter = createDocsRouter({ api: router });
app.route("/docs", docsRouter);
// Access at /docs/openapi.json
Then fetch and generate types in client:
// client project
const response = await fetch("http://localhost:3000/docs/openapi.json");
const openApiSpec = await response.json();
const generatedCode = openApiToZodTsCode(openApiSpec, undefined, {
includeRoutes: true,
});