Navigation

Define a Custom Resolver

You can define custom resolvers that extend the GraphQL API for your app's use cases. Custom resolvers allow you to define new root-level operations that are more complex or specific than the generated query and mutation resolvers. You can also add new computed fields to generated document types that dynamically evaluate a result whenever an operation reads a document of the extended type.

1

In the Realm UI, click GraphQL in the navigation sidebar and then select the Custom Resolvers tab.

Click the Add a Custom Resolver button to open the configuration screen for a new custom resolver.

The custom resolvers screen in the Realm UI
2

Specify a descriptive name for the resolver in the GraphQL Field Name input. Realm exposes the custom resolver in its parent type using this name, so the name should describe what the resolver does in a way that is useful to developers who work with the GraphQL API.

The GraphQL Field Name input in the Realm UI
3

Realm exposes every custom resolver as a field on a parent type. The parent type can be a root-level query, mutation, or a generated document type.

In the Parent Type dropdown, select one of the following options:

OptionDescription
Query

The resolver is a root-level query operation:

Beaker IconExample

A custom resolver for a query named myCustomQuery has the following generated schema:

type Query {
myCustomQuery: DefaultPayload
...
}
Mutation

The resolver is a root-level mutation operation:

Beaker IconExample

A custom resolver for a mutation named myCustomMutation has the following generated schema:

type Mutation {
myCustomMutation: DefaultPayload
...
}
Document Type

The resolver is a computed property on the specified document type. Any query or mutation that returns the document type can also ask for computed properties defined by custom resolvers on the type.

Beaker IconExample

A custom resolver that defines a computed property on the Task type named myCustomTaskProperty has the following generated schema:

type Task {
myCustomTaskProperty: DefaultPayload
...
}
4

A custom resolver can accept input parameters from the incoming query or mutation. You can use an existing generated input type or define a new custom input type specifically for the resolver.

If you specify an input type, Realm exposes the input parameter in the custom resolver's generated GraphQL schema definition as an optional parameter that accepts the specified input type. If you don't specify an input type, the custom resolver does not accept any arguments.

In the Input Type dropdown, select one of the following options:

OptionDescription
None

The resolver does not accept any input.

A custom resolver configuration that does not accept any input parameters.
Beaker IconExample

A custom resolver named myCustomQuery that does not accept an input has the following generated schema:

type Query {
myCustomQuery: DefaultPayload
...
}
Scalar

The resolver uses an existing scalar type from the generated GraphQL schema.

In the second dropdown input, select either a single scalar or an array of multiple scalars of the same type.

A custom resolver configuration that specifies an existing scalar type.
Beaker IconExample

A custom resolver named myCustomQuery that uses the Scalar Type option to specify an input type of DateTiem has the following generated schema:

type Query {
myCustomQuery(input: DateTime): DefaultPayload
...
}
Existing Type

The resolver uses an existing input type from the generated GraphQL schema.

In the second dropdown input, select either a single input object or an array of multiple input objects of the same type.

A custom resolver configuration that specifies an existing input type.
Beaker IconExample

A custom resolver named myCustomQuery that uses the Existing Type option to specify an input type of TaskInsertInput has the following generated schema:

type Query {
myCustomQuery(input: TaskInsertInput): DefaultPayload
...
}
Custom Type

Realm generates a new input type specifically for the resolver based on a JSON schema that you define. The schema must be an object that contains at least one property and a title field that defines a unique name for the generated input type.

A custom resolver configuration for a custom input type.
Beaker IconExample

A custom resolver named myCustomQuery that uses the Custom Type option with an input type named MyCustomQueryInput has the following generated schema:

input MyCustomQueryInput {
someArgument: String;
}
type Query {
myCustomQuery(input: MyCustomQueryInput): DefaultPayload
...
}
5

All GraphQL resolvers must return a payload that conforms to a specific type in the schema. For a custom resolver, you can use an existing generated document type, define a new custom payload type specifically for the resolver, or use a default payload. Realm includes the specified payload type in the custom resolver's generated GraphQL schema definition.

In the Payload Type dropdown, select one of the following options:

OptionDescription
DefaultPayload

The resolver returns the automatically generated DefaultPayload type which has the following signature:

type DefaultPayload {
status: String!
}

The status field will always resolve to "complete" regardless of the resolver function's return value.

{
status: "complete"
}
A custom resolver configuration that uses the default payload type.
Beaker IconExample

A custom resolver named myCustomQuery that uses the DefaultPayload option has the following generated schema:

type Query {
myCustomQuery: DefaultPayload
...
}
Scalar

The resolver uses an existing scalar type from the generated GraphQL schema.

In the second dropdown input, select either a single scalar or an array of multiple scalars of the same type.

A custom resolver configuration that specifies an existing scalar type.
Beaker IconExample

A custom resolver named myCustomQuery that uses the Scalar Type option to specify a payload type of DateTime has the following generated schema:

type Query {
myCustomQuery: DateTime
...
}
Existing Type

The resolver returns an existing document type from the generated GraphQL schema.

In the second dropdown input, select either a single document type or an array of multiple documents of the same type.

A custom resolver configuration that specifies an existing document payload type.
Beaker IconExample

A custom resolver named myCustomQuery that uses the Existing Type option to specify an input type of TaskInsertInput has the following generated schema:

A custom resolver named myCustomQuery that uses the Existing Type option to specify a payload type of [Task] has the following generated schema:

type Query {
myCustomQuery: [Task]
...
}
Custom Type

Realm generates a new payload type specifically for the resolver based on a JSON schema that you define. The schema must be an object that contains at least one property and a title field that defines a unique name for the generated input type.

A custom resolver configuration that defines a new custom payload type.
Beaker IconExample

A custom resolver named myCustomQuery that uses the Custom Type option with a payload type named MyCustomQueryPayload has the following generated schema:

input MyCustomQueryPayload {
someValue: String;
}
type Query {
myCustomQuery: MyCustomQueryPayload
...
}
6

When a user calls a custom resolver Realm executes the resolver function and returns the result, which must conform to the resolver's Payload Type.

Realm passes the function any input data from the operation, if applicable. If the resolver is a computed property on a document type, Realm passes the function the specific document that the resolver was called on.

A custom resolver function has one of two possible signatures, depending on whether or not it accepts an input:

exports = function myCustomResolver(input, source) {
// The `input` parameter that contains any input data provided to the resolver.
// The type and shape of this object matches the resolver's input type.
const { someArgument } = input;
// If the resolver is a computed property, `source` is the parent document.
// Otherwise `source` is undefined.
const { _id, name } = source;
// The return value must conform to the resolver's configured payload type
return {
"someValue": "abc123",
};
}

To define the resolver function, click the Function dropdown and either select an existing function or create a new one.

A custom resolver configured to use the `myCustomResolver` function
7

Once you have configured the resolver, click Save and then deploy your application. Once deployed, you can call the custom resolver through the GraphQL API.

Consider a hypothetical dashboard that a sales team uses to show various statistics and other performance metrics for a given time period. The dashboard uses the custom resolvers in this section to handle some of its specific use cases.

The resolvers all reference Sale documents, which have the following schema:

type Sale {
_id: ObjectId!
customer_id: String!
year: String!
month: String!
saleTotal: Float!
notes: [String]
}

The sales team's hypothetical dashboard uses a custom query resolver that returns aggregated sales data for a specific month.

Realm generates schema definitions for the resolver's custom input and payload types and adds the resolver to its parent type, the root-level Query:

type Query {
averageSaleForMonth(input: AverageSaleForMonthInput): AverageSaleForMonthPayload
}
input AverageSalesForMonthInput {
month: String!;
year: String!;
}
type AverageSaleForMonthPayload {
month: String!;
year: String!;
averageSale: Float!;
}

The resolver uses the following configuration:

OptionDescription
Parent TypeQuery
GraphQL Field NameaverageSaleForMonth
Input Type

Custom Type: AverageSaleForMonthInput

{
"bsonType": "object",
"title": "AverageSaleForMonthInput",
"required": ["month", "year"],
"properties": {
"month": {
"bsonType": "string"
},
"year": {
"bsonType": "string"
}
}
}
Payload Type

Custom Type: AverageSaleForMonthPayload

{
"bsonType": "object",
"title": "AverageSaleForMonthPayload",
"required": ["month", "year", "averageSale"],
"properties": {
"month": {
"bsonType": "string"
},
"year": {
"bsonType": "string"
},
"averageSale": {
"bsonType": "decimal"
}
}
}
Function
exports = async function averageSaleForMonth({ month, year }) {
const cluster = context.services.get("mongodb-atlas");
const sales = cluster.db("corp").collection("sales");
const averageSalePayload = await sales
.aggregate([
{ $match: { month: month, year: year } },
{
$group: {
_id: "$month",
month: "$month",
year: "$year",
averageSale: { $avg: "$saleTotal" },
},
},
])
.next();
return averageSalePayload;
};

To call this custom query, you could use the following operation and variables:

query GetAverageSaleForMonth($averageSaleInput: AverageSaleForMonthInput!) {
averageSaleForMonth(input: $averageSaleInput) {
month
year
averageSale
}
}
{
"variables": {
"averageSaleInput": { month: "March", year: "2020" }
}
}

The sales team's hypothetical dashboard uses a custom mutation resolver that adds a string note to a specific Sale document, identified by its _id.

Realm generates schema definitions for the resolver's custom input type and adds the resolver to its parent type, the root-level Mutation:

type Mutation {
addNoteToSale(input: AddNoteToSaleInput): Sale
}
input AddNoteToSaleInput {
sale_id: ObjectId!;
note: String!;
}

The resolver uses the following configuration:

OptionDescription
Parent TypeMutation
GraphQL Field NameaddNoteToSale
Input Type

Custom Type: AddNoteToSaleInput

{
"bsonType": "object",
"title": "AddNoteToSaleInput",
"required": ["sale_id", "note"],
"properties": {
"sale_id": {
"bsonType": "objectId"
},
"note": {
"bsonType": "string"
}
}
}
Payload Type

Existing Type: Sale

type Sale {
_id: ObjectId!
customer_id: String!
year: String!
month: String!
saleTotal: Float!
notes: [String]
}
Function
exports = async function addNoteToSale({ sale_id, note }) {
const cluster = context.services.get("mongodb-atlas");
const sales = cluster.db("corp").collection("sales");
const sale = await sales.findOneAndUpdate(
{ _id: sale_id },
{ $push: { notes: note } },
{ returnNewDocument: true }
);
return sale;
}

To call this custom query, you could use the following operation and variables:

mutation AddNoteToSale($addNoteToSaleInput: AddNoteToSaleInput) {
addNoteToSale(input: $addNoteToSaleInput) {
_id
customer_id
month
year
saleTotal
notes
}
}
{
"variables": {
"addNoteToSaleInput": {
"sale_id": "5f3c2779796615b661fcdc25",
"note": "This was such a great sale!"
}
}
}

The sales team's hypothetical dashboard uses a custom resolver that adds a new computed property to each Sale document. When an operation requests the computed field for a given Sale, the resolver queries an external system and returns support cases filed by the associated customer.

Realm generates schema definitions for the resolver's custom payload type and adds the resolver to its parent type, Sale:

type Sale {
_id: ObjectId!
customer_id: String!
year: String!
month: String!
saleTotal: Float!
notes: [String]
customerSupportCases: [CustomerSupportCase]
}
type CustomerSupportCase {
caseId: String!
description: String!
}

The resolver uses the following configuration:

OptionDescription
Parent TypeSale
GraphQL Field NamecustomerSupportCases
Input TypeNone
Payload Type

Custom Type: [CustomerSupportCase]

{
"bsonType": "array",
"items": {
"title": "CustomerSupportCase",
"bsonType": "object",
"required": ["caseId", "description"],
"properties": {
"caseId": { "bsonType": "string" },
"description": { "bsonType": "string" }
}
}
}
Function
exports = async function customerSupportCases(sale) {
// Return a list of objects from some external system
const cases = await fetchCustomerSupportCases({
customerId: sale.customer_id
});
return cases;
};

To use this custom computed property, you could run the following operation:

query GetSalesWithSupportCases {
sales {
_id
customer_id
year
month
saleTotal
notes
customerSupportCases {
caseId
description
}
}
}
Give Feedback