Skip to main content
Orionjs provides a clean, decorator-based approach to define GraphQL resolvers. The @Resolvers() decorator along with @Query(), @Mutation(), and other decorators make it easy to create type-safe GraphQL APIs with minimal boilerplate.

Structure and Naming

  • Use @Resolvers() decorator from @orion-js/graphql
  • Each resolver class should handle only one operation
  • Follow naming conventions:
    • Resolver classes: {Action}{Entity}Resolvers (e.g., GetUserResolvers, UpdateProductResolvers)
    • Mutations: descriptive of the operation (e.g., listProducts, createUser)
    • Queries: only the name of the entity (e.g., user, productsList, blogPosts)
  • Put resolvers in app/{component}/controllers/resolvers/{Entity}/{Action}/index.ts

File Organization

The resolvers folder should have an index.ts that exports all resolvers as a single array:
controllers/
└── resolvers/
    ├── users/
    │   ├── GetUser/index.ts
    │   └── CreateUser/index.ts
    ├── orders/
    │   ├── GetOrder/index.ts
    │   └── CreateOrder/index.ts
    └── index.ts  # Exports all resolvers as a single array
The controllers/resolvers/index.ts file:
import {GetUserResolvers} from './users/GetUser'
import {CreateUserResolvers} from './users/CreateUser'
import {GetOrderResolvers} from './orders/GetOrder'
import {CreateOrderResolvers} from './orders/CreateOrder'

export default [
  GetUserResolvers,
  CreateUserResolvers,
  GetOrderResolvers,
  CreateOrderResolvers
]

Best Practices

  • Use @Inject(() => ServiceName) for dependency injection of services
  • Always define proper schemas for parameters and return values
  • Use createQuery(), createMutation() and createModelResolver() functions
  • Provide descriptive names and descriptions for GraphQL schema documentation
  • Always use schemaWithName() for schemas that will be used in GraphQL
  • When creating a schema for params, prefer to clone another schema with cloneSchema()

Query Example

import {Query, Resolvers, createQuery} from '@orion-js/graphql'
import {Inject} from '@orion-js/services'
import {schemaWithName} from '@orion-js/schema'
import {WebsiteConfigsRepo} from 'app/providers/repos/WebsiteConfigs'
import {WebsiteConfigSchema} from 'app/providers/schemas/WebsiteConfig'

const WebsiteConfigParams = schemaWithName('WebsiteConfigParams', {
  websiteId: {type: String}
})

@Resolvers()
export class WebsiteConfigResolvers {
  @Inject(() => WebsiteConfigsRepo)
  private websiteConfigsRepo: WebsiteConfigsRepo

  @Query()
  websiteConfig = createQuery({
    params: WebsiteConfigParams,
    returns: WebsiteConfigSchema,
    resolve: async params => {
      return await this.websiteConfigsRepo.getWebsiteConfigByWebsiteId(params.websiteId)
    }
  })
}

Mutation with cloneSchema

Use cloneSchema to create parameter schemas based on existing schemas:
import {Mutation, Resolvers, createMutation} from '@orion-js/graphql'
import {Inject} from '@orion-js/services'
import {schemaWithName, cloneSchema} from '@orion-js/schema'
import {WebsiteConfigsRepo} from 'app/providers/repos/WebsiteConfigs'
import {WebsiteConfigSchema} from 'app/providers/schemas/WebsiteConfig'

// Define a schema with only the fields we want to update
const UpdatesSchema = cloneSchema({
  name: 'WebsiteConfigUpdates',
  schema: WebsiteConfigSchema,
  pickFields: ['name', 'description'],
})

const UpdateWebsiteConfigParams = schemaWithName('UpdateWebsiteConfigParams', {
  websiteId: {type: String},
  updates: {type: UpdatesSchema}
})

@Resolvers()
export class UpdateWebsiteConfigResolvers {
  @Inject(() => WebsiteConfigsRepo)
  private websiteConfigsRepo: WebsiteConfigsRepo

  @Mutation()
  updateWebsiteConfig = createMutation({
    params: UpdateWebsiteConfigParams,
    returns: WebsiteConfigSchema,
    resolve: async params => {
      return await this.websiteConfigsRepo.updateWebsiteConfig(params.websiteId, params.updates)
    }
  })
}

Simple Mutation with cloneSchema

import {Mutation, Resolvers, createMutation} from '@orion-js/graphql'
import {Inject} from '@orion-js/services'
import {cloneSchema} from '@orion-js/schema'
import {WebsiteConfigsRepo} from 'app/providers/repos/WebsiteConfigs'
import {WebsiteConfigSchema} from 'app/providers/schemas/WebsiteConfig'

const SetWebsiteConfigNameParams = cloneSchema({
  name: 'SetWebsiteConfigNameParams',
  schema: WebsiteConfigSchema,
  pickFields: ['websiteId', 'name']
})

@Resolvers()
export class SetWebsiteConfigNameResolvers {
  @Inject(() => WebsiteConfigsRepo)
  private websiteConfigsRepo: WebsiteConfigsRepo

  @Mutation()
  setWebsiteConfigName = createMutation({
    params: SetWebsiteConfigNameParams,
    returns: WebsiteConfigSchema,
    resolve: async params => {
      return await this.websiteConfigsRepo.updateWebsiteConfig(params.websiteId, {name: params.name})
    }
  })
}

Mutation Using a Service

import {Mutation, Resolvers, createMutation} from '@orion-js/graphql'
import {Inject} from '@orion-js/services'
import {schemaWithName} from '@orion-js/schema'
import {SendToInboxService} from 'app/services/SendToInbox'

const SendToInboxParams = schemaWithName('SendToInboxParams', {
  email: {type: String},
  message: {type: String}
})

@Resolvers()
export class SendToInboxResolvers {
  @Inject(() => SendToInboxService)
  private sendToInboxService: SendToInboxService

  @Mutation()
  sendToInbox = createMutation({
    params: SendToInboxParams,
    returns: String,
    resolve: async params => {
      return await this.sendToInboxService.execute(params)
    }
  })
}

Model Resolver

import {ModelResolver, ModelResolvers} from '@orion-js/graphql'
import {InferSchemaType} from '@orion-js/schema'
import {createModelResolver} from '@orion-js/resolvers'
import {PersonSchema} from 'app/providers/schemas/Person'

type Person = InferSchemaType<typeof PersonSchema>

@ModelResolvers(PersonSchema)
export class PersonResolvers {
  @ModelResolver()
  sayHi = createModelResolver<Person>({
    returns: String,
    resolve: async person => {
      return `My name is ${person.name}`
    }
  })
}

Paginated Query

import {Query, Resolvers} from '@orion-js/graphql'
import {Inject} from '@orion-js/services'
import {createPaginatedResolver} from '@orion-js/paginated-mongodb'
import {ProductsRepo} from 'app/providers/repos/Products'
import {ProductSchema} from 'app/providers/schemas/Product'
import {ProductsListParams} from 'app/providers/schemas/params/ProductsList'

@Resolvers()
export class ProductsListResolvers {
  @Inject(() => ProductsRepo)
  private productsRepo: ProductsRepo

  @Query()
  productsList = createPaginatedResolver({
    returns: ProductSchema,
    params: ProductsListParams,
    allowedSorts: ['createdAt', 'name'],
    getCursor: async (params) => {
      return {
        cursor: this.productsRepo.loadProductsListCursor(params),
        count: () => this.productsRepo.loadProductsListCount(params)
      }
    }
  })
}

Complex cloneSchema for Params

import {cloneSchema, InferSchemaType} from '@orion-js/schema'
import {ArticleSchema} from '../Article'

export const ArticlesListParamsSchema = cloneSchema({
  name: 'ArticlesListParams',
  schema: ArticleSchema,
  extendSchema: {
    filter: {
      type: String,
      optional: true,
    },
  },
  pickFields: ['websiteId', 'type'],
})

export type ArticlesListParamsType = InferSchemaType<typeof ArticlesListParamsSchema>

Subscription Resolvers

For real-time functionality:
import {Subscription, Subscriptions} from '@orion-js/graphql'
import {createSubscription} from '@orion-js/subscriptions'

@Subscriptions()
export class UserSubscriptions {
  @Subscription()
  userCreated = createSubscription({
    params: ParamsSchema,
    returns: UserSchema,
    async canSubscribe(params) {
      return params.name === 'test'
    }
  })
}

Error Handling

Orionjs automatically handles errors in resolvers:
@Query()
riskyOperation = createQuery({
  returns: String,
  resolve: async () => {
    try {
      const result = await this.someService.performOperation()
      return result
    } catch (error) {
      throw new Error(`Operation failed: ${error.message}`)
    }
  }
})

Context and Viewer

All resolver methods receive the viewer object as the second parameter:
@Query()
userProfile = createQuery({
  returns: UserProfile,
  resolve: async (_, viewer) => {
    if (!viewer.user) {
      throw new Error('Unauthorized')
    }

    return await this.userService.getProfile(viewer.user._id)
  }
})

Starting GraphQL Server

import {startGraphQL, startGraphiQL} from '@orion-js/graphql'

startGraphQL({
  path: '/graphql'
})

// Optional: Start GraphiQL (GraphQL IDE)
startGraphiQL({
  path: '/graphiql',
  graphqlUrl: '/graphql'
})

Query vs Mutation

  • Use @Query() for operations that fetch data without side effects
  • Use @Mutation() for operations that create, update, or delete data