Skip to main content

Overview

@contract-kit/core is the foundation of Contract Kit. It provides the tools to define type-safe HTTP contracts that can be used across your entire stack.
Prefer the contract-kit meta package for new projects. It re-exports @contract-kit/core along with client, ports, application, and more.

Installation

npm install @contract-kit/core zod

Key Exports

createContractGroup

Creates a group of related contracts that share configuration:
import { createContractGroup } from "@contract-kit/core";

const todos = createContractGroup()
  .namespace("todos")
  .meta({ auth: "required" });

Contract Builders

Build contracts using a fluent API:
export const getTodo = todos
  .get("/api/todos/:id")
  .path(z.object({ id: z.string() }))
  .response(200, TodoSchema);

API Reference

Contract Group Methods

.namespace(name: string)

Set a namespace for OpenAPI tags:
const todos = createContractGroup().namespace("todos");

.meta(metadata: Record<string, unknown>)

Attach shared metadata for all contracts in the group:
const protectedRoutes = createContractGroup().meta({ auth: "required" });

.errors(schemas: Record<number, Schema>)

Define shared error responses:
const api = createContractGroup()
  .errors({
    401: z.object({ message: z.string() }),
    500: z.object({ message: z.string() }),
  });

HTTP Method Builders

.get(path: string)

Define a GET endpoint:
const getTodo = todos.get("/api/todos/:id");

.post(path: string)

Define a POST endpoint:
const createTodo = todos.post("/api/todos");

.put(path: string)

Define a PUT endpoint:
const updateTodo = todos.put("/api/todos/:id");

.patch(path: string)

Define a PATCH endpoint:
const patchTodo = todos.patch("/api/todos/:id");

.delete(path: string)

Define a DELETE endpoint:
const deleteTodo = todos.delete("/api/todos/:id");

Schema Methods

.path(schema: Schema)

Define path parameters:
.path(z.object({ id: z.string() }))

.query(schema: Schema)

Define query parameters:
.query(z.object({ 
  limit: z.number().optional(),
  offset: z.number().optional(),
}))

.body(schema: Schema)

Define request body:
.body(z.object({
  title: z.string(),
  completed: z.boolean(),
}))

.response(status: number, schema: Schema)

Define response for a status code:
.response(200, TodoSchema)
.response(201, TodoSchema)

.errors(schemas: Record<number, Schema>)

Define error response:
.errors({ 404: z.object({ message: z.string() }) })

Metadata Methods

.meta(metadata: Record<string, unknown>)

Attach custom metadata (auth, rate limit, idempotency, etc.):
.meta({
  auth: "required",
  rateLimit: { max: 10, windowSec: 60 },
  idempotency: { enabled: true },
})

Examples

Simple GET Endpoint

import { createContractGroup } from "@contract-kit/core";
import { z } from "zod";

const todos = createContractGroup();

export const getTodo = todos
  .get("/api/todos/:id")
  .path(z.object({ id: z.string() }))
  .response(200, z.object({
    id: z.string(),
    title: z.string(),
    completed: z.boolean(),
  }));

POST with Validation

export const createTodo = todos
  .post("/api/todos")
  .body(z.object({
    title: z.string().min(1).max(100),
    completed: z.boolean().optional(),
  }))
  .response(201, z.object({
    id: z.string(),
    title: z.string(),
    completed: z.boolean(),
  }))
  .errors({
    400: z.object({
      message: z.string(),
      errors: z.array(z.string()),
    }),
  });

List with Pagination

export const listTodos = todos
  .get("/api/todos")
  .query(z.object({
    limit: z.number().int().min(1).max(100).optional(),
    offset: z.number().int().min(0).optional(),
    completed: z.boolean().optional(),
  }))
  .response(200, z.object({
    todos: z.array(TodoSchema),
    total: z.number(),
    limit: z.number(),
    offset: z.number(),
  }));

Protected Endpoint

const protectedTodos = createContractGroup()
  .namespace("todos")
  .meta({ auth: "required" })
  .errors({ 401: z.object({ message: z.string() }) });

export const getMyTodos = protectedTodos
  .get("/api/todos/me")
  .response(200, z.object({
    todos: z.array(TodoSchema),
  }));

TypeScript Support

The core package provides full TypeScript inference:
import type { InferOutput } from "@contract-kit/core";

// Type is inferred from the contract
type GetTodoResponse = InferOutput<(typeof getTodo)["schema"]["responses"][200]>;
// { id: string, title: string, completed: boolean }

// Path params are typed
type PathParams = InferOutput<(typeof getTodo)["schema"]["path"]>;
// { id: string }

Best Practices

Include all validation rules in your schemas for automatic validation.
Make paths self-documenting with clear resource names and RESTful conventions.
Add JSDoc comments to contracts for better IDE support and OpenAPI generation.

Next Steps