HTTP apps

To deploy an Express or NestJS app, mark your app factory with http() and run laranja deploy. laranja bundles the app into one function behind a public HTTPS endpoint — a Lambda with a Function URL on AWS, an HTTP-triggered function on Azure — and every route you registered is served by it. No API Gateway to wire up, no YAML, no handler rewriting.

The same app code targets either provider; see what gets deployed for the exact resources on each.

Declaring your app (the http() marker)

The code-first way: mark your app with the http() marker and export it. laranja finds it by scanning your code — there's nothing to configure.

// src/app.ts
import express from "express";
import { http } from "@alzulejos/laranja-decorators";

const app = express();
app.use(express.json());

app.get("/", (_req, res) => res.json({ ok: true }));
app.post("/users", (req, res) => res.status(201).json(req.body));

export default http(app); // or: export const api = http(app);

http() returns the app untouched — it's a static marker, not a wrapper, so it has no runtime effect. That's all you need: every route you register is served by the deployed proxy. The marker is the only way to declare an HTTP app — there's exactly one per project, and it must be exported so the scanner can find it.

NestJS

Nest apps work the same way, with one difference: a Nest app only exists after an async NestFactory.create(...), so instead of a ready app object you wrap your bootstrap function and have it return the app:

// src/main.ts
import { NestFactory } from "@nestjs/core";
import { http } from "@alzulejos/laranja-decorators";
import { AppModule } from "./app.module";

export async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  // configure however you like — pipes, guards, middleware, raw body, cookies…
  app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }));
  return app; // ← the only change laranja needs
}

// Local dev only: laranja imports this file, it never runs it as the entry point.
if (require.main === module) {
  void bootstrap().then((app) => app.listen(process.env.PORT ?? 3000));
}

export default http(bootstrap); // wrap the factory, not a module

Keep listen() outside bootstrap(), as above. laranja serves your app itself — on AWS through the Lambda proxy, on Azure through a loopback server — so a listen() inside the factory binds a second, unused port on every cold start. On Azure that is also a failure risk: if the Functions host has PORT set, your app binds it, EADDRINUSE is thrown during bootstrap, and the app never starts.

laranja runs your bootstrap() verbatim, so every pipe, guard, and piece of middleware you configure is preserved — nothing is re-derived. You keep your normal Nest project (@nestjs/platform-express); no laranja-specific restructuring.

Two things to know:

  • Build before you deploy. laranja packages your compiled output (nest builddist/), because Nest's dependency injection relies on the decorator metadata your own TypeScript build emits. laranja deploys what you build — it doesn't run your build for you — so run nest build yourself after every code change. Deploying without a dist/ fails with a clear message, but a stale dist/ (source edited since your last build) deploys silently as outdated code, so make the build part of your deploy step (e.g. nest build && laranja deploy).
  • Use the default Express platform. The Fastify adapter isn't supported yet.

Routing, middleware, and STAGE

Your app runs as-is inside the deployed function. Standard Express features work — routing, middleware, JSON bodies, route params. The active stage is available as process.env.STAGE:

app.get("/whoami", (_req, res) => res.json({ stage: process.env.STAGE }));

CORS and auth

The endpoint is public — laranja adds no auth layer, so handle authentication inside your app, the same way you would anywhere else. Cross-origin access is off by default and opt-in via cors in your config (AWS today; on Azure, set CORS headers in your app).

Compute (memory & timeout)

The HTTP function's memory and timeout come from compute in your config, overridable under the http key in resources. Long-running work belongs in a cron job or behind a queue, not a request.

Workers-only deployments

If your HTTP API is hosted elsewhere and you only want to deploy scheduled jobs and queue consumers, just don't add an http() marker — there's nothing to set in config:

// laranja.config.ts
const config: LaranjaConfig = {
  name: "my-workers",
  env: { LOG_LEVEL: "info" },
};

With no marker, only your @Cron / @Queue handlers are deployed — no HTTP function, no public endpoint.

For a workers-only Nest app, there's no http(bootstrap) to build the DI container from, so declare your module with the workers() marker instead (export default workers(AppModule)) — see Cron jobs → NestJS.