원클릭으로
typedapi
// Help AI agents understand and use the typedapi.ts framework for building type-safe HTTP APIs. Use this when creating or modifying typedapi.ts routes, middleware, dependency injection, CORS, response helpers, or OpenAPI generation.
// Help AI agents understand and use the typedapi.ts framework for building type-safe HTTP APIs. Use this when creating or modifying typedapi.ts routes, middleware, dependency injection, CORS, response helpers, or OpenAPI generation.
| name | typedapi |
| description | Help AI agents understand and use the typedapi.ts framework for building type-safe HTTP APIs. Use this when creating or modifying typedapi.ts routes, middleware, dependency injection, CORS, response helpers, or OpenAPI generation. |
| license | MIT |
Use this skill when working in a project that uses typedapi.ts.
The framework is a type-safe HTTP API layer built on the standard Request / Response / fetch interfaces. The main pattern is:
api().routes() and createRouter().Path<T> and Json<T>.Response.Install the framework:
npm install typedapi.ts
typia and ts-patch are required peer dependencies and are installed automatically alongside typedapi.ts (npm 7+). Use tspc (provided by ts-patch) instead of tsc in your build scripts. tspc is a drop-in tsc replacement that applies custom transformers without patching your TypeScript installation.
Use this tsconfig.json plugin order when you want compile-time metadata extraction and Typia validation:
{
"compilerOptions": {
"plugins": [
{ "transform": "typedapi.ts/transform" },
{ "transform": "typia/lib/transform" }
]
}
}
Rules:
typedapi.ts/transform must come before typia/lib/transform.tspc -p tsconfig.json (from ts-patch) instead of tsc in build scripts. tspc is a drop-in replacement with no global side effects.typia and ts-patch are required peer dependencies.import { api, createRouter } from "typedapi.ts";
const health = api({ method: "GET", path: "/health" }, async () => {
return { status: "ok" };
});
export default createRouter([health]);
createRouter<T>() returns a (request: Request, context?: T) => Promise<Response> handler, so it fits Workers-style and Fetch-based runtimes directly. Handlers, middleware, and inject functions receive { request, context } as a second argument. Use the second argument of createRouter for router-wide behavior such as { middlewares, onError }.
Model handler params with wrapper types instead of manually parsing the request:
Path<T, Meta>Query<T, Meta>Header<T, Meta>Cookie<T, Meta>Json<T, Meta>Form<T, Meta>Parameter merge precedence is:
path > body > query > cookie > header
Access the raw Request and custom context via the handler's second argument { request, context }.
import { api, Path, Query, Json } from "typedapi.ts";
const updateUser = api(
{ method: "PUT", path: "/users/:id" },
async (params: {
id: Path<number>;
notify?: Query<boolean>;
name: Json<string>;
}) => {
return {
id: params.id,
notify: params.notify,
name: params.name,
};
},
);
Write runtime validators explicitly in the api() call. The transformer does not generate validate.
import typia from "typia";
import {
api,
inject,
type Inject,
type Json,
type Path,
type RequestParams,
} from "typedapi.ts";
const db = inject(async () => connectDb());
type CreateUserParams = {
id: Path<number>;
body: Json<{ name: string }>;
db: Inject<typeof db>;
};
const createUser = api(
{ method: "POST", path: "/users/:id" },
async (params: CreateUserParams) => {
return { id: params.id, name: params.body.name };
},
{
validate: typia.createValidate<RequestParams<CreateUserParams>>(),
},
);
Rules:
typia in every file that passes validate to api().RequestParams<T> whenever handler params include Inject<typeof dependency>.api() validation runs on request-sourced params only.Prefer returning plain values unless you need explicit status, headers, or content type. typedapi.ts auto-converts handler results as follows:
Response -> passthroughnull -> 204 No Contentstring -> text/plain; charset=utf-8URL -> 307 redirectReadableStream -> application/octet-streamAsyncIterable -> text/event-streamAvailable response helpers:
json()html()text()stream()sse()redirect()file()cookie() / clearCookie() for Set-Cookie valuesimport { api, json, text, redirect } from "typedapi.ts";
const createUser = api(
{ method: "POST", path: "/users" },
async () => json({ id: 1 }, 201, { location: "/users/1" }),
);
const health = api({ method: "GET", path: "/health" }, async () => text("ok"));
const docs = api(
{ method: "GET", path: "/docs" },
async () => redirect("https://example.com/docs"),
);
Middleware uses the onion model. The signature is:
(next) => (params) => Response | Promise<Response>
Use middleware() when you want typed reusable middleware that can also contribute OpenAPI metadata.
Use createRouter(..., { middlewares, onError }) for router-wide behavior, and routes() to add a common prefix, middleware stack, group-level tags, or group-level onError handler. Group middleware runs before route-level middleware. Nested routes() calls stack prefixes and middleware, and merge group tags ahead of route tags with deduplication.
import { api, createRouter, Header, middleware, routes } from "typedapi.ts";
const auth = middleware((next) =>
async (params: { authorization: Header<string> }) => {
if (!params.authorization?.startsWith("Bearer ")) {
return new Response("Unauthorized", { status: 401 });
}
return next();
},
);
const getUsers = api({ method: "GET", path: "/users" }, async () => [{ id: 1 }]);
export default createRouter(
routes({ prefix: "/api", middlewares: [auth] }, getUsers),
);
Use cors() as router middleware via createRouter(..., { middlewares: [cors()] }). It handles normal responses and preflight requests while letting createRouter() keep control of route matching and automatic OPTIONS responses.
import { api, cors, createRouter } from "typedapi.ts";
const health = api(
{ method: "GET", path: "/health" },
async () => ({ status: "ok" }),
);
export default createRouter([health], {
middlewares: [cors()],
});
Throw HttpError(status, body?, headers?) for controlled HTTP failures.
body becomes { message }body is returned as JSONbody returns an empty response bodyUnhandled non-HttpError exceptions are rethrown to the caller; the framework does not convert them into 500.
Use createRouter(..., { onError }) for app-wide fallback handling and routes({ onError }) for group-specific strategies. Both receive (error, request). Use handleError() when you want the built-in HttpError fallback while letting unknown errors bubble up. If an onError callback throws, that thrown value is also passed through handleError().
Use inject() for request-scoped dependencies.
Patterns:
async function* () { yield resource; cleanup(); } for resources with cleanupasync () => resource for simple async valuesInject into handlers with Inject<typeof dependency>.
import { api, inject, type Inject, type Path } from "typedapi.ts";
const db = inject(async function* () {
const client = await connectDb();
yield client;
await client.close();
});
const getUser = api(
{ method: "GET", path: "/users/:id" },
async (params: {
id: Path<number>;
db: Inject<typeof db>;
}) => {
return params.db.query("SELECT * FROM users WHERE id = $1", [params.id]);
},
);
Access custom context via the second argument:
import { inject, type HandlerContext } from "typedapi.ts";
const db = inject(async (_params, { context }: HandlerContext<Env>) => {
return context.DB;
});
Notes:
cache: true is the default, so the same injectable instance is reused within one request.validate, use RequestParams<T> so injected fields are excluded from the validator type.validate -> inject -> handler; failed validation returns 400 before injectables are resolved.inject() handlers can also use Path, Query, Header, Cookie, Json, and JsonResponse so they can contribute request and response metadata.Use openapi() to generate an OpenAPI 3.1 document from exposed routes:
import { api, openapi, type Json, type JsonResponse } from "typedapi.ts";
const createOrder = api(
{ method: "POST", path: "/orders", expose: true },
async (_params: { customer: Json<string> }): Promise<
JsonResponse<201, {}, { id: number; customer: string }>
> => {
return { id: 1, customer: "Acme Corp" };
},
);
const document = openapi({
info: { title: "Orders API", version: "1.0.0" },
routes: [createOrder],
});
Rules:
expose: true are includedtags, summary, description, operationId, deprecated, and externalDocsroutes({ tags }) prepends shared tags to child routes and removes duplicatesJsonResponsemiddleware < inject < routeparameters or responses are already supplied manually, the transformer does not overwrite themWhen modifying a typedapi codebase:
URL, headers, or body parsing.routes() for shared prefix, middleware, or group-level error handling instead of duplicating config across endpoints.expose: true only for routes that belong in the OpenAPI document.Read references/api-reference.md when you need exact examples for: