Skip to content

HTTP adapters

HTTP adapters form the boundary between business code and the web server. Modules declare MIC routes; the selected adapter translates framework requests into business inputs, then turns MIC responses and errors into HTTP responses.

MIC provides two distinct contract families, each designed for its language:

  • Python — mic.http.HttpRouteSpec, served by FastAPI or Litestar;
  • Node.js / TypeScript — @mic-struct/mic-core HttpRouteSpec, served by Node HTTP, Express, Fastify, Hono, or NestJS.

The Python and TypeScript APIs are not interchangeable. They apply the same architectural rule: handlers depend on the MIC contract, never on a framework-specific Request or Response object.

Boundary guarantees

In either runtime, the adapter owns:

  • method, path, and route-parameter mapping;
  • access to parameters, headers, and cookies through a MIC context;
  • validation of declared inputs;
  • invocation of synchronous or asynchronous handlers;
  • serialization of responses and canonical errors;
  • configured cross-cutting behavior such as authentication and correlation.

Business code therefore remains testable without starting an HTTP server. Parsing, lifecycle, and framework integration details stay inside the adapter.

Python: FastAPI and Litestar

mic.http exposes HttpRouteSpec, HttpResponseSpec, and HttpRequestContext. The build_fastapi_router and build_litestar_router builders serve the same route tuple:

from fastapi import FastAPI
from mic.contracts import StrictSchema, wrap_http_success
from mic.fastapi import build_fastapi_router
from mic.http import HttpResponseSpec, HttpRouteSpec


class CreateItemPayload(StrictSchema):
    name: str


def create_item(*, payload: CreateItemPayload) -> HttpResponseSpec:
    return HttpResponseSpec(
        status_code=201,
        body=wrap_http_success({"name": payload.name}),
    )


routes = (
    HttpRouteSpec(
        method="POST",
        path="/items",
        name="items.create",
        category="items",
        handler=create_item,
    ),
)

app = FastAPI()
app.include_router(build_fastapi_router(routes=routes))

Moving to Litestar changes the builder and router mounting code; routes and handlers remain unchanged. Both adapters resolve the arguments required by the handler signature: context, validated payload, path parameters, and query parameters.

Dependencies remain optional: install mic-struct[fastapi] or mic-struct[litestar] for the selected server.

Node.js: five integrations

@mic-struct/mic-core exposes the route() helper and TypeScript HTTP contracts. The handler receives a normalized { params, query, body, context } object:

import { ok, route } from "@mic-struct/mic-core"
import { createNodeHttpServer } from "@mic-struct/mic-node-http"

const health = route({
  method: "GET",
  path: "/health",
  name: "system.health",
  category: "system",
  handler: ({ context }) => ok({ status: "ok", requestId: context.requestId }),
})

createNodeHttpServer({ routes: [health] }).listen(3000)

The same routes can be mounted with the adapter that matches the application:

  • Node HTTP — @mic-struct/mic-node-http: createNodeHttpServer or createNodeHttpHandler;
  • Express — @mic-struct/mic-express: createExpressMicApp or registerMicRoutes;
  • Fastify — @mic-struct/mic-fastify: createFastifyMicApp or registerMicRoutes;
  • Hono — @mic-struct/mic-hono: createHonoMicApp or registerMicRoutes;
  • NestJS — @mic-struct/mic-nest: MicNestModule.forRoot.

The create* builders create a complete application. The registerMicRoutes functions add routes to an existing Express, Fastify, or Hono application. The NestJS adapter creates controllers from MIC routes and relies on @nestjs/platform-express.

Parity and intentional differences

Parity is verified within each runtime:

  • in Python, a shared suite runs the same scenarios against FastAPI and Litestar;
  • in Node.js, each package verifies the shared contract, and tests also start real HTTP listeners for the framework integrations.

Parity does not make the frameworks identical. Path syntax, body parsing, middleware ordering, 404 responses, and application lifecycle remain the responsibility of the adapter or the host application.

Choosing an adapter

Start with the framework already standardized in the service. For a new service, decide from explicit constraints: required ecosystem, integration into an existing application, execution model, testing strategy, and operations. The MIC contract then makes it possible to change adapters within the same runtime without rewriting business code.

To build a first service, follow the Python quickstart or the Node.js / TypeScript quickstart.