# Deploying [#deploying] An Aventara server made by `aventara new` is a NestJS application. You deploy it like any Node service: install, build, run `dist/main`. A Next.js app that hosts Aventara deploys as a Next.js app: see [Next.js](#nextjs). The framework adds three things to think about: the generated files (the discovery artifact and the rest of what `aventara:prepare` writes) that must exist before the build, the database schema that must exist before the first request, and the frontend client, which is bound to the contract your server serves. ## Environment [#environment] The scaffold reads two variables: | Variable | Meaning | Default | | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | `DATABASE_URL` | The database connection string. The server will not start without it: `DATABASE_URL is not set: put it in .env (the start scripts load it) or in the environment.` The check runs while Nest builds the application, before it listens. | none | | `PORT` | The port `main.ts` listens on. | `3000` | Locally they live in `.env`. In production, set them in the platform's environment. `npm run start:prod` is: ```json "start:prod": "node --env-file-if-exists=.env dist/main" ``` `--env-file-if-exists` loads `.env` when it is present and does nothing when it is not, so the same script works on a laptop and on a platform with no file. A real environment variable wins over `.env`: with `DATABASE_URL=file:./override.db npm run start:prod`, the server used `override.db`, not the `.env` value. Do not commit `.env`. The scaffold's `.gitignore` already lists it, with `/src/generated/` (or `/src/aventara/`, in a project whose Prisma client is generated into `src/generated`) and the SQLite files. ## Build and run [#build-and-run] ```bash npm ci # installs, then runs `aventara:prepare` (the postinstall script) npm run build # nest build -> dist/ npm run start:prod # node --env-file-if-exists=.env dist/main ``` `npm ci` runs the project's `postinstall` script, which runs `aventara:prepare`: it reads `prisma/schema.prisma`, runs `prisma generate`, writes `src/generated/aventara/discovery.artifact.ts`, then runs `aventara-build`, which writes `resources.generated.ts`, `computed-fields.generated.ts` and `contracts.generated.ts` beside it. All are generated and git-ignored, and `nest build` needs them. If your pipeline installs with scripts disabled (`npm ci --ignore-scripts`), run `npm run aventara:prepare` yourself before `npm run build`. Re-run it whenever the schema changes. Prefer building once and running the same artifact everywhere: build in CI, copy `dist/` and a production `node_modules`, and give each environment its own `DATABASE_URL`. Your Prisma client is generated for the provider in `schema.prisma` (SQLite or PostgreSQL), so build with the same schema you deploy. On start, the host logs one line you can use as a readiness check: ```text Aventara mounted 45 operations at /api (+ GET /api/_contract, POST /api/_transactions, POST /api/_all) ``` Call `app.enableShutdownHooks()` in `main.ts` so Nest closes the Prisma client on SIGTERM. ### Build-time Contracts [#build-time-contracts] A long-running Nest server compiles its Contracts when it starts, which takes tens of milliseconds, and needs nothing more. On serverless and edge platforms every cold start pays that. `aventara-build` compiles the Contracts once, at build time, and writes them to `contracts.generated.ts`. Pass that file as `prebuilt` where you create the framework: ```ts // a serverless or edge entry import { createFramework } from "@aventara/core"; import { createPrismaAdapter } from "@aventara/prisma7-adapter"; import adapter from "./aventara.adapter.js"; import { aventaraConfig } from "./aventara.config.js"; import { aventaraContracts } from "./generated/aventara/contracts.generated.js"; import { PrismaService } from "./prisma.service.js"; const prisma = new PrismaService(); export const framework = await createFramework({ ...aventaraConfig, adapter: await createPrismaAdapter({ ...adapter, client: prisma }), prebuilt: aventaraContracts, }); ``` `PrismaService` is the file the scaffold writes (`src/prisma.service.ts`): a `PrismaClient` subclass that reads `DATABASE_URL`. Outside Nest, build it directly with `new PrismaService()`, as above. The Contracts, their hashes and `GET /api/_contract` are the same as when compiled at startup. The scaffolded Nest server passes no `prebuilt`. The framework uses the file only when this `@aventara/core` compiled it, for this protocol version, from the same configuration and the same database model. Otherwise it ignores the whole file, compiles at startup as if there were none, and prints one warning, `AVENTARA_PREBUILT_STALE`, that says what changed (the Resources whose configuration or model changed, or the version that differs). A stale file never serves a wrong Contract. It only costs the startup time it was meant to save. Add a check to CI so a stale file fails the pipeline before the deploy: ```bash npx aventara-build --check ``` `--check` writes nothing. It exits `1` when `resources.generated.ts`, `computed-fields.generated.ts` or `contracts.generated.ts` is missing or differs from what a build would write now, and says what changed, in the words of the `AVENTARA_PREBUILT_STALE` warning at startup: ```text aventara-build: src/generated/aventara/contracts.generated.ts is stale: it was built by @aventara/core 1.0.0-rc.7, and this is 1.0.0-rc.8. Run aventara-build (npm run aventara:prepare) and deploy the file it writes. aventara-build: src/generated/aventara/contracts.generated.ts is stale: Resource User changed and the configuration or model outside the Resources changed since it was written. Run aventara-build (npm run aventara:prepare) and deploy the file it writes. ``` It names the core version that wrote the file against the installed one, a file format or protocol version that differs, the Resources that changed, are new or are gone, the configuration or model outside the Resources, or a file that no longer reads as a prebuilt Contracts file. A configuration that a server would refuse at startup (`CONFIG_...` or `COMPILER_...`) fails `aventara-build` itself, with the same message. Two things follow from `aventara-build` running your configuration: * `src/aventara.config.ts` and everything it imports run at build time. Keep their top level free of side effects: no connection, no request. * A value read from `process.env` in the configuration is read when the build runs. If the deployed environment gives another value, the file reads as stale and the framework compiles at startup. Where the three files go: beside the discovery artifact, in `src/generated/aventara/`, or in `src/aventara/` in a project whose Prisma client is generated into `src/generated` (`prisma generate` empties that directory). See [CLI reference](/framework/docs/cli-reference#aventara-build). ### The server starts without a database [#the-server-starts-without-a-database] The framework compiles its contracts from the discovery artifact, not from a live connection. A server whose database is unreachable still starts, still mounts its routes and still serves `GET /api/_contract`; a request that needs the database fails with `A3001` ("Adapter execution failed."), and your [diagnostics reporter](/framework/docs/request-ids-and-diagnostics) gets the connection error. This is verified with `DATABASE_URL="postgresql://u:p@127.0.0.1:1/db"` (nothing listening). It is what lets CI generate the frontend client without a database (below). It also means a liveness probe on `/api/_contract` says nothing about the database: add your own health route for that, outside the protocol's `_` paths (for example `GET /api/health`). ## PostgreSQL [#postgresql] Create the project for PostgreSQL, or switch an existing one: ```bash npx @aventara/cli@rc new my-api --db postgresql ``` The scaffold then differs from SQLite in four places: ```prisma datasource db { provider = "postgresql" } ``` ```ts // src/prisma.service.ts import { PrismaPg } from "@prisma/adapter-pg"; super({ adapter: new PrismaPg({ connectionString: url }) }); ``` ```ts // src/aventara.adapter.ts export default describePrismaAdapter({ discovery, provider: "postgresql", driver: "@prisma/adapter-pg" }); ``` ```json "aventara:prepare": "aventara-prisma7-generate --schema prisma/schema.prisma --client src/generated/prisma --provider postgresql --driver @prisma/adapter-pg --out src/generated/aventara/discovery.artifact.ts --config src/aventara.config.ts && aventara-build" ``` and `.env` gets Prisma's placeholder, which you replace: ```text DATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/DBNAME?schema=public" ``` `provider` and `driver` are declared, never sniffed: they must match your schema and the Prisma driver adapter you construct. The generate step checks `--provider` against your schema's `datasource` block and refuses a mismatch. It cannot check the driver. Changing database means changing all four, then `npm run aventara:prepare`. Transaction options (`timeout`, `maxWait`, `isolationLevel`) go on the adapter ([Configuration](/framework/docs/configuration#adapter)). SQLite supports `Serializable` only; on PostgreSQL pick the level your invariants need. String filters (`contains`, `startsWith`, `endsWith`) follow the database by default: case-insensitive for ASCII on SQLite, case-sensitive on PostgreSQL. On PostgreSQL, pass `mode: "sensitive"` or `mode: "insensitive"` to make the behavior explicit; SQLite offers only `"default"` ([Filtering](/framework/docs/filtering-sorting-paging#case-mode)). ## Creating and changing the schema [#creating-and-changing-the-schema] Aventara never touches your database or your schema: that is Prisma's job. Two commands: | Command | Use it for | | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | `npx prisma db push` | Development and prototypes. Makes the database match `schema.prisma` right now. No migration history. | | `npx prisma migrate dev --name ` | Development, when you want a history. Writes `prisma/migrations/_/migration.sql` and applies it. Commit the folder. | | `npx prisma migrate deploy` | Production. Applies the committed migrations that have not run yet. Never generates or resets. | For production, use migrations: `migrate dev` while developing, commit `prisma/migrations/`, and run `migrate deploy` as a release step before the new server version starts: ```bash DATABASE_URL=file:./mig.db npx prisma migrate dev --name init # Applying migration `20261006225019_init` ... Your database is now in sync with your schema. DATABASE_URL=file:./mig2.db npx prisma migrate deploy # 1 migration found ... All migrations have been successfully applied. ``` Prisma 7 reads the connection from `prisma.config.ts`, which the scaffold writes to load `.env` and read `DATABASE_URL`, so the commands see the same variable as the server. After any schema change, in this order: change `schema.prisma`; migrate (or push); `npm run aventara:prepare`; rebuild; deploy. A new contract has a new hash, so regenerate the frontend client too. ## The frontend client [#the-frontend-client] The generated client is bound to the contract hash of the server it was generated from. A server whose contract changed answers a stale client with `A2005` on every call. Follow these steps: 1. Deploy (or start) the server. 2. Run `npm run avclient:generate` in the frontend, with `AVENTARA_API_URL` pointing at it. 3. Commit the generated files (`src/api/AvClient.ts` and `src/api/generated/`). They are deterministic: the same contract writes the same bytes, so a diff means the contract changed. 4. Build the frontend. ```bash # in the frontend, in CI AVENTARA_API_URL=https://staging.example.com/api npm run avclient:generate -- --yes git diff --exit-code src/api # fails the build if the committed client is stale ``` If the contract did not change, the generator prints `avclient: up to date: ... nothing was written.` and exits 0. ### Generating in CI against a throwaway server [#generating-in-ci-against-a-throwaway-server] The server does not need a database to serve its contract, so a frontend pipeline can start the server itself: ```bash cd api npm ci && npm run build DATABASE_URL="file:./ci.db" PORT=3000 node dist/main & # any valid URL will do; the contract needs no tables npx wait-on http://localhost:3000/api/_contract cd ../web AVENTARA_API_URL=http://localhost:3000/api npm run avclient:generate -- --yes git diff --exit-code src/api ``` > Note: the commands above were each verified individually (build, start without a database, generate, `up to date`); the assembled CI script was not run end to end here. ### Keeping versions together [#keeping-versions-together] The server's contract is checked on every call, so ship the server and the frontend that matches it. A server deployed with a new schema before the frontend is regenerated leaves old frontends getting `A2005` until they update. Mitigations: deploy additive changes (a new nullable column does not break old clients' calls but still changes the hash, so the old client still gets `A2005`), release both together, or serve the previous frontend build until the new one is live. ## CORS and browsers [#cors-and-browsers] A browser frontend on another origin needs CORS on the server. Nest's own `enableCors` works on Aventara routes: it is an HTTP-platform middleware, which also runs on the protocol's routes ([NestJS host](/framework/docs/nestjs-host#what-runs-on-aventara-routes)). The `aventara new` scaffold enables it from `CORS_ORIGINS`, a comma-separated list in the environment. The scaffold's `.env` lists the local dev servers; in production, set it to your deployed frontend's origin (and remove the local ones if they should not be allowed): ```bash CORS_ORIGINS="https://app.example.com" ``` Unset or empty, CORS is off: no origin is allowed. (`.env` is not committed; set the variable in your hosting environment.) The protocol's custom headers must be allowed, and you may want the request id exposed to browser code. Both are `enableCors` options; the scaffold's call allows the headers by reflecting the request's, so add only the exposure: ```ts // src/main.ts app.enableCors({ origin: corsOrigins.length > 0 ? corsOrigins : false, exposedHeaders: ["Aventara-Request-Id"], }); ``` Verified: the preflight `OPTIONS` for `content-type, aventara-protocol-version, aventara-contract-hash, aventara-request-id` answers `204` with `Access-Control-Allow-Origin: https://app.example.com`, the requested headers allowed, and `Access-Control-Expose-Headers: Aventara-Request-Id`; the actual `POST` and `GET /_contract` carry the same origin and expose headers. Authentication via `Authorization` needs `Authorization` allowed too, which `enableCors` does by default by reflecting the request's headers. CORS is not access control. It tells browsers which origins may read the answers; a server, a script or a bot ignores it. [Guards](/framework/docs/authentication-and-guards) decide who may call, and a [rate limiter](/framework/docs/limits-and-safety#rate-limiting) bounds how often. ## Next.js [#nextjs] A Next.js app made with `aventara new --host next` (or `init`) deploys like any Next.js app on the Node.js runtime: ```bash npm ci # installs, then runs aventara:prepare (the postinstall script) npm run build # prebuild runs aventara:prepare again, then next build npm start # next start ``` * `aventara:prepare` ends with `aventara-build --client`, so the build compiles the Contracts and writes the browser client (`src/aventara/client.ts`, `src/aventara/universal.ts`, `src/aventara/generated/`) from the same configuration. Nothing has to run first, and there is no frontend client to commit or regenerate separately. * `src/aventara/server.ts` passes `prebuilt: aventaraContracts` when `NODE_ENV` is `production`, so `next start` loads the Contracts instead of compiling them. In CI, `npx aventara-build --check --client` exits `1` when a generated file is stale. * `src/instrumentation.ts` awaits `ready()`, so a bad configuration fails `next start` instead of the first request. Keep it: `@/aventara/universal` needs it on the server. * Keep `serverExternalPackages: ["@aventara/core", "@aventara/prisma7-adapter"]` in `next.config.ts`, so every route and page shares one core and one Prisma client ([Next.js host](/framework/docs/nextjs-host#serverexternalpackages)). * `DATABASE_URL` comes from the environment; Next.js also loads `.env`. `src/prisma.ts` refuses a missing one on first use. * The Edge runtime is not supported. Keep Aventara's routes and pages on the Node.js runtime (the default). * Aventara needs no proxy file (`proxy.ts`). If your own proxy reads request bodies, Next.js's limit on the bodies it buffers there (10 MB by default) applies before `limits.maxRequestBytes` does. * The browser calls the API on its own origin, so there is no CORS to configure. ## Behind a proxy [#behind-a-proxy] * The protocol path is `api` (`/api`), regardless of Nest's global prefix. Route `/api/*` to the server. * Pass `Aventara-*` and `Authorization` headers through. If your proxy assigns a request id, have it set `Aventara-Request-Id` (not `X-Request-Id`). * A proxy's own body limit and the framework's `maxRequestBytes` both apply: the stricter one wins. ## Checklist [#checklist] * [ ] `DATABASE_URL` and `PORT` set in the environment; `.env` not committed. * [ ] `aventara:prepare` runs before `build` (install scripts enabled, or run it yourself). * [ ] On serverless or edge: `aventara-build --check` in CI, and `prebuilt: aventaraContracts` where the framework is created. * [ ] `prisma migrate deploy` runs before the new server starts. * [ ] A health route outside `/api/_*`, and `app.enableShutdownHooks()`. * [ ] `CORS_ORIGINS` set to the deployed frontend's origin, if a browser on another origin calls the API. * [ ] A [rate limiter](/framework/docs/limits-and-safety#rate-limiting) in front of, or in, the server. * [ ] Guards and limits set for the client layer: [Authentication and guards](/framework/docs/authentication-and-guards), [Limits and safety](/framework/docs/limits-and-safety). * [ ] The frontend client regenerated and committed from the deployed contract (a separate frontend; a Next.js app builds its own). * [ ] Next.js: `serverExternalPackages` kept, `instrumentation.ts` awaiting `ready()`, `aventara-build --check --client` in CI. ## See also [#see-also] * [Getting started](/framework/docs/getting-started) * [Upgrading](/framework/docs/upgrading) * [Configuration](/framework/docs/configuration#adapter) * [Frontend client](/framework/docs/keeping-in-sync) * [Troubleshooting](/framework/docs/troubleshooting)