Docs Menu
Docs Home
/ /
Atlas App Services
/ /

Define a Custom Resolver

On this page

  • Overview
  • Procedure
  • Create a New Custom Resolver
  • Define the Resolver Field Name
  • Define the Parent Type
  • Define the Input Type
  • Define the Payload Type
  • Define the Resolver Function
  • Save and Deploy the Resolver
  • Custom Resolver Examples
  • Scenario & Schemas
  • Custom Query Resolver
  • Custom Mutation
  • Computed Properties

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 App Services 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 App Services UI
2

Specify App Servicese name for the resolver in the GraphQL Field Name input. App Services 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.

3

App Services 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:

Option
Description
Query

The resolver is a root-level query operation:

Example

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:

Example

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.

Example

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, App Services 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:

Option
Description
None

The resolver does not accept any input.

Example

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.

Example

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.

Example

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

App Services generates a new input type specifically for the resolver based on a 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.

Example

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. App Services 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:

Option
Description
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"
}

Example

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.

Example

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.

Example

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

App Services generates a new payload type specifically for the resolver based on a 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.

Example

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 App Services executes the resolver function and returns the result, which must conform to the resolver's Payload Type.

App Services passes the function any input data from the operation, if applicable. If the resolver is a computed property on a document type, App Services 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",
};
}
exports = function myCustomResolver(source) {
// If the resolver is a computed property, `source` is the parent document.
// Otherwise `source` is undefined.
const { _id, name } = parent;
// 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.

7

Once you have configured the resolver, click Save and 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]
}
{
"title": "Sale",
"bsonType": "object",
"required": ["_id", "customer_id", "year", "month", "saleTotal"],
"properties": {
"_id": { "bsonType": "objectId" },
"customer_id": { "bsonType": "string" },
"year": { "bsonType": "string" },
"month": { "bsonType": "string" },
"saleTotal": { "bsonType": "decimal" },
"notes": {
"bsonType": "array",
"items": { "bsonType": "string" }
}
}
}

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

App Services 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:

Option
Description
Parent Type
Query
GraphQL Field Name
averageSaleForMonth
Input Type

Custom Type: AverageSaleForMonthInput

{
"bsonType": "object",
"title": "AverageSaleForMonthInput",
"required": ["month", "year"],
"properties": {
"month": {
"bsonType": "string"
},
"year": {
"bsonType": "string"
}
}
}
input AverageSalesForMonthInput {
month: String!;
year: String!;
}
Payload Type

Custom Type: AverageSaleForMonthPayload

{
"bsonType": "object",
"title": "AverageSaleForMonthPayload",
"required": ["month", "year", "averageSale"],
"properties": {
"month": {
"bsonType": "string"
},
"year": {
"bsonType": "string"
},
"averageSale": {
"bsonType": "decimal"
}
}
}
type AverageSaleForMonthPayload {
month: String!;
year: String!;
averageSale: Float!;
}
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", year: "$year" },
averageSale: { $avg: "$saleTotal" },
}
},
{
$project: {
month: "$_id.month",
year: "$_id.year",
averageSale: 1
}
}
])
.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.

App Services 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:

Option
Description
Parent Type
Mutation
GraphQL Field Name
addNoteToSale
Input Type

Custom Type: AddNoteToSaleInput

{
"bsonType": "object",
"title": "AddNoteToSaleInput",
"required": ["sale_id", "note"],
"properties": {
"sale_id": {
"bsonType": "objectId"
},
"note": {
"bsonType": "string"
}
}
}
input AddNoteToSaleInput {
sale_id: ObjectId!;
note: String!;
}
Payload Type

Existing Type: Sale

type Sale {
_id: ObjectId!
customer_id: String!
year: String!
month: String!
saleTotal: Float!
notes: [String]
}
{
"title": "Sale",
"bsonType": "object",
"required": ["_id", "customer_id", "year", "month", "saleTotal"],
"properties": {
"_id": { "bsonType": "objectId" },
"customer_id": { "bsonType": "string" },
"year": { "bsonType": "string" },
"month": { "bsonType": "string" },
"saleTotal": { "bsonType": "decimal" },
"notes": {
"bsonType": "array",
"items": { "bsonType": "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.

App Services 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:

Option
Description
Parent Type
Sale
GraphQL Field Name
customerSupportCases
Input Type
None
Payload Type

Custom Type: [CustomerSupportCase]

{
"bsonType": "array",
"items": {
"title": "CustomerSupportCase",
"bsonType": "object",
"required": ["caseId", "description"],
"properties": {
"caseId": { "bsonType": "string" },
"description": { "bsonType": "string" }
}
}
}
type CustomerSupportCase {
caseId: String!
description: String!
}
type Sale {
_id: ObjectId!
customer_id: String!
year: String!
month: String!
saleTotal: Float!
notes: [String]
customerSupportCases: [CustomerSupportCase]
}
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
}
}
}

Back

GraphQL Types, Resolvers, and Operators