Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ You can find the full documentation [here](https://orpc.unnoq.com).
- [@orpc/contract](https://www.npmjs.com/package/@orpc/contract): Build your API contract.
- [@orpc/server](https://www.npmjs.com/package/@orpc/server): Build your API or implement API contract.
- [@orpc/client](https://www.npmjs.com/package/@orpc/client): Consume your API on the client with type-safety.
- [@orpc/nest](https://www.npmjs.com/package/@orpc/nest): Deeply integrate oRPC with NestJS.
- [@orpc/react](https://www.npmjs.com/package/@orpc/react): Utilities for integrating oRPC with React and React Server Actions.
- [@orpc/react-query](https://www.npmjs.com/package/@orpc/react-query): Integration with [React Query](https://tanstack.com/query/latest/docs/framework/react/overview).
- [@orpc/vue-query](https://www.npmjs.com/package/@orpc/vue-query): Integration with [Vue Query](https://tanstack.com/query/latest/docs/framework/vue/overview).
Expand Down
7 changes: 7 additions & 0 deletions apps/content/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,13 @@ export default defineConfig({
{ text: 'OpenAPI Link', link: '/docs/openapi/client/openapi-link' },
],
},
{
text: 'NestJS',
collapsed: true,
items: [
{ text: 'Implement Contract', link: '/docs/openapi/nest/implement-contract' },
],
},
{
text: 'Advanced',
collapsed: true,
Expand Down
246 changes: 246 additions & 0 deletions apps/content/docs/openapi/nest/implement-contract.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
---
title: Implement Contract in NestJS
description: Seamlessly implement oRPC contracts in your NestJS applications.
---

# Implement Contract in NestJS

This guide explains how to easily implement [oRPC contract](/docs/contract-first/define-contract) within your [NestJS](https://nestjs.com/) application using `@orpc/nest`.

::: warning
This feature is currently experimental and may be subject to breaking changes.
:::

## Installation

::: code-group

```sh [npm]
npm install @orpc/nest@latest
```

```sh [yarn]
yarn add @orpc/nest@latest
```

```sh [pnpm]
pnpm add @orpc/nest@latest
```

```sh [bun]
bun add @orpc/nest@latest
```

```sh [deno]
deno install npm:@orpc/nest@latest
```

:::

## Requirements

oRPC is an ESM-only library. Therefore, your NestJS application must be configured to support ESM modules.

1. **Configure `tsconfig.json`**: with `"module": "NodeNext"` or a similar ESM-compatible option.

```json
{
"compilerOptions": {
"module": "NodeNext", // <-- this is recommended
"strict": true // <-- this is recommended
// ... other options,
}
}
```

2. **Node.js Environment**:

- **Node.js 22+**: Recommended, as it allows `require()` of ESM modules natively.
- **Older Node.js versions**: Alternatively, use a bundler to compile ESM modules (including `@orpc/nest`) to CommonJS.

::: warning
By default, NestJS bundler ([Webpack](https://webpack.js.org/) or [SWC](https://swc.rs/)) might not compile `node_modules`. You may need to adjust your bundler configs to include `@orpc/nest` for compilation.
:::

## Define Your Contract

Before implementation, define your oRPC contract. This process is consistent with the standard oRPC methodology. For detailed guidance, refer to the main [Contract-First guide](/docs/contract-first/define-contract).

::: details Example Contract

```ts
import { populateContractRouterPaths } from '@orpc/nest'
import { oc } from '@orpc/contract'
import { z } from 'zod'

export const PlanetSchema = z.object({
id: z.number().int().min(1),
name: z.string(),
description: z.string().optional(),
})

export const listPlanetContract = oc
.route({
method: 'GET',
path: '/planets' // Path is required for NestJS implementation
})
.input(
z.object({
limit: z.number().int().min(1).max(100).optional(),
cursor: z.number().int().min(0).default(0),
}),
)
.output(z.array(PlanetSchema))

export const findPlanetContract = oc
.route({
method: 'GET',
path: '/planets/{id}' // Path is required
})
.input(PlanetSchema.pick({ id: true }))
.output(PlanetSchema)

export const createPlanetContract = oc
.route({
method: 'POST',
path: '/planets' // Path is required
})
.input(PlanetSchema.omit({ id: true }))
.output(PlanetSchema)

/**
* populateContractRouterPaths is completely optional,
* because the procedure's path is required for NestJS implementation.
* This utility automatically populates any missing paths
* Using the router's keys + `/`.
*/
export const contract = populateContractRouterPaths({
planet: {
list: listPlanetContract,
find: findPlanetContract,
create: createPlanetContract,
},
})
```

:::

::: warning
For a contract to be implementable in NestJS using `@orpc/nest`, each contract **must** define a `path` in its `.route`. Omitting it will cause a build‑time error.
You can avoid this by using the `populateContractRouterPaths` utility to automatically fill in any missing paths.
:::

## Path Parameters

Aside from [oRPC Path Parameters](/docs/openapi/routing#path-parameters), regular NestJS route patterns still work out of the box. However, they are not standard in OpenAPI, so we recommend using oRPC Path Parameters exclusively.

::: warning
[oRPC Path Parameter matching with slashes (/)](/docs/openapi/routing#path-parameters) does not work on the NestJS Fastify platform, because Fastify does not allow wildcard (`*`) aliasing in path parameters.
:::

## Implement Your Contract

```ts
import { Implement, implement, ORPCError } from '@orpc/nest'

@Controller()
export class PlanetController {
/**
* Implement a standalone procedure
*/
@Implement(contract.planet.list)
list() {
return implement(contract.planet.list).handler(({ input }) => {
// Implement logic here

return []
})
}

/**
* Implement entire a contract
*/
@Implement(contract.planet)
planet() {
return {
list: implement(contract.planet.list).handler(({ input }) => {
// Implement logic here
return []
}),
find: implement(contract.planet.find).handler(({ input }) => {
// Implement logic here
return {
id: 1,
name: 'Earth',
description: 'The planet Earth',
}
}),
create: implement(contract.planet.create).handler(({ input }) => {
// Implement logic here
return {
id: 1,
name: 'Earth',
description: 'The planet Earth',
}
}),
}
}

// other handlers...
}
```

::: info
The `@Implement` decorator functions similarly to NestJS built-in HTTP method decorators (e.g., `@Get`, `@Post`). Handlers decorated with `@Implement` are standard NestJS controller handlers and can leverage all NestJS features.
:::

## Body Parser

By default, NestJS parses request bodies for `application/json` and `application/x-www-form-urlencoded` content types. However:

- NestJS `urlencoded` parser does not support [Bracket Notation](/docs/openapi/bracket-notation) like in standard oRPC parsers.
- In some edge cases like uploading a file with `application/json` content type, the NestJS parser does not treat it as a file, instead it parses the body as a JSON string.

Therefore, we **recommend** disabling the NestJS body parser:

```ts
import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'

async function bootstrap() {
const app = await NestFactory.create(AppModule, {
bodyParser: false, // [!code highlight]
})

await app.listen(process.env.PORT ?? 3000)
}
```

::: info
oRPC will use NestJS parsed body when it's available, and only use the oRPC parser if the body is not parsed by NestJS.
:::

## Create a Type-Safe Client

When you implement oRPC contracts in NestJS using `@orpc/nest`, the resulting API endpoints are OpenAPI compatible. This allows you to use an OpenAPI-compatible client link, such as [OpenAPILink](/docs/openapi/client/openapi-link), to interact with your API in a type-safe way.

```typescript
import type { JsonifiedClient } from '@orpc/openapi-client'
import type { ContractRouterClient } from '@orpc/contract'
import { createORPCClient } from '@orpc/client'
import { OpenAPILink } from '@orpc/openapi-client/fetch'

const link = new OpenAPILink(contract, {
url: 'http://localhost:3000',
headers: () => ({
'x-api-key': 'my-api-key',
}),
// fetch: <-- polyfill fetch if needed
})

const client: JsonifiedClient<ContractRouterClient<typeof contract>> = createORPCClient(link)
```

::: info
Please refer to the [OpenAPILink](/docs/openapi/client/openapi-link) documentation for more information on client setup and options.
:::
2 changes: 2 additions & 0 deletions apps/content/docs/playgrounds.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ featuring pre-configured examples accessible instantly via StackBlitz or local s
| Solid Start Playground | [Open in StackBlitz](https://stackblitz.com/github/unnoq/orpc/tree/main/playgrounds/solid-start) | [View Source](https://github.com/unnoq/orpc/tree/main/playgrounds/solid-start) |
| Svelte Kit Playground | [Open in StackBlitz](https://stackblitz.com/github/unnoq/orpc/tree/main/playgrounds/svelte-kit) | [View Source](https://github.com/unnoq/orpc/tree/main/playgrounds/svelte-kit) |
| Contract-First Playground | [Open in StackBlitz](https://stackblitz.com/github/unnoq/orpc/tree/main/playgrounds/contract-first) | [View Source](https://github.com/unnoq/orpc/tree/main/playgrounds/contract-first) |
| NestJS Playground | [Open in StackBlitz](https://stackblitz.com/github/unnoq/orpc/tree/main/playgrounds/nest) | [View Source](https://github.com/unnoq/orpc/tree/main/playgrounds/nest) |

:::warning
StackBlitz has own limitations, so some features may not work as expected.
Expand All @@ -32,6 +33,7 @@ npx degit unnoq/orpc/playgrounds/nuxt orpc-nuxt-playground
npx degit unnoq/orpc/playgrounds/solid-start orpc-solid-start-playground
npx degit unnoq/orpc/playgrounds/svelte-kit orpc-svelte-kit-playground
npx degit unnoq/orpc/playgrounds/contract-first orpc-contract-first-playground
npx degit unnoq/orpc/playgrounds/nest orpc-nest-playground
```

For each project, set up the development environment:
Expand Down
2 changes: 1 addition & 1 deletion apps/content/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ features:
details: Optionally define your API contract before implementation.
- icon: ⚙️
title: Framework Integrations
details: Seamlessly integrate with TanStack Query (React, Vue, Solid, Svelte), Pinia Colada, and more.
details: Seamlessly integrate with TanStack Query (React, Vue, Solid, Svelte), Pinia Colada, NestJS, and more.
- icon: 🚀
title: Server Actions
details: Fully compatible with React Server Actions on Next.js, TanStack Start, and other platforms.
Expand Down
6 changes: 6 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,10 @@ export default antfu({
rules: {
'no-alert': 'off',
},
}, {
files: ['playgrounds/nest/**'],
rules: {
'node/prefer-global/process': 'off',
'@typescript-eslint/consistent-type-imports': 'off',
},
})
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"build": "pnpm run -r build",
"build:packages": "pnpm --filter=\"./packages/*\" run -r build",
"preview": "pnpm run --parallel preview",
"type:check": "pnpm run -r type:check && tsc --noEmit",
"type:check": "pnpm run -r type:check && pnpm run -r type:check:test && tsc --noEmit",
"test": "vitest run",
"test:watch": "vitest watch",
"test:coverage": "vitest run --coverage --coverage.include='packages/*/src/**'",
Expand Down
1 change: 1 addition & 0 deletions packages/arktype/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ You can find the full documentation [here](https://orpc.unnoq.com).
- [@orpc/contract](https://www.npmjs.com/package/@orpc/contract): Build your API contract.
- [@orpc/server](https://www.npmjs.com/package/@orpc/server): Build your API or implement API contract.
- [@orpc/client](https://www.npmjs.com/package/@orpc/client): Consume your API on the client with type-safety.
- [@orpc/nest](https://www.npmjs.com/package/@orpc/nest): Deeply integrate oRPC with NestJS.
- [@orpc/react](https://www.npmjs.com/package/@orpc/react): Utilities for integrating oRPC with React and React Server Actions.
- [@orpc/react-query](https://www.npmjs.com/package/@orpc/react-query): Integration with [React Query](https://tanstack.com/query/latest/docs/framework/react/overview).
- [@orpc/vue-query](https://www.npmjs.com/package/@orpc/vue-query): Integration with [Vue Query](https://tanstack.com/query/latest/docs/framework/vue/overview).
Expand Down
1 change: 1 addition & 0 deletions packages/client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ You can find the full documentation [here](https://orpc.unnoq.com).
- [@orpc/contract](https://www.npmjs.com/package/@orpc/contract): Build your API contract.
- [@orpc/server](https://www.npmjs.com/package/@orpc/server): Build your API or implement API contract.
- [@orpc/client](https://www.npmjs.com/package/@orpc/client): Consume your API on the client with type-safety.
- [@orpc/nest](https://www.npmjs.com/package/@orpc/nest): Deeply integrate oRPC with NestJS.
- [@orpc/react](https://www.npmjs.com/package/@orpc/react): Utilities for integrating oRPC with React and React Server Actions.
- [@orpc/react-query](https://www.npmjs.com/package/@orpc/react-query): Integration with [React Query](https://tanstack.com/query/latest/docs/framework/react/overview).
- [@orpc/vue-query](https://www.npmjs.com/package/@orpc/vue-query): Integration with [Vue Query](https://tanstack.com/query/latest/docs/framework/vue/overview).
Expand Down
1 change: 1 addition & 0 deletions packages/contract/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ You can find the full documentation [here](https://orpc.unnoq.com).
- [@orpc/contract](https://www.npmjs.com/package/@orpc/contract): Build your API contract.
- [@orpc/server](https://www.npmjs.com/package/@orpc/server): Build your API or implement API contract.
- [@orpc/client](https://www.npmjs.com/package/@orpc/client): Consume your API on the client with type-safety.
- [@orpc/nest](https://www.npmjs.com/package/@orpc/nest): Deeply integrate oRPC with NestJS.
- [@orpc/react](https://www.npmjs.com/package/@orpc/react): Utilities for integrating oRPC with React and React Server Actions.
- [@orpc/react-query](https://www.npmjs.com/package/@orpc/react-query): Integration with [React Query](https://tanstack.com/query/latest/docs/framework/react/overview).
- [@orpc/vue-query](https://www.npmjs.com/package/@orpc/vue-query): Integration with [Vue Query](https://tanstack.com/query/latest/docs/framework/vue/overview).
Expand Down
26 changes: 26 additions & 0 deletions packages/nest/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Hidden folders and files
.*
!.gitignore
!.*.example

# Common generated folders
logs/
node_modules/
out/
dist/
dist-ssr/
build/
coverage/
temp/

# Common generated files
*.log
*.log.*
*.tsbuildinfo
*.vitest-temp.json
vite.config.ts.timestamp-*
vitest.config.ts.timestamp-*

# Common manual ignore files
*.local
*.pem
Loading