Start here

@hulla/api

Type-check HTTP requests, handlers, and clients against one shared contract, across the hosts and transports your app uses.

On this page

@hulla/api keeps methods, paths, inputs, and response types in one shared contract. Your server implements that contract; your client calls it through the transport you choose.

Edit any file. TypeScript checks all three together.

contract.tsEditable
import { defineContract, route, response } from '@hulla/api'

export const contract = defineContract({
  routes: {
    profile: route.get('/profile', {
      responses: {
        200: response.json<{ name: string }>(),
      },
    }),
  },
})

TypeScript

Ready

Rename name in the response type, or use the shortcut. The handler and caller will need to follow.

    Real TypeScript and @hulla/api declarations. Code is type-checked locally; no API request or AI agent runs.

    src/api/projects.ts
    import { defineContract, request, response, route } from '@hulla/api'
    import { createClient } from '@hulla/api/client'
    import { inProcessTransport } from '@hulla/api/in-process'
    import { defineServer } from '@hulla/api/server'
    
    const contract = defineContract({
      routes: {
        createProject: route.post('/projects', {
          body: request.json<{ name: string }>(),
          responses: { 201: response.json<{ id: string; name: string }>() },
        }),
      },
    })
    
    const implementation = defineServer(contract).implement({
      createProject: ({ body, response }) =>
        response(201, { id: crypto.randomUUID(), name: body.name }),
    })
    
    const api = createClient(contract, {
      transport: inProcessTransport(implementation),
    })
    
    const result = await api.createProject({ body: { name: 'Documentation' } })
    ConcernHow it works
    HTTP behaviorMethods, paths, headers, and statuses stay explicit. A declared 404 is a typed result the client can handle.
    Framework choiceNative adapters connect the implementation to your host. The host keeps routing, middleware, and deployment.
    Runtime validationAdd a Standard Schema validator where needed. Type-only declarations skip application-shape validation.
    Contract driftMissing handlers, undeclared statuses, and incorrect response types become TypeScript errors.

    Use codecs for values such as Date that need different in-memory and wire representations.

    These tools overlap, but they start from different sources of truth:

    ToolThe source of truthIt feels most natural when
    @hulla/apiA standalone HTTP contractHTTP details matter and the same contract must cross different hosts or transports
    tRPCA server procedure routerOne TypeScript application wants inference directly from its server implementation
    oRPCProcedures or a separate contractYou want its RPC/OpenAPI builders and broad procedure ecosystem
    ts-restA standalone REST contractYou want a contract-first REST client with its supported server integrations
    Hono RPCA typed Hono route chainThe server already belongs to Hono and clients should infer from that app

    Choose @hulla/api when the boundary should outlive any one framework and when transport choice should remain explicit. If the API is only an internal function call, use an ordinary function. If a framework-owned typed client already matches every consumer, adding a separate contract may not buy you enough.

    The core package includes contracts, clients, servers, Fetch, and in-process calls. Add integrations only for the boundary you need.