Skip to main content
Orionjs provides seamless integration with tRPC for building end-to-end type-safe APIs. The @orion-js/trpc package offers decorators and utilities that work with Orionjs schemas and the service injection system.

Installation

pnpm add @orion-js/trpc @trpc/server

Key Features

  • Full TypeScript type inference
  • Integration with Orionjs schema validation
  • Service injection support via @Inject()
  • Compatible with the Orionjs component system

Basic Setup

1. Create Procedures

Create a procedures class using the @Procedures() decorator:
// app/exampleComponent/controllers/trpc/ExampleProcedures/index.ts
import {Procedures, TQuery, TMutation, createTQuery, createTMutation} from '@orion-js/trpc'
import {Inject} from '@orion-js/services'
import {ExampleSchema} from 'app/exampleComponent/schemas/ExampleSchema'
import {ExampleService} from 'app/exampleComponent/services/ExampleService'

@Procedures()
export class ExampleProcedures {
  @Inject(() => ExampleService)
  private exampleService: ExampleService

  @TQuery()
  getExample = createTQuery({
    params: {exampleId: {type: String}},
    returns: ExampleSchema,
    resolve: async ({exampleId}) => {
      return await this.exampleService.getExample(exampleId)
    },
  })

  @TQuery()
  listExamples = createTQuery({
    returns: [ExampleSchema],
    resolve: async () => {
      return await this.exampleService.getExamples()
    },
  })

  @TMutation()
  createExample = createTMutation({
    params: {name: {type: String}},
    returns: {message: {type: String}},
    resolve: async ({name}) => {
      await this.exampleService.createExample(name)
      return {message: 'Created successfully'}
    },
  })
}

2. Create a Typed Router

Create a router file that exports the typed router:
// app/router.ts
import {buildRouter, mergeProcedures} from '@orion-js/trpc'
import {ExampleProcedures} from './exampleComponent/controllers/trpc/ExampleProcedures'
import {UserProcedures} from './userComponent/controllers/trpc/UserProcedures'

// Merge all procedure classes with preserved types
const procedures = mergeProcedures([ExampleProcedures, UserProcedures])

// Build the router
export const appRouter = buildRouter(procedures)

// Export the router type
export type AppRouter = typeof appRouter

3. Start the tRPC Server

// app/config/trpc/index.ts
import {startTRPC} from '@orion-js/trpc'
import {logger} from '@orion-js/logger'
import {appRouter} from 'app/router'

export default async function startTrpc() {
  await startTRPC({
    router: appRouter,
    path: '/trpc',
  })

  logger.info('tRPC started at /trpc')

  return {router: appRouter}
}

Decorators Reference

@Procedures()

Marks a class as a tRPC procedures container. This decorator also applies @Service() for dependency injection.
@Procedures()
export class MyProcedures {
  // procedures here
}

@TQuery()

Marks a field as a tRPC query procedure.
@TQuery()
myQuery = createTQuery({
  params: {...},
  returns: {...},
  resolve: async (params) => {...}
})

@TMutation()

Marks a field as a tRPC mutation procedure.
@TMutation()
myMutation = createTMutation({
  params: {...},
  returns: {...},
  resolve: async (params) => {...}
})

Procedure Options

Both createTQuery and createTMutation accept the same options:
OptionTypeDescription
paramsSchemaInput validation schema (optional)
returnsSchemaOutput schema for documentation (optional)
resolveFunctionThe resolver function

Schema Types

You can use Orionjs schema definitions:
// Inline schema
createTQuery({
  params: {
    userId: {type: String},
    includeDetails: {type: Boolean, optional: true}
  },
  returns: UserSchema,
  resolve: async ({userId, includeDetails}) => {...}
})

// Array returns
createTQuery({
  returns: [UserSchema],
  resolve: async () => {...}
})

Merging Procedures

Use mergeProcedures to combine multiple procedure classes while preserving types:
import {mergeProcedures} from '@orion-js/trpc'

// Supports up to 5 procedure classes with full type inference
const procedures = mergeProcedures([
  UserProcedures,
  PostProcedures,
  CommentProcedures,
])

Server-Side Caller

Create a server-side caller for testing or internal use:
const {router} = await startApp([...components])

const caller = router.createCaller({})

// Call procedures directly
const example = await caller.getExample({exampleId: '123'})

startTRPC Options

import {startTRPC} from '@orion-js/trpc'
import {appRouter} from 'app/router'

await startTRPC({
  router: appRouter,        // Required: your tRPC router
  path: '/trpc',            // Optional: endpoint path (default: '/trpc')
  bodyParserOptions: {      // Optional: body parser config
    limit: '10mb'
  }
})

Context and Viewer

The tRPC context includes the Orionjs viewer:
createTQuery({
  resolve: async (params, viewer) => {
    if (!viewer.user) {
      throw new Error('Unauthorized')
    }
    return await this.service.getData(viewer.user._id)
  }
})

Error Handling

Throw errors normally - they’ll be properly serialized by tRPC:
import {TRPCError} from '@trpc/server'

createTMutation({
  resolve: async (params) => {
    const item = await this.repo.findOne(params.id)

    if (!item) {
      throw new TRPCError({
        code: 'NOT_FOUND',
        message: 'Item not found'
      })
    }

    return item
  }
})

File Organization

Recommended structure for tRPC procedures:
app/
└── exampleComponent/
    └── controllers/
        └── trpc/
            ├── ExampleProcedures/
            │   └── index.ts
            └── index.ts  # Exports procedure classes as array
The controllers/trpc/index.ts:
import {ExampleProcedures} from './ExampleProcedures'

export default [ExampleProcedures]