Define an endpoint as follows:
export const getUser = app.createEndpoint(
"GET /users/:id",
{
params: z.object({ id: z.string() }),
response: { 200: z.custom<User>() },
},
async ({ params }) => {
const user = await db.users.findOne(params.id);
return {
statusCode: 200,
body: user,
};
}
);
Serve it:
const serve = createServer({port: 3000, hostname: "localhost"});
serve(getUser, ...otherEndpoints);
Use it through the auto-generated client:
const user = await getUser({ params: { id: "some-id" } }).ok(200);
- it is a stand-alone web framework with a custom middleware engine, which means you don't have to integrate it with another framework like expressjs
- it is simple and modular, giving you freedom in how you organize your code
- it is 100% typesafe all the way down to the middleware engine
Kinekt uses middlewares which are combined into pipelines to handle requests. An incoming request will be transformed into a context object and passed through a given pipeline.
const pipeline = createPipeline(
cors(),
authenticate(),
checkAcceptHeader(),
deserialize(),
basicEndpoint(params),
serialize(),
finalize()
)
const result = await pipeline(context)
Kinekt uses a route tree, compiled at startup, to efficiently dispatch incoming requests to the correct pipelines.
Kinekt aims to be 100% Type Safe.
For example, adding an authenticate()
middleware to your pipeline will make a user
property available on the pipeline context which you can then consume in a request handler:
const pipeline = createPipeline(
...,
authenticate(),
...
)
You now have access to a context.user
property:
export const getUser = app.createEndpoint(
// ...
async ({ context }) => {
const currentUser = context.user; // <-- the compiler gives an error if the
// authenticate middleware is not
// present in the pipeline.
// ...
}
);
When creating validated endpoints, you can accurately declare each aspect of your contract and the compiler will take care of warning you about any violations:
export const createUser = testPipeline.createEndpoint(
"POST /organization/:organizationId/users",
// ^---- by using a param segment, you are forced to
// use a `params` schema containing
// `organizationId` (see below).
// ^---- by using POST method here, you are forced to declare a body schema
// (see below).
{
params: z.object({ organizationId: z.string() }), // <- params schema
query: z.object({ private: zodBooleanFromString() }),
body: z.object({ email: z.string() }), // <- body schema
response: {
// You must explicitly declare which bodies are returned for which status
// codes.
200: z.custom<User>(),
409: z.custom<{ message: string }>(),
},
},
async ({ params, query, body, context }) => {
// You must return bodies and status codes as declared in the response
// schemas.
if (body.email === "[email protected]") {
return {
statusCode: 409,
body: { message: "User with this email already exists" },
};
}
return {
statusCode: 200,
body: {
id: "some-id",
email: body.email,
organizationId: params.organizationId,
private: query.private,
},
};
}
);