You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

基于TypeScript的类型安全客户端-服务端API实现方案问询

Optimized TypeScript Solution for Type-Safe API Contracts

Great question! This is a common pain point when building TypeScript full-stack apps—redundant type definitions, mismatched request/response types, and forgotten endpoint registrations can lead to bugs and maintenance headaches. Let's walk through a TypeScript-native solution to enforce contract consistency, then cover code generation tools that can automate this workflow.


1. Type-Native Contract Binding (No Code Gen Needed)

We can use TypeScript's advanced type system to create a single source of truth for API contracts, then derive client/server functions and enforce endpoint registration automatically.

Step 1: Define a Unified API Contract

First, create a shared file that ties together endpoints, request arguments, and response types for all your APIs:

// ./api.ts
// Base type for all API commands
type ApiCommand = {
  endpoint: string;
  arg: unknown;
  resp: unknown;
};

// Individual API contracts
type GetUser = {
  endpoint: '/users';
  arg: { id: string };
  resp: { name: string };
};

type CreateUser = {
  endpoint: '/users/create';
  arg: { name: string };
  resp: { id: string; name: string };
};

// Union of all API commands (enforces completeness later)
type AllApiCommands = GetUser | CreateUser;

// Derive client function type: auto-infers endpoint, args, and return type
type ClientFn<Cmd extends ApiCommand> = 
  (http: HttpClient, arg: Cmd['arg']) => Promise<Cmd['resp']>;

// Derive server handler type: auto-infers args and return type
type ServerHandler<Cmd extends ApiCommand> = 
  (db: Db, arg: Cmd['arg']) => Promise<Cmd['resp']>;

// Force server to implement handlers for ALL endpoints
type ServerHandlerRegistry = {
  [Cmd in AllApiCommands as Cmd['endpoint']]: ServerHandler<Cmd>;
};

Step 2: Type-Safe Client Implementation

The client can use the contract to create strongly-typed functions without repeating type definitions:

// ./client-api.ts
import { AllApiCommands, ClientFn, GetUser, CreateUser } from './api.ts';

// Optional helper to reduce boilerplate
const createClientApi = <Cmd extends AllApiCommands>(endpoint: Cmd['endpoint']) => {
  return (http: HttpClient, arg: Cmd['arg']) => 
    http.get<Cmd['resp']>(endpoint, arg) as Promise<Cmd['resp']>;
};

// Strongly-typed client functions
const getUser: ClientFn<GetUser> = createClientApi('/users');
const createUser: ClientFn<CreateUser> = createClientApi('/users/create');

// Usage: TypeScript enforces correct args and infers return type
const user = await getUser(http, { id: '1' }); // user is typed as { name: string }
const newUser = await createUser(http, { name: 'Alice' }); // newUser is typed as { id: string; name: string }

Step 3: Enforced Server Endpoint Registration

The ServerHandlerRegistry type will throw a TypeScript error if you miss any endpoint or mismatch types, and we can batch-register handlers to avoid manual work:

// ./server-api.ts
import { AllApiCommands, ServerHandlerRegistry } from './api.ts';

// TypeScript forces you to implement ALL endpoints correctly
const apiHandlers: ServerHandlerRegistry = {
  '/users': async (db, arg) => {
    // arg is automatically typed as { id: string }
    const user = await db.findById(arg.id);
    return { name: user.name }; // Must return { name: string }
  },
  '/users/create': async (db, arg) => {
    // arg is automatically typed as { name: string }
    const newUser = await db.create(arg);
    return { id: newUser.id, name: newUser.name }; // Must return { id: string; name: string }
  }
};

// Batch-register all endpoints in one go
Object.entries(apiHandlers).forEach(([endpoint, handler]) => {
  httpServer.on(endpoint, async (params) => handler(db, params));
});

Key Benefits:

  • No redundant type definitions—all types flow from a single contract
  • TypeScript enforces matching request/response types across client and server
  • Impossible to forget endpoint registration (TypeScript will flag missing handlers)

2. Code Generation Tools for Larger APIs

If you're working with a large number of endpoints, manual contract maintenance can still be tedious. Here are tools that automate this workflow:

tRPC

tRPC is the gold standard for type-safe full-stack TypeScript APIs. It lets you define your API routes on the server, and the client gets auto-generated strongly-typed functions with zero manual contract work.

Server Example:

import { initTRPC } from '@trpc/server';
import { z } from 'zod'; // For runtime validation (optional but recommended)

const t = initTRPC.create();

export const appRouter = t.router({
  getUser: t.procedure
    .input(z.object({ id: z.string() })) // Runtime + type validation
    .query(async ({ input }) => {
      return db.findById(input.id);
    }),
  createUser: t.procedure
    .input(z.object({ name: z.string() }))
    .mutation(async ({ input }) => {
      return db.create(input);
    })
});

// Export type definition for client
export type AppRouter = typeof appRouter;

Client Example:

import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from './server';

const trpc = createTRPCProxyClient<AppRouter>({
  links: [httpBatchLink({ url: '/api' })],
});

// Fully type-safe calls with auto-completion
const user = await trpc.getUser.query({ id: '1' });
const newUser = await trpc.createUser.mutate({ name: 'Bob' });

Zod + Custom Code Generation

If you prefer a more flexible setup, use Zod to define request/response schemas, then use tools like zod-to-ts or custom scripts to generate client/server type definitions and function templates. Zod provides both runtime validation and TypeScript types, so you get type safety and runtime error checking.

OpenAPI + openapi-typescript

If your API needs to follow OpenAPI standards, use openapi-typescript to generate TypeScript types from your OpenAPI spec. You can then build client/server implementations around these auto-generated types to ensure full contract compliance.


内容的提问来源于stack exchange,提问作者Alex Craft

火山引擎 最新活动