ts-typed-api
TypeScript icon, indicating that this package has built-in type declarations

0.1.2 • Public • Published

ts-typed-api 🚀

A lightweight, type-safe API library for TypeScript with Zod validation.

Motivation

After building several full-stack applications, I discovered that Large Language Models (LLMs) face significant challenges when implementing features that span both backend and frontend components, particularly around API interfaces.

The core issues I observed:

  • API Contract Drift: LLMs struggle to maintain consistency when defining an API endpoint and then implementing its usage in the frontend
  • Context Loss: Without a clear, shared contract, LLMs lack the contextual assistance needed to ensure proper integration between client and server
  • Integration Errors: The disconnect between backend definitions and frontend consumption leads to runtime errors that could be prevented

The Solution: Leverage TypeScript's powerful type system to provide real-time feedback and compile-time validation for both LLMs and developers. By creating a shared contract that enforces consistency across the entire stack, we eliminate the guesswork and reduce integration issues.

🤖 Built for LLM-Assisted Development

This module is specifically designed to make coding with Large Language Models (LLMs) easier and more efficient. When working on bigger applications with extensive APIs, maintaining context becomes challenging for both developers and AI assistants. ts-typed-api solves this by:

🔑 Key Benefits for LLM Development

  • Centralized Type Definitions: Keep all API contracts in one place, making it easier for LLMs to understand your entire API surface
  • Automatic Type Synchronization: The type system ensures both client and server stay perfectly in sync, preventing the drift that commonly occurs in large codebases. Compile-time checks prevent the common client-server mismatches that occur in AI-assisted development
  • Context-Friendly Structure: Organized domain-based API definitions that LLMs can easily parse and understand
  • Compile-Time Validation: Catch integration issues before runtime, reducing the debugging cycles when working with AI-generated code
  • Self-Documenting: Type definitions serve as living documentation that LLMs can easily parse

📦 Installation

npm install --save ts-typed-api

How to use it?

  1. Define your API in a file that will be shared by both the server and the client
  2. Implement handlers in the server, leveraging the type system and request/response validation
  3. Implement the client based on the contract from #1 leveraging type system

Examples

Check out the examples/ directory:

  • simple/ - Basic usage with ping endpoints and middleware
  • advanced/ - Complex schemas with authentication, CRUD operations, and file uploads

🚀 Quick Start

1. Define API Routes with Domains

Create your API definitions organized by logical domains:

// definitions.ts
import { ZodSchema as z, CreateApiDefinition, CreateResponses } from 'ts-typed-api';

// you can create multiple definitions per app
export const PublicApiDefinition = CreateApiDefinition({
    prefix: '/api/v1/public',
    endpoints: {
        common: { // domain name
            ping: { // endpoint name
                method: 'GET',
                path: '/ping',
                // validate route parameters with Zod
                params: z.object({}),
                // validate query parameters with Zod
                query: z.object({}), 
                // validate body with Zod
                body: z.object({}),
                // validate query parameters with Zod
                responses: CreateResponses({
                    // specify response codes and response shapes
                    200: z.enum(["pong"]),
                    201: z.boolean()
                })
            },
        }
    }
});

2. Implement Server-Side Handlers

Register handlers with full type safety and middleware support:

// server.ts
import express from 'express';
import { RegisterHandlers, EndpointMiddleware } from 'ts-typed-api';
import { PublicApiDefinition } from './definitions';

const app = express();
app.use(express.json());

// Example middleware with endpoint information
const loggingMiddleware: EndpointMiddleware = (req, res, next, endpointInfo) => {
    console.log(`[${new Date().toISOString()}] ${req.method} ${req.path} - Endpoint: ${endpointInfo.domain}.${endpointInfo.routeKey}`);
    next();
};

// Register handlers with TypeScript enforcing all required handlers are present
RegisterHandlers(app, PublicApiDefinition, {
    common: {
        ping: async (req, res) => {
            console.log('Ping endpoint called');
            res.respond(200, "pong");
        }
    },
}, [loggingMiddleware]);

app.listen(3001, () => {
    console.log('Server running on http://localhost:3001');
});

3. Type-Safe Client Calls

Make API calls with full type safety and response handling:

// client.ts
import { ApiClient, FetchHttpClientAdapter } from 'ts-typed-api';
import { PublicApiDefinition } from './definitions';

async function runClientExample(): Promise<void> {
    const apiClient = new ApiClient('http://localhost:3001', PublicApiDefinition);

    // Type-safe API calls with response handlers
    await apiClient.callApi('common', 'ping', {}, {
        // you have to handle each possible status code defined in your contract
        200: (payload) => {
            console.log('Success:', payload); // payload is typed as "pong"
        },
        201: (payload) => {
            console.log('Success:', payload); // payload is typed as boolean
        },
        // you always need to handle 422 since it's returned on request validation
        422: (payload) => {
            console.log('Request validation error:', payload);
        }
    });
}

Now both server and client are type safe and in sync! The moment you change the definition of the API, type system will let you know about potential changes you need to handle (like additional response code or a change request body schema).

4. File Upload Example

Handle file uploads with type-safe validation:

// Define file upload endpoints
const FileUploadApiDefinition = CreateApiDefinition({
    prefix: '/api',
    endpoints: {
        files: {
            uploadSingle: {
                path: '/upload/single',
                method: 'POST',
                body: z.object({
                    description: z.string().optional(),
                }),
                fileUpload: {
                    single: {
                        fieldName: 'file',
                        maxSize: 5 * 1024 * 1024, // 5MB
                        allowedMimeTypes: ['image/jpeg', 'image/png', 'image/gif']
                    }
                },
                responses: CreateResponses({
                    200: z.object({
                        message: z.string(),
                        fileInfo: z.object({
                            originalName: z.string(),
                            size: z.number(),
                            mimetype: z.string()
                        })
                    })
                })
            }
        }
    }
});

// Implement handler
RegisterHandlers(app, FileUploadApiDefinition, {
    files: {
        uploadSingle: async (req, res) => {
            const file = req.file as UploadedFile | undefined;
            
            res.respond(200, {
                message: 'File uploaded successfully',
                fileInfo: {
                    originalName: file!.originalname,
                    size: file!.size,
                    mimetype: file!.mimetype
                }
            });
        }
    }
});

🌟 Features

Custom HTTP Client Adapters

Create custom HTTP client adapters by implementing the HttpClientAdapter interface:

interface HttpClientAdapter {
  request<T = any>(url: string, options: HttpRequestOptions): Promise<HttpResponse<T>>;
}

Middleware System

Add cross-cutting concerns like authentication, logging, and validation:

const customMiddleware: EndpointMiddleware = (req, res, next, endpointInfo) => {
    // Access to endpoint metadata
    console.log(`Domain: ${endpointInfo.domain}, Route: ${endpointInfo.routeKey}`);
    next();
};

Roadmap

  • OpenAPI generation with dynamic documentation based on Swagger

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

📄 License

Apache 2.0 License

Package Sidebar

Install

npm i ts-typed-api

Weekly Downloads

130

Version

0.1.2

License

ISC

Unpacked Size

172 kB

Total Files

30

Last publish

Collaborators

  • posinsk