# Getting started (/docs/composer/getting-started) > For the complete Prisma documentation index, see [llms.txt](https://www.prisma.io/docs/llms.txt). A markdown version of any docs page is available by appending `.md` to its URL. Build a two-service Prisma App from an empty directory and run it on your machine with one command. Location: Composer > Getting started By the end of this page you will have a working Prisma App: a `quotes` API and a public `gateway` that calls it. A typed contract wires the two together, and one command runs them on your machine. Along the way you will meet every core Composer idea once: a contract, a service, a root module, a build, and a local run. The app is deliberately tiny, with no database, so you can see the whole shape at once. Adding a Postgres is the natural next step. See [Databases](https://www.prisma.io/docs/composer/databases) when you are ready. > [!NOTE] > Working with a coding agent > > Add the Composer skill first, even if you plan to write every line yourself. It ships inside the `@prisma/composer` package, and one command, run after step 1 installs the packages, copies it into the directories your agent reads and stops the agent from inventing an API that does not exist: > > > > > #### bun > ```bash > bunx --bun prisma@latest init > ``` > > > #### pnpm > ```bash > pnpm dlx prisma@latest init > ``` > > > #### yarn > ```bash > yarn dlx prisma@latest init > ``` > > > #### npm > ```bash > npx prisma@latest init > ``` > > > > `init` also adds a `postinstall` hook that keeps the skill matching the installed version; see [`skills`](https://www.prisma.io/docs/cli/skills). ## Prerequisites [#prerequisites] * **Node.js 22.18 or newer on the 22 line, 24.11 or newer on the 24 line, or 26 or newer.** Check with `node --version`. Composer hands your TypeScript entry file straight to Node. Node runs `.ts` files directly only from 22.18.0, the release that turns type stripping on by default. On any older version, including 22.17, Composer commands stop at `ERR_UNKNOWN_FILE_EXTENSION` naming your own entry file. * **[Bun](https://bun.sh).** Prisma Compute runs Bun, so the server code targets it (`Bun.serve`). This guide also uses `bun build` to produce the built output. * **npm, pnpm, or Bun.** Bun workspaces need two extra direct dependencies; see the note in step 1. You do not need a Prisma account for this page. Local runs never talk to the platform. You only need credentials when you [deploy](https://www.prisma.io/docs/composer/deploying). ## 1. Set up the project [#1-set-up-the-project] Create a directory and install the two Composer packages, plus [arktype](https://arktype.io) for the contract schemas: #### bun ```bash bun init npm pkg set type module # couldn't auto-convert command bun add @prisma/composer @prisma/composer-prisma-cloud arktype bun add --dev typescript @types/bun prisma ``` #### pnpm ```bash pnpm init npm pkg set type=module # couldn't auto-convert command pnpm add @prisma/composer @prisma/composer-prisma-cloud arktype pnpm add -D typescript @types/bun prisma ``` #### yarn ```bash yarn init npm pkg set type=module # couldn't auto-convert command yarn add @prisma/composer @prisma/composer-prisma-cloud arktype yarn add --dev typescript @types/bun prisma ``` #### npm ```bash npm init npm pkg set type=module npm install @prisma/composer @prisma/composer-prisma-cloud arktype npm install -D typescript @types/bun prisma ``` `"type": "module"` matters: Composer loads your entry file as an ES module, and without it every Composer command stops at `COMPOSE.ENTRY_UNLOADABLE` ("Cannot use import statement outside a module"). You do not need to pin `effect` yourself. `@prisma/composer` and `@prisma/composer-prisma-cloud` pin every `effect`-family package their deploy engine needs, so a fresh install resolves a single copy. A conflict only appears when something in your dependency tree, your own `effect` pin or another dependency's, hoists a different `effect` where the deploy engine resolves it. The CLI then cannot evaluate `prisma.config.ts` and stops with `CLI.CONFIG_UNREADABLE`; see [When prisma.config.ts fails on an effect version conflict](https://www.prisma.io/docs/composer/deploying#when-prismaconfigts-fails-on-an-effect-version-conflict). > [!NOTE] > Bun workspaces > > In a Bun workspace (a monorepo with isolated installs), two of Composer's indirect dependencies must also be direct ones, because Bun does not expose transitive packages or their bins to your app: > > ```bash > bun add alchemy > bun add -d @prisma/dev > ``` > > Without `alchemy`, `dev` and `deploy` stop at `DEPLOY.ALCHEMY_BIN_MISSING`; without `@prisma/dev`, local dev stops with "local dev needs @prisma/dev". A single-package Bun project is not affected. Add a `tsconfig.json` so the compiler checks your wiring the same way the deploy will: ```json title="tsconfig.json" { "compilerOptions": { "target": "ES2022", "module": "Preserve", "moduleResolution": "bundler", "allowImportingTsExtensions": true, "noEmit": true, "strict": true, "skipLibCheck": true, "types": ["bun"] }, "include": ["module.ts", "src"] } ``` This is what you are about to create: ```text no-copy my-app/ ├── module.ts # the root module: the app itself ├── prisma.config.ts # deploy config: the composer section (read only by the CLI) └── src/ ├── quotes/ │ ├── contract.ts # the quotes service's public API, as types │ ├── service.ts # what quotes is: deps + build + what it exposes │ └── server.ts # the code that actually runs └── gateway/ ├── service.ts └── server.ts ``` ## 2. Create the quotes service [#2-create-the-quotes-service] A service is three files. First, the **contract**: the API other services will call, written as schemas. It lives with the service that owns it. Any [Standard Schema](https://standardschema.dev) validator works (arktype, zod, valibot); these examples use arktype: ```ts title="src/quotes/contract.ts" import { contract, rpc } from '@prisma/composer/service-rpc'; import { type } from 'arktype'; export const quotesContract = contract({ random: rpc({ input: type({}), output: type({ quote: 'string' }) }), }); ``` Second, the **service declaration**. This is pure data with no behavior. It says what the service is called, what it depends on (nothing yet), how it is built, and which contract it exposes: ```ts title="src/quotes/service.ts" import node from '@prisma/composer/node'; import { compute } from '@prisma/composer-prisma-cloud'; import { quotesContract } from './contract.ts'; export default compute({ name: 'quotes', deps: {}, build: node({ module: import.meta.url, entry: '../../dist/quotes/server.mjs' }), expose: { rpc: quotesContract }, }); ``` Third, the **server**: the code your build turns into `dist/quotes/server.mjs` and the platform boots. `serve()` generates the HTTP handler from the contract. If you forget a handler or return the wrong shape, the code does not compile: ```ts title="src/quotes/server.ts" import { serve } from '@prisma/composer/service-rpc'; import service from './service.ts'; const port = service.port(); // the reserved port, resolved (default 3000) const QUOTES = [ 'Simplicity is prerequisite for reliability.', 'Make it work, make it right, make it fast.', ]; const handler = serve(service, { rpc: { random: async () => ({ quote: QUOTES[Math.floor(Math.random() * QUOTES.length)]! }), }, }); export default handler; // Bind all interfaces. Compute routes external HTTP to the VM, so a // loopback-only listener would be unreachable. Bun.serve({ port, hostname: '0.0.0.0', fetch: handler }); ``` Notice what is missing: no URL of anything and no `process.env`. Every service gets a port for free, read through `service.port()`. Dependencies arrive through `service.load()`. Any configuration of your own arrives through `service.input()` (see [Service input](https://www.prisma.io/docs/composer/service-input)). Those three typed accessors are the whole framework contract with your code. ## 3. Create the gateway service [#3-create-the-gateway-service] The gateway depends on the quotes contract. Note the asymmetry with step 2: the quotes service *exposed* the bare contract (what it provides), while the gateway wraps it in `rpc()` (what it needs: a client of this contract). Declaring `deps: { quotes: rpc(quotesContract) }` means `service.load()` hands the server a ready-made typed client: ```ts title="src/gateway/service.ts" import node from '@prisma/composer/node'; import { rpc } from '@prisma/composer/service-rpc'; import { compute } from '@prisma/composer-prisma-cloud'; import { quotesContract } from '../quotes/contract.ts'; export default compute({ name: 'gateway', deps: { quotes: rpc(quotesContract) }, build: node({ module: import.meta.url, entry: '../../dist/gateway/server.mjs' }), }); ``` ```ts title="src/gateway/server.ts" import service from './service.ts'; const { quotes } = service.load(); const port = service.port(); Bun.serve({ port, hostname: '0.0.0.0', fetch: async () => { const { quote } = await quotes.random({}); return new Response(quote); }, }); ``` The gateway exposes no contract of its own, so there is no `serve()` here. It is an ordinary HTTP server that happens to receive a typed client, and calling `quotes.random({})` is an ordinary async function call. ## 4. Compose the app [#4-compose-the-app] The root module is the app. It provisions both services and wires the quotes service's exposed port into the gateway's dependency slot. `provision()` returns a ref carrying one port per exposed contract, so `quotes.rpc` exists because the service declared `expose: { rpc: ... }`: ```ts title="module.ts" import { module } from '@prisma/composer'; import gatewayService from './src/gateway/service.ts'; import quotesService from './src/quotes/service.ts'; export default module('my-app', ({ provision }) => { const quotes = provision(quotesService); provision(gatewayService, { deps: { quotes: quotes.rpc } }); }); ``` Next to it goes `prisma.config.ts`, the one config file the Prisma CLI reads. The deploy config is its `composer` section: the extensions that deploy your app and the store that keeps deploy state. Only the CLI reads this file; your app code never imports it. If you ran `prisma init` for the agent skill, `prisma.config.ts` already exists with a `skills` section. Add the `composer` key to its `definePrismaConfig` call and keep `skills`; the `postinstall` hook `init` added reads it. The file then looks like this, with your own agents in `skills`: ```ts title="prisma.config.ts" import { defineConfig as composer } from '@prisma/composer/config'; import { nodeBuild } from '@prisma/composer/node/control'; import { prismaCloud, prismaState } from '@prisma/composer-prisma-cloud/control'; import { definePrismaConfig } from 'prisma/config'; export default definePrismaConfig({ skills: { agents: ['claude'], }, composer: composer({ extensions: [prismaCloud(), nodeBuild()], state: prismaState(), }), }); ``` Otherwise create the file with the `composer` section alone: ```ts title="prisma.config.ts" import { defineConfig as composer } from '@prisma/composer/config'; import { nodeBuild } from '@prisma/composer/node/control'; import { prismaCloud, prismaState } from '@prisma/composer-prisma-cloud/control'; import { definePrismaConfig } from 'prisma/config'; export default definePrismaConfig({ composer: composer({ extensions: [prismaCloud(), nodeBuild()], state: prismaState(), }), }); ``` The commands find `prisma.config.ts` from the directory you run them in, so run them from the app's root. When you add a database, its `orm` section goes in this same file. ## 5. Build [#5-build] You own the build. The framework only assembles what you built, and it has one requirement: each entry must be a single self-contained file, with everything inlined except the runtime's own built-ins (`bun`, `bun:*`, `node:*`). Any bundler that produces such a file works. This guide uses Bun. Build the two services separately, because a multi-entry build would split the shared contract code into a chunk neither output contains: ```json title="package.json" { "scripts": { "build": "bun build src/quotes/server.ts --target=bun --outfile dist/quotes/server.mjs && bun build src/gateway/server.ts --target=bun --outfile dist/gateway/server.mjs" } } ``` #### bun ```bash bun run build ``` #### pnpm ```bash pnpm run build ``` #### yarn ```bash yarn build ``` #### npm ```bash npm run build ``` This is also a good moment to typecheck. The compiler checks the whole wiring, so a dependency wired to the wrong producer or a missing RPC handler fails here, in seconds: #### bun ```bash bunx tsc --noEmit ``` #### pnpm ```bash pnpm tsc --noEmit ``` #### yarn ```bash yarn tsc --noEmit ``` #### npm ```bash npx tsc --noEmit ``` ## 6. Run it locally [#6-run-it-locally] With the build from step 5 in place, one command brings the whole app up on your machine, both services wired together, with no cloud credentials: #### bun ```bash bunx prisma dev module.ts ``` #### pnpm ```bash pnpm prisma dev module.ts ``` #### yarn ```bash yarn prisma dev module.ts ``` #### npm ```bash npx prisma dev module.ts ``` It runs the same pipeline a deploy runs, against local stand-ins for Prisma Compute and Prisma Postgres, and prints each service's local URL once every service accepts connections: ```text no-copy dev: ready gateway: http://localhost:3001 quotes: http://localhost:3000 ``` Each service gets the lowest free port from 3000 up, so your ports can differ if another program already uses 3000 or 3001. `dev` keeps running in this terminal. Verify it works by calling the gateway. Open a second terminal in the same directory and ask `dev url` where the gateway answers, then call it: ```bash curl "$(npx prisma dev url gateway)" ``` ```text no-copy ✔ The service gateway is running at http://localhost:3001 Make it work, make it right, make it fast. ``` The first line comes from `dev url`, which says what it found; only the bare URL is passed to `curl`. The second line is the gateway's response, one of the two quotes at random. That response traveled over one typed RPC hop: the gateway received your request, called `quotes.random({})` through its injected client, and returned the result. `npx prisma dev url gateway` prints only the gateway's URL, so the command works whichever port the gateway got. If it fails with `DEV.NOT_RUNNING`, the first terminal's `dev` is no longer running; start it again. `dev url` is in `prisma` since `8.0.0-rc.24`. On an older version, call the URL that `dev` printed, for example `curl http://localhost:3001`. Now try the quotes service directly, from the same second terminal: ```bash curl -i -X POST "$(npx prisma dev url quotes)/rpc/random" -H 'content-type: application/json' -d '{}' ``` ```text no-copy ✔ The service quotes is running at http://localhost:3000 HTTP/1.1 401 Unauthorized ... {"error":"Unauthorized: missing or invalid service key"} ``` You get `401 Unauthorized`. That is deliberate: Composer mints a **service key** for each dependency edge and the quotes service accepts only calls from the gateway. Neither service's code mentions a key. See [Services and contracts](https://www.prisma.io/docs/composer/services-and-contracts#calls-are-authenticated-for-you) for how this works. `dev` watches your built output and restarts a service when its build changes, so run `npm run build` in the second terminal and the affected service restarts. Press `Ctrl-C` in the first terminal to stop the app. Your local data and ports stay, so the next start is warm. See [Local development](https://www.prisma.io/docs/composer/local-development) for logs, running the app in the background, `--fresh`, and what persists. ## 7. Deploy it (optional) [#7-deploy-it-optional] Local dev needed no account. Deploying the same app to [Prisma Compute](https://www.prisma.io/docs/compute) needs a signed-in session: #### bun ```bash bunx prisma auth login bunx prisma deploy module.ts ``` #### pnpm ```bash pnpm prisma auth login pnpm prisma deploy module.ts ``` #### yarn ```bash yarn prisma auth login yarn prisma deploy module.ts ``` #### npm ```bash npx prisma auth login npx prisma deploy module.ts ``` (In CI, where the browser sign-in is not an option, set `PRISMA_SERVICE_TOKEN` and `PRISMA_WORKSPACE_ID` from the [Prisma Console](https://console.prisma.io/?utm_source=docs\&utm_medium=content\&utm_content=composer) instead; see [Deploying](https://www.prisma.io/docs/composer/deploying#credentials).) A new project needs a region, so give it one before the first deploy, in the deploy config or in the environment. The [`deploy` reference](https://www.prisma.io/docs/cli/deploy) shows both ways. The CLI creates a project named `my-app` in your workspace (a project this module deployed before is reused; one whose hosted state the CLI cannot verify stops the deploy with `HostedStateBootstrapError`, so pass `--name ` in that case), provisions both services on Prisma Compute, points the gateway's `quotes` dependency at the deployed quotes service, and starts everything. The deploy finishes by printing what it made: your module names, the platform resource each became, and the public URLs. Open the gateway's URL and you get a quote. Re-deploying is idempotent. For an isolated copy of the whole app, deploy a stage: #### bun ```bash bunx prisma deploy module.ts --stage demo ``` #### pnpm ```bash pnpm prisma deploy module.ts --stage demo ``` #### yarn ```bash yarn prisma deploy module.ts --stage demo ``` #### npm ```bash npx prisma deploy module.ts --stage demo ``` Tearing a stage down is the `destroy` operation of the [control API](https://www.prisma.io/docs/cli/deploy#the-control-api). That operation does not read your `auth login` session: it needs `PRISMA_SERVICE_TOKEN` and `PRISMA_WORKSPACE_ID` in the environment, the same service-token credentials CI uses. Without them, delete the stage's branch in the [Console](https://console.prisma.io/?utm_source=docs\&utm_medium=content\&utm_content=composer) instead. See [Deploying](https://www.prisma.io/docs/composer/deploying) for stages, CI, and teardown in full. ## Where to go next [#where-to-go-next] * [Apps and Modules](https://www.prisma.io/docs/composer/apps-and-modules): package a service and its database as a reusable Module. * [Databases](https://www.prisma.io/docs/composer/databases): add a Postgres, plain or typed by a Prisma ORM contract. * [Building blocks](https://www.prisma.io/docs/composer/building-blocks): compose the ready-made cron, storage, and streams Modules. * [Testing](https://www.prisma.io/docs/composer/testing): unit tests with `mockService`, integration tests with `bootstrapService`. * [Porting an existing app](https://www.prisma.io/docs/composer/porting-an-app): keep your server code and declare the app around it. ## Related pages - [`Apps and Modules`](https://www.prisma.io/docs/composer/apps-and-modules): How services, resources, and Modules compose into a Prisma App, and how provision() wires them together. - [`Building blocks`](https://www.prisma.io/docs/composer/building-blocks): Compose the ready-made cron, storage, and streams Modules instead of building scheduled jobs, blob storage, or event streams yourself. - [`Core concepts`](https://www.prisma.io/docs/composer/core-concepts): The ideas every Composer declaration and command builds on: services, resources, Modules, ports, contracts, stages, and the deploy model. - [`Databases`](https://www.prisma.io/docs/composer/databases): Give a service a Postgres database, either as a plain connection or typed by a Prisma ORM contract with managed migrations. - [`Deploying`](https://www.prisma.io/docs/composer/deploying): Deploy a Prisma App to production or an isolated stage, run it in CI, and tear environments down safely.