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 build→dist/), 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 runnest buildyourself after every code change. Deploying without adist/fails with a clear message, but a staledist/(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.
laranja