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.
Creating a Resolvers Controller
A resolvers controller is a class decorated with @Resolvers()
that contains methods decorated with @Query()
or @Mutation()
to define GraphQL operations:
import { Mutation, Query, Resolvers, createQuery, createMutation } from '@orion-js/graphql'
import { Inject } from '@orion-js/services'
import { schemaWithName, InferSchemaType } from '@orion-js/schema'
import { ExampleService } from '../services/ExampleService'
// Define schemas with schemaWithName
export const ExampleParams = schemaWithName('ExampleParams', {
exampleId: { type: String }
})
export const ExampleSchema = schemaWithName('ExampleSchema', {
_id: { type: String },
name: { type: String },
createdAt: { type: Date }
})
// Infer types from schemas
export type ExampleParamsType = InferSchemaType<typeof ExampleParams>
export type ExampleSchemaType = InferSchemaType<typeof ExampleSchema>
@Resolvers()
export default class ExampleResolvers {
@Inject(() => ExampleService)
private exampleService: ExampleService
@Query()
example = createQuery({
params: ExampleParams,
returns: ExampleSchema,
resolve: async (params: ExampleParamsType) => {
return await this.exampleService.getAExample(params.exampleId)
}
})
@Query()
examples = createQuery({
returns: [ExampleSchema],
resolve: async () => {
return await this.exampleService.getExamples()
}
})
@Mutation()
createExample = createMutation({
returns: String,
resolve: async () => {
await this.exampleService.makeExample()
return 'Created example'
}
})
}
Query Resolvers
Use the @Query()
decorator with the createQuery()
function to define GraphQL query operations:
@Query()
user = createQuery({
params: UserParams,
returns: UserSchema,
resolve: async (params) => {
return await this.userService.getUser(params.userId)
}
})
Query Decorator Options
The @Query()
decorator accepts the following options:
@Query({
name: 'findUser', // Custom name for the GraphQL query (defaults to method name)
description: 'Find a user by ID' // Description for GraphQL schema documentation
})
Mutation Resolvers
Use the @Mutation()
decorator with the createMutation()
function to define GraphQL mutation operations:
@Mutation()
createUser = createMutation({
params: CreateUserParams,
returns: UserSchema,
resolve: async (params) => {
return await this.userService.createUser(params)
}
})
Mutation Decorator Options
The @Mutation()
decorator accepts the same options as @Query()
:
@Mutation({
name: 'registerUser', // Custom name for the GraphQL mutation
description: 'Register a new user' // Description for GraphQL schema documentation
})
Defining Parameters
Define parameters using the schemaWithName
function:
export const UserParams = schemaWithName('UserParams', {
userId: { type: String, required: true }
})
export type UserParamsType = InferSchemaType<typeof UserParams>
@Query()
user = createQuery({
params: UserParams,
returns: UserSchema,
resolve: async (params: UserParamsType) => {
return await this.userService.getUser(params.userId)
}
})
Nested Parameters
You can create complex parameter structures:
export const AddressInput = schemaWithName('AddressInput', {
street: { type: String, required: true },
city: { type: String, required: true },
zipCode: { type: String, required: true }
})
export const CreateUserParams = schemaWithName('CreateUserParams', {
name: { type: String, required: true },
email: { type: String, required: true },
address: { type: AddressInput }
})
Defining Return Types
Specify the return type in the returns
property of createQuery
or createMutation
:
// Return a single item
@Query()
user = createQuery({
params: UserParams,
returns: UserSchema,
resolve: async (params) => {
// ...
}
})
// Return an array of items
@Query()
users = createQuery({
returns: [UserSchema],
resolve: async () => {
// ...
}
})
// Return primitive types
@Query()
getMessage = createQuery({
returns: String,
resolve: async () => {
// ...
}
})
@Query()
checkAvailability = createQuery({
returns: Boolean,
resolve: async () => {
// ...
}
})
@Query()
countUsers = createQuery({
returns: Number,
resolve: async () => {
// ...
}
})
Middleware
You can add middleware to your resolvers using the @UseMiddleware()
decorator:
import { UseMiddleware } from '@orion-js/graphql'
import { authMiddleware } from '../middleware/auth'
@Query()
@UseMiddleware(authMiddleware)
me = createQuery({
returns: UserSchema,
resolve: async (_, viewer) => {
// viewer.user is available because of authMiddleware
return viewer.user
}
})
Middleware Chaining
You can apply multiple middlewares to a resolver:
@Query()
@UseMiddleware(authMiddleware)
@UseMiddleware(logMiddleware)
@UseMiddleware(rateLimit(100))
protectedResource = createQuery({
// ...
})
Model Resolvers
For defining field resolvers on specific GraphQL types, see the Model Resolvers documentation.
Subscription Resolvers
For real-time functionality, use the @Subscriptions()
and @Subscription()
decorators:
import { Subscription, Subscriptions, createSubscription } from '@orion-js/graphql'
import { Inject } from '@orion-js/services'
import { UserService } from '../services/UserService'
@Subscriptions()
export default class UserSubscriptions {
@Inject(() => UserService)
private userService: UserService
@Subscription()
userCreated = createSubscription({
params: ParamsSchema,
returns: UserSchema,
description: 'Triggers when a user is created',
async canSubscribe(params) {
// Check if subscription is allowed
return true
}
})
}
Error Handling
Orionjs automatically handles errors in resolvers and formats them appropriately:
@Query()
riskyOperation = createQuery({
returns: String,
resolve: async () => {
try {
const result = await this.someService.performOperation()
return result
} catch (error) {
// Custom error handling
throw new Error(`Operation failed: ${error.message}`)
}
}
})
Context and Viewer
All resolver methods receive the viewer object as the second parameter, which contains the authenticated user and other context information:
@Query()
userProfile = createQuery({
returns: UserProfile,
resolve: async (_, viewer) => {
// Check if user is authenticated
if (!viewer.user) {
throw new Error('Unauthorized')
}
return await this.userService.getProfile(viewer.user._id)
}
})
GraphQL Info
You can access the GraphQL info object to optimize your queries:
@Query()
complexData = createQuery({
returns: ComplexData,
resolve: async (params, viewer, info) => {
// Use info to determine which fields were requested
const requestedFields = extractFieldsFromInfo(info)
// Only fetch the data that was requested
return await this.dataService.getOptimizedData(requestedFields)
}
})
Starting GraphQL Server
To start the GraphQL server:
import { startGraphQL, startGraphiQL } from '@orion-js/graphql'
import { app } from '@orion-js/http'
// Start GraphQL server
startGraphQL({
path: '/graphql'
})
// Optional: Start GraphiQL (GraphQL IDE)
startGraphiQL({
path: '/graphiql',
graphqlUrl: '/graphql'
})
Best Practices
-
Organize by Domain: Group related resolvers in the same controller class.
-
Leverage Dependency Injection: Use @Inject(() => Service)
to access services.
-
Keep Resolvers Focused: Each resolver method should handle one specific GraphQL operation.
-
Use Strong Typing: Define parameter and return types using schema and InferSchemaType
.
-
Validate Input: The schema system automatically validates input.
-
Implement Authorization: Use middleware for authentication and authorization.
-
Handle Errors Gracefully: Catch and handle errors appropriately.
-
Optimize Queries: Use the info parameter to optimize database queries.
-
Document Your API: Add descriptions to your schemas and resolvers.
-
Test Your Resolvers: Write unit tests for your resolver logic.