feTS HTTP Framework
feTS (Fetch API + TypeScript) is a modern TypeScript HTTP framework designed for building performant and type-safe REST APIs. It prioritizes end-to-end type-safety, ease of setup, and a superior developer experience by leveraging the Web Fetch API standards. The framework is currently stable at version 0.8.6 and maintains an active development pace with frequent patch releases and minor version updates every few months, incorporating bug fixes, dependency upgrades, and new features. A key differentiator is its deep integration with TypeScript, coupled with `@sinclair/typebox` for defining robust schemas. This approach enables automatic runtime validation and OpenAPI specification generation, significantly reducing common API development pitfalls and ensuring consistent type adherence across the entire application stack, from client to server. It's built for Node.js environments and requires version 16.0.0 or higher.
Common errors
-
ReferenceError: require is not defined
cause Attempting to use CommonJS `require()` syntax in a feTS project, which is typically configured for ESM.fixChange `const { symbol } = require('package')` to `import { symbol } from 'package'` for all module imports. Ensure your `package.json` has `"type": "module"`. -
Argument of type '...' is not assignable to parameter of type '...' (TypeScript error)
cause Mismatch between the data structure provided to a feTS route handler (e.g., body, params) or returned from it, and the TypeBox schema defined for that route.fixCarefully review your TypeBox schemas and the corresponding TypeScript types in your route handlers. Ensure that the shapes of the data being sent, received, or returned precisely match the defined schemas. -
Request body is required but not provided (during OpenAPI validation or client request)
cause Sending a request to an endpoint with a TypeBox schema that defines the request body as `Type.Object(...)` without `Type.Optional(...)` wrapper, but the request does not include a body.fixIf the request body is genuinely optional, ensure your TypeBox schema for the request body is wrapped with `Type.Optional(Type.Object(...))` and you are on `fets@0.8.6` or higher. Otherwise, provide the required request body in your client request.
Warnings
- breaking Previously, the OpenAPI plugin hardcoded `requestBody.required = true` for all JSON and formData request bodies, even if they were defined as optional using TypeBox's `Type.Optional` modifier. This meant that clients making requests without a body to an endpoint expecting an optional body would still receive a validation error indicating a missing body.
- breaking Version 0.8.1 introduced more strict typing on request parameters. Existing code that previously passed type checks for request parameters (path, query, headers, body) might now yield TypeScript errors if the types are not precisely aligned with the defined TypeBox schemas.
- gotcha feTS is designed for modern Node.js environments (>=16.0.0) and largely expects an ESM (ECMAScript Modules) setup. Using CommonJS `require()` syntax for imports can lead to `ReferenceError: require is not defined` or similar module resolution issues.
Install
-
npm install fets -
yarn add fets -
pnpm add fets
Imports
- createServer
const { createServer } = require('fets')import { createServer } from 'fets' - createRouter
import createRouter from 'fets/router'
import { createRouter } from 'fets' - Type
import { Type } from '@sinclair/typebox'
Quickstart
import { createServer, createRouter, Response } from 'fets';
import { Type } from '@sinclair/typebox';
const userSchema = Type.Object({
id: Type.String(),
name: Type.String(),
});
const router = createRouter()
.get(
'/greet/:name',
{
parameters: Type.Object({
name: Type.String({ description: 'The name to greet' }),
}),
responses: {
200: Type.String(),
},
},
({ params }) => Response.json(`Hello, ${params.name}!`)
)
.post(
'/users',
{
body: userSchema,
responses: {
201: userSchema,
},
},
async ({ body }) => {
console.log('Received user:', body);
// In a real app, you'd save this to a database
return new Response(JSON.stringify({ ...body, id: 'new-id-' + Math.random().toString(36).substring(7) }), {
status: 201,
headers: { 'Content-Type': 'application/json' },
});
}
);
const server = createServer({
router,
});
server.listen(4000, () => {
console.log('feTS server running on http://localhost:4000');
console.log('Try: curl http://localhost:4000/greet/World');
console.log('Try: curl -X POST -H "Content-Type: application/json" -d \'{"name":"Alice"}\' http://localhost:4000/users');
});