FAQ
Frequently asked questions about itty-spec.
General Questions
What is itty-spec?
itty-spec is a contract-first, type-safe API framework built on top of itty-router. It provides automatic validation, type inference, and OpenAPI generation.
Why use itty-spec?
- Type Safety: End-to-end TypeScript inference from contract to handler
- Validation: Automatic request validation before handlers run
- Documentation: Generate OpenAPI specs from your contracts
- Lightweight: Minimal bundle size, perfect for edge/serverless
How does itty-spec differ from itty-router?
itty-router is a minimal router. itty-spec adds:
- Contract-based routing
- Automatic validation
- Type inference
- OpenAPI generation
Is itty-spec production-ready?
Yes! itty-spec is used in production and actively maintained.
Contract Questions
Do I need to define contracts for all endpoints?
Yes, contracts are required. They serve as the single source of truth for your API.
Can I use contracts without handlers?
No, every operation in your contract needs a corresponding handler.
Can I have multiple contracts?
Yes, you can split contracts by domain and combine them:
const contract = {
...usersContract,
...productsContract,
};How do I handle optional fields?
Use .optional() or .default() in your schemas:
query: z.object({
page: z.number().optional(),
limit: z.number().default(10),
})Type Safety Questions
Why are my path parameters typed as EmptyObject?
Use as const when creating contracts:
const contract = createContract({
getUser: {
path: "/users/:id",
// ...
},
} as const);How do I access typed request data?
Use the validated properties:
const { id } = request.validatedParams;
const { page } = request.validatedQuery;
const body = request.validatedBody;
const auth = request.validatedHeaders.get("authorization");Can I extend the request type?
Yes, use TypeScript generics:
interface AuthenticatedRequest extends IRequest {
user: User;
}
const router = createRouter<typeof contract, AuthenticatedRequest>({
// ...
});Validation Questions
What happens if validation fails?
Validation errors return a 400 Bad Request with details:
{
"error": "Validation failed",
"details": [...]
}Can I customize validation errors?
Yes, use custom error handling:
const router = Router({
catch: (error, request) => {
// Custom error handling
},
});How do I validate complex data?
Use Zod refinements or Valibot pipes:
const schema = z.object({
password: z.string().min(8),
confirmPassword: z.string(),
}).refine(
(data) => data.password === data.confirmPassword,
{ message: "Passwords don't match" }
);Response Questions
How do I return different status codes?
Define them in your contract and use request.respond():
responses: {
200: { "application/json": { body: SuccessSchema } },
404: { "application/json": { body: ErrorSchema } },
}
return request.respond({
status: 404,
contentType: "application/json",
body: { error: "Not found" },
});Can I return HTML responses?
Yes, define HTML content type in your contract:
responses: {
200: {
"text/html": { body: z.string() },
},
}
return request.respond({
status: 200,
contentType: "text/html",
body: "<html>...</html>",
});How do I set response headers?
Define headers in your contract and include them in the response:
responses: {
201: {
"application/json": {
body: UserSchema,
headers: z.object({
location: z.string().url(),
}),
},
},
}
return request.respond({
status: 201,
contentType: "application/json",
body: user,
headers: {
location: `/users/${user.id}`,
},
});Middleware Questions
How do I add authentication?
Use before middleware:
const router = createRouter({
contract,
handlers,
before: [
async (request) => {
const auth = request.headers.get("authorization");
if (!auth) {
throw new Error("Unauthorized");
}
// Attach user to request
},
],
});Can I use Express middleware?
No, itty-spec uses Fetch API middleware. You'll need to adapt Express middleware or write new middleware.
How do I handle CORS?
Use finally middleware:
const router = createRouter({
contract,
handlers,
finally: [
async (response, request) => {
response.headers.set("access-control-allow-origin", "*");
return response;
},
],
});OpenAPI Questions
How do I generate OpenAPI specs?
Use createOpenApiSpecification:
import { createOpenApiSpecification } from "itty-spec/openapi";
const spec = await createOpenApiSpecification(contract, {
title: "My API",
version: "1.0.0",
});Can I customize the OpenAPI spec?
The spec is generated from your contract. Customize it by:
- Adding metadata to operations
- Using schema descriptions
- Organizing with tags
How do I serve the OpenAPI spec?
Add it as a route:
const router = createRouter({
contract: {
...contract,
getOpenApiSpec: {
path: "/openapi.json",
method: "GET",
responses: {
200: { "application/json": { body: z.any() } },
},
},
},
handlers: {
...handlers,
getOpenApiSpec: async (request) => {
return request.respond({
status: 200,
contentType: "application/json",
body: openApiSpec,
});
},
},
});Schema Library Questions
Which schema library should I use?
- Zod: Best TypeScript inference, mature ecosystem
- Valibot: Smaller bundle size, similar features
Can I use both Zod and Valibot?
Technically yes, but it's not recommended. Stick to one library for consistency.
How do I migrate between libraries?
Since both implement Standard Schema V1, you can migrate by updating schema definitions. See the Schema Libraries guide.
Performance Questions
Is itty-spec fast?
Yes! itty-spec is designed for performance:
- Minimal overhead
- Efficient validation
- Small bundle size
How do I optimize bundle size?
- Use Valibot instead of Zod
- Tree-shake unused code
- Avoid heavy dependencies
Can I cache validation results?
Validation happens per request. If you need caching, implement it in your handlers.
Deployment Questions
Can I use itty-spec with Cloudflare Workers?
Yes! itty-spec works perfectly with Cloudflare Workers:
export default {
fetch: router.fetch,
};Can I use itty-spec with AWS Lambda?
Yes, with a Fetch adapter:
import { createServerAdapter } from "@whatwg-node/server";
const adapter = createServerAdapter(router.fetch);
export const handler = adapter;Can I use itty-spec with Node.js?
Yes, use @whatwg-node/server:
import { createServerAdapter } from "@whatwg-node/server";
import { createServer } from "http";
const adapter = createServerAdapter(router.fetch);
const server = createServer(adapter);
server.listen(3000);Related Topics
- Getting Started - Learn the basics
- Troubleshooting - Common issues
- Examples - Working examples