# Core concepts (/docs/composer/core-concepts)

> 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.

The ideas every Composer declaration and command builds on: services, resources, Modules, ports, contracts, stages, and the deploy model.

Location: Composer > Core concepts

Prisma Composer is a TypeScript framework for applications made of several services. You touch two things: the `@prisma/composer` library, which you use to declare your services and what they depend on, and the `dev` and `deploy` commands in the Prisma CLI, which run and provision what you declared on [Prisma Compute](https://www.prisma.io/docs/compute) and [Prisma Postgres](https://www.prisma.io/docs/postgres). The declarations use a small vocabulary that repeats everywhere: in the authoring API, in deploy output, and in error messages. The sections below define each term in plain language, and each links to the page that goes deeper.

## Declarations are data [#declarations-are-data]

Everything you author in Composer is a declaration: a plain TypeScript value that describes a piece of your application without running it. A service declaration says what the service is, what it depends on, and how it was built:

```ts title="src/storefront/service.ts"
import nextjs from '@prisma/composer/nextjs';
import { rpc } from '@prisma/composer/service-rpc';
import { compute } from '@prisma/composer-prisma-cloud';
import { catalogContract } from '../catalog/contract.ts';

export default compute({
  name: 'storefront',
  deps: { catalog: rpc(catalogContract) },
  build: nextjs({ module: import.meta.url, appDir: '..' }),
});
```

The `deps` line says the storefront calls the catalog service's API, named by a [contract](#contracts) defined further down this page. Importing this file starts no service and provisions nothing: `compute()` returns a plain description. The declaration is data that three different consumers read: the TypeScript compiler checks the wiring, the deploy provisions what it describes, and the runtime injects the declared dependencies into your code. That is why a mistake, such as a dependency wired to a producer with a different contract, fails `tsc` in seconds instead of failing a deploy.

Two rules follow from this design and shape everything else:

1. **Your code never reads its environment.** No `process.env`, no hardcoded URLs. Every dependency and config value arrives typed, through injection.
2. **Composer never bundles or transforms your code.** You build with your own tooling; the deploy assembles what you built.

## The three node kinds [#the-three-node-kinds]

A Composer application is a tree of nodes. There are three kinds, and each answers a different question:

| Kind         | What it is                                                               | Declared with         |
| ------------ | ------------------------------------------------------------------------ | --------------------- |
| **Service**  | A unit that runs your code: an API, a web app, a worker                  | `compute()`           |
| **Resource** | A stateful dependency the platform manages for you: a database, a bucket | `postgres()`, storage |
| **Module**   | A boundary that groups services and resources and exposes typed ports    | `module()`            |

A service is atomic: Composer sees its ports and nothing inside. A Module is the opposite: it runs no code of its own; its behaviour is the composition of what it wraps. A Module owns its internals, so a consumer can use its exposed port but can never reach the database inside it.

The **App** is not a fourth kind. It is the outermost Module, the one whose file you hand to the CLI. It wires the top-level pieces together, and deploying it deploys everything it contains. On the platform, the App becomes one Prisma [project](https://www.prisma.io/docs/compute#the-model): services land on [Prisma Compute](https://www.prisma.io/docs/compute) and databases on [Prisma Postgres](https://www.prisma.io/docs/postgres).

What is deliberately not a node: plain values. An API key, a region name, or a feature flag has no lifecycle to manage, so it is **configuration**, and it travels through a different channel ([below](#two-channels-dependencies-and-input)). The test for whether something is a node is whether the deploy can create, update, and destroy it.

See [Apps and Modules](https://www.prisma.io/docs/composer/apps-and-modules) for the full authoring surface.

## Ports and wiring [#ports-and-wiring]

Every node has typed connection points, called ports. A `deps` entry is a port where the node requires something; an `expose` entry is a port where it offers something. Wiring means connecting a required port to an offered one that satisfies it, and it happens in exactly one place: a Module's builder function, using `provision()`:

```ts title="module.ts"
import { module } from '@prisma/composer';
import catalogModule from './src/catalog/module.ts';
import ordersModule from './src/orders/module.ts';
import storefrontService from './src/storefront/service.ts';

export default module('store', ({ provision }) => {
  const catalog = provision(catalogModule);
  const orders = provision(ordersModule, { deps: { catalog: catalog.rpc } });
  provision(storefrontService, { deps: { catalog: catalog.rpc, orders: orders.rpc } });
});
```

`provision()` places a node in the graph and returns a ref carrying one port per exposed contract. Because ports are typed, the compiler verifies every wire: each declared dependency must be bound, and bound to a producer whose contract matches. The wired graph as a whole is the application's **topology**, and the deploy prints it as a tree with dotted addresses (`auth.service`), so the structure you author is the structure you operate.

## Two channels: dependencies and input [#two-channels-dependencies-and-input]

A service receives things from outside through two deliberately separate channels, with two separate accessors in your code.

**Dependencies** are live connections to other nodes: another service's API, a database. They are declared in `deps` and arrive in code through `service.load()`, already typed and already authenticated:

```ts title="src/storefront/data.ts"
import service from './service.ts';

const { catalog } = service.load();
const { products } = await catalog.listProducts({});
```

**Input** is configuration: plain values and credentials, declared together as one [Standard Schema](https://standardschema.dev) and read through `service.input()`. The service declares what shapes are legal; the place that provisions it decides where each value comes from. There are three bindings:

1. A **literal**: the value is written at the provision site and travels with the deployment.
2. **`envParam(NAME)`**: the value is read from a named platform variable per environment, so the same app reads different values in production and in a staging copy.
3. **`envSecret(NAME)`**: like `envParam`, but the value is a credential: redacted in output and never stored in deploy state. This is the only thing Composer calls a secret.

The split is what makes environments cheap: production, a staging stage, a local run, and a test all execute the same code with different injected values. See [Service input](https://www.prisma.io/docs/composer/service-input).

## Contracts [#contracts]

A **contract** is the typed interface through which services communicate. You write it once as a set of method signatures with Standard Schema message types (arktype, zod, and valibot all work), then both sides use it: the providing service serves it, and a consuming service declares `rpc(contract)` as a dependency and gets a typed client from `service.load()`. On the wire, calls are RPC over HTTP, authenticated with per-consumer service keys Composer provisions for you, and retried safely. See [Services and contracts](https://www.prisma.io/docs/composer/services-and-contracts).

Databases have a contract story of their own: a `postgres()` resource is typed by a [Prisma 8 data contract](https://www.prisma.io/docs/orm/contract-authoring/the-data-contract), and its migrations run as part of the deploy. Every database belongs to exactly one Module, which is what makes migration ownership unambiguous: the Module that owns the database owns its schema. See [Databases](https://www.prisma.io/docs/composer/databases).

## The deploy model [#the-deploy-model]

A deploy is not a script of steps you wrote; it is a comparison. `deploy` loads your root module, compares the topology it describes with the **deploy state** recorded for that environment, and applies only the difference: create what is new, update what changed, leave the rest alone. Re-running a deploy with nothing changed is a no-op, and re-running after a failure converges what the deploy state already tracks rather than starting over. One edge sits outside that guarantee: if an environment's very first deploy fails before its state is written, resources created up to that point are untracked, and neither a retry nor a destroy will manage them.

  

#### bun

```bash
bunx prisma@latest deploy module.ts
```

#### pnpm

```bash
pnpm dlx prisma@latest deploy module.ts
```

#### yarn

```bash
yarn dlx prisma@latest deploy module.ts
```

#### npm

```bash
npx prisma@latest deploy module.ts
```

Two consequences of convergence are worth naming. Removing a node from your module removes the deployed thing on the next deploy, because the topology is the source of truth. And tearing a whole environment down works through the same mechanism, via the `destroy` operation in `@prisma/composer/control`. See [Deploying](https://www.prisma.io/docs/composer/deploying) and the [`deploy` reference](https://www.prisma.io/docs/cli/deploy).

## Stages [#stages]

A **stage** is an environment name chosen at deploy time, never written in the topology. The same graph deploys anywhere:

  

#### bun

```bash
bunx prisma@latest deploy module.ts                  # production
bunx prisma@latest deploy module.ts --stage staging  # a persistent staging environment
bunx prisma@latest deploy module.ts --stage pr-42    # one environment per PR
```

#### pnpm

```bash
pnpm dlx prisma@latest deploy module.ts                  # production
pnpm dlx prisma@latest deploy module.ts --stage staging  # a persistent staging environment
pnpm dlx prisma@latest deploy module.ts --stage pr-42    # one environment per PR
```

#### yarn

```bash
yarn dlx prisma@latest deploy module.ts                  # production
yarn dlx prisma@latest deploy module.ts --stage staging  # a persistent staging environment
yarn dlx prisma@latest deploy module.ts --stage pr-42    # one environment per PR
```

#### npm

```bash
npx prisma@latest deploy module.ts                  # production
npx prisma@latest deploy module.ts --stage staging  # a persistent staging environment
npx prisma@latest deploy module.ts --stage pr-42    # one environment per PR
```

Deploying with no `--stage` targets production, which lives at the project level. A named stage is a complete, isolated copy of the app, deployed as a [preview branch](https://www.prisma.io/docs/compute/branching) of the same project: its own services, its own databases, its own configuration. The only thing a stage shares with production is the code, which is exactly what the two-channel design promises: same topology, different injected values.

## Local development [#local-development]

`dev` brings the whole app up on your machine by running the same deploy pipeline against local emulators standing in for Prisma Compute and Prisma Postgres. No credentials, no network. Your services, databases, and wiring are real; only the providers underneath are swapped, so behaviour like service-key authentication works locally exactly as it does deployed:

  

#### bun

```bash
bunx prisma@latest dev module.ts
```

#### pnpm

```bash
pnpm dlx prisma@latest dev module.ts
```

#### yarn

```bash
yarn dlx prisma@latest dev module.ts
```

#### npm

```bash
npx prisma@latest dev module.ts
```

See [Local development](https://www.prisma.io/docs/composer/local-development) for what persists between runs and the [`dev` reference](https://www.prisma.io/docs/cli/dev).

## Building blocks and extensions [#building-blocks-and-extensions]

A Module is also the unit of reuse. A **building block** is a ready-made Module that ships with the framework and composes in a couple of lines: `cron` for scheduled jobs, `storage` for S3-backed blobs, and `streams` for durable event streams. Because a block is an ordinary Module, wiring one in is the same `provision()` call you use for your own.

An **extension** is a package that brings its own Modules, resources, or deploy target, published under the `prisma-composer-*` naming convention. The first-party blocks above are the whole catalogue today. See [Building blocks](https://www.prisma.io/docs/composer/building-blocks) and [Object storage](https://www.prisma.io/docs/composer/object-storage).

## Testing [#testing]

Because your code receives everything through `service.load()` and `service.input()`, a test is another environment: one where you decide what those calls return. `mockService` substitutes a fake for a unit test; `bootstrapService` hydrates a service with real local dependencies for an integration test. Nothing is provisioned and no code changes. See [Testing](https://www.prisma.io/docs/composer/testing).

## Next steps [#next-steps]

* [Apps and Modules](https://www.prisma.io/docs/composer/apps-and-modules): the authoring surface behind the node kinds and `provision()`.
* [Getting started](https://www.prisma.io/docs/composer/getting-started): build and run a two-service app from an empty directory.
* [Limitations](https://www.prisma.io/docs/composer/limitations): what Composer does not do yet, before you commit to a design.

## 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.
- [`Databases`](https://www.prisma.io/docs/composer/databases): Give a service a Postgres database, either as a plain connection or typed by a Prisma 8 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.
- [`Getting started`](https://www.prisma.io/docs/composer/getting-started): Build a two-service Prisma App from an empty directory and run it on your machine with one command.