Skip to content

[Feature]: Native Rust Middlewares for Kito #20

Description

@nehu3n

Summary

Add support for native Rust middlewares that can be loaded dynamically at startup and executed directly inside Kito’s core, before any TypeScript middleware.
The idea is to allow anyone to write high-performance middleware as independent Rust crates. When compiled, these crates automatically generate a small npm package that acts as a descriptor used by the JS runtime to register the middleware into the Rust core.

Motivation

Right now, all middleware must be implemented in TypeScript, which:

  • limits performance in the hottest part of the pipeline,
  • makes it harder to implement heavy or low-level logic efficiently,
  • prevents community developers from creating proper native extensions,
  • and forces the core to keep growing instead of letting features live as external plugins.

With this feature, Kito gains a native plugin architecture, enabling:

  • pure Rust execution for critical middleware,
  • dynamic loading of third-party native extensions,
  • clean integration through npm,
  • real extensibility without recompiling the core,
  • and maximal performance with Tokio + Hyper + Kito’s runtime

Proposed Solution

1. Third-party crates

Any developer will be able to create a Rust crate that implements the Middleware trait from kitojs.

A macro like #[export] will:

  • generate the required extern "C" functions to expose the middleware,
  • handle JSON serialization/deserialization for the middleware options,
  • and produce an npm descriptor package at build time through a build script.

2. npm descriptor package

The build script will generate a minimal npm package containing:

  • a package.json with metadata and "kito.native": true,
  • the path to the generated .so/.dll/.dylib,
  • a small index.js file exporting a simple descriptor object.

This descriptor is what the TS runtime of Kito consumes.

3. Registration at startup

Whenever someone does:

app.use(cors({ origin: "*" }));

Kito’s JS runtime detects that it’s a native middleware and calls a N-API function in the core:

register_native_middleware(descriptor)

The call provides:

  • the route (or global usage),
  • middleware options as JSON,
  • the path to the cdylib,
  • the middleware’s name and version.

4. Dynamic loading

The Rust core uses libloading to:

  • load the cdylib dynamically,
  • fetch the exported kito_new_middleware function,
  • instantiate the middleware,
  • store it inside a global structure like:
DashMap<Route, Vec<MiddlewareEntry>>

Each MiddlewareEntry contains:

  • the instantiated middleware,
  • the function pointer to handle,
  • metadata,
  • and information about whether it’s global or route-specific.

5. Pipeline execution

For each request:

  1. The core gathers global + route-specific middlewares.
  2. Native middlewares run first.
  3. If none of them terminate the request, JS middlewares run next.

4. Finally, the JS route handler runs.

Native middlewares receive the usual Context, can modify it, call next, or finish the response early.

Checklist

  • I've searched for related issues
  • I've read the documentation

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions