Hosting

Express and Fastify

Run Aventara on Nest's Express or Fastify platform - what is the same, what differs, middleware exclusion on Fastify, global prefix and CORS.

Read as Markdown

@aventara/nest supports both of Nest's HTTP platforms. The protocol behaves the same on both, because its routes are registered on the platform itself and all of its rules live in the protocol. What differs is how Nest's own machinery (middleware, routing) touches those routes.

Choose a platform

Express is Nest's default and what aventara new creates. For Fastify, install the adapter and pass it when creating the app:

Code
npm install @nestjs/platform-fastify fastify @fastify/cors
Code
// src/main.ts
import { NestFactory } from "@nestjs/core";
import { FastifyAdapter, type NestFastifyApplication } from "@nestjs/platform-fastify";
import { AppModule } from "./app.module.js";

const app = await NestFactory.create<NestFastifyApplication>(AppModule, new FastifyAdapter());
await app.listen(process.env.PORT ?? 3000, "127.0.0.1");

Nothing in AventaraModule or your configuration changes. Any other platform fails at startup, before listening.

What is the same

Both platforms log the same startup line, serve GET /api/_contract and POST /api/_resources/..., and produce the same envelopes and statuses. Nest guards, interceptors and pipes never run on Aventara routes on either; authentication belongs in Aventara pipelines.

What differs

ExpressFastify
app.use(...) middleware, helmetruns on Aventara routesruns
MiddlewareConsumer middlewarenever runs on Aventara routesruns on them unless excluded
Your own controller at <api>/_contract (any _ path)never reachedthe application does not start
Resource keys or api paths needing * or : + # $ & , / ; = ? @ in a route pathacceptedrefused at startup, naming it
Routing casecase-insensitive (/API/_contract answers)case-sensitive
X-Powered-By response headersent (app.disable("x-powered-by") removes it)not sent
CORS preflight methodsGET,HEAD,PUT,PATCH,POST,DELETEGET,HEAD,POST (all the protocol needs)

On Express, a protocol route requested with a percent-encoding a generated client never sends (for example %6Eotes for notes) answers 500 A3000; Fastify and the in-process protocol execute it. Generated clients are unaffected.

Middleware on Fastify

On Fastify, Nest middleware registered through MiddlewareConsumer runs before routing, so it also runs on Aventara routes. A logger registered with forRoutes("*path") would see every protocol request. Keep it off them with .exclude(), using your api path plus /_*path:

Code
// src/app.module.ts
import { Module, type MiddlewareConsumer, type NestModule } from "@nestjs/common";
import { LoggerMiddleware } from "./logger.middleware.js";

@Module({ /* imports ... */ })
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .exclude("api/_*path")      // "<api without the slash>/_*path"
      .forRoutes("*path");
  }
}

With the exclusion, a request to /api/_contract does not reach LoggerMiddleware; a request to / does. Without it, both do. On Express the same module code is harmless: the middleware never ran on Aventara routes there.

Middleware you want on Aventara routes on both platforms goes through app.use(...) instead, which runs on both.

Global prefix

app.setGlobalPrefix("v1") moves your controllers and leaves the protocol where it is. With the prefix set and api: "/api":

Code
GET /api/_contract      200
GET /v1/api/_contract   404
GET /v1                 200   (your own controller's root)

Put the prefix in api instead (api: "/v1/api") and generate the client against that URL.

CORS

A browser frontend on another origin needs CORS. app.enableCors() is a platform-level feature that runs on Aventara routes on both platforms. A project made by aventara new already has it in src/main.ts, driven by CORS_ORIGINS in .env (a comma-separated list, default http://localhost:5173,http://localhost:3001; unset or empty means CORS is off):

Code
const corsOrigins = (process.env.CORS_ORIGINS ?? '')
  .split(',')
  .map((origin) => origin.trim())
  .filter(Boolean);
app.enableCors({ origin: corsOrigins.length > 0 ? corsOrigins : false });

Without the scaffold, a single origin is one line:

Code
app.enableCors({ origin: "http://localhost:5173" });

The generated client sends Content-Type: application/json, Aventara-Protocol-Version and Aventara-Contract-Hash (plus Aventara-Request-Id if you set requestId, and anything your custom fetch adds, such as Authorization). Those make every call a non-simple request, so the browser preflights it; Nest's default reflects the requested headers back, which is what you need. Verified on both platforms:

Code
curl -si -X OPTIONS http://localhost:3103/api/_resources/Post/find/many \
  -H "Origin: http://localhost:5173" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: content-type,aventara-protocol-version,authorization"
Code
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: http://localhost:5173
Access-Control-Allow-Methods: GET,HEAD,POST
Access-Control-Allow-Headers: content-type,aventara-protocol-version,authorization

(Fastify's wording; Express answers the same with its longer method list.) Restrict origin to your frontends. If you send cookies, add credentials: true and use a specific origin. CORS only tells browsers what they may read: it is not access control, and servers and bots ignore it (Limits and safety).

Request body handling

Protocol routes read bodies raw on both platforms, never through Nest's body parser, up to limits.maxRequestBytes. Your own routes keep Nest's parsed req.body. See NestJS host.

See also