How to return from Parse Cloud Function in Node.js

Hello,

I have a parse cloud function that returns as follows on success:

	.then((res) => {
		console.log(res);
		return res;
	})

However, I get this error after the function executes:

Fatal error: Error calling cloud function: ParseError code=-1 error=Invalid struct: No value associated with key CodingKeys(stringValue: “result”, intValue: nil) (“result”).

I have tried several ways to return but always get this error.

What is the correct format to return from a cloud function?

Thanks for any help!!

Please provide the code for your ParseObjects when asking questions along with the code you tried… The playgrounds shows how to do this. You should know the “type” you expect to be returned:

The documentation for ParseCloud provides additional info ParseCloud Protocol Reference

If you need type erasure, you should read here to learn possible packages to add:

So my swift code right now to call the cloud functions this:

let sendEmail_CloudFunction = Cloud(functionJobName: "sendEmail", arguments: myParameters)
sendEmail_CloudFunction.runFunction() { result in
	switch result {
	case .success(let response):
		print("Response from cloud function: \(response)")
	case .failure(let error):
		assertionFailure("Error calling cloud function: \(error)")
	}
}

And the node.js cloud function is this:

    var args = request.params.arguments

    var mailTemplateName = args.mailTemplateName
    var mailSubject = args.mailSubject
    var mailFromAddress = args.mailFromAddress
    var mailRecipient = args.mailRecipient
    var mailVariables = args.mailVariables
    var mailReplyTo = args.mailReplyTo
    var mailAttachmentName = args.mailAttachmentName

	const mailgun = require("mailgun-js");
	const DOMAIN = 'xxxxxx';
	const mg = mailgun({apiKey: 'key', domain: DOMAIN});
	const data = {
		from: mailFromAddress,
		to: mailRecipient,
		subject: mailSubject,
		template: mailTemplateName,
		text: 'Testing some Mailgun mail!',
		'h:X-Mailgun-Variables': mailVariables
	};
	mg.messages().send(data)
	.then((res) => {
		console.log(res);
		console.log(`************************* Sending Mail - console.log - ${JSON.stringify(res)}`);
		return res;
	})
	.catch((err) => {
		console.error(err);	
		// console.log(`************************* Sending Mail - console.log - ${JSON.stringify(err)}`);
		return err;
	});

The return res is this format:

{"id":"<[email protected]>","message":"Queued. Thank you."}

But from the error, it looks like parse is expecting a ‘result’ : Int.

I have tried returning { result : 1} as JSON and returning just the result above, plus returning the result above with the result property added to the JSON, but none seem to work.

I guess the question is, what is parse expecting, or do I define what the response is, like when I used dataTaskPublisher and .decode with my own response struct.

I hope that helps to clarify my. question.

Thanks for the help!!

You didn’t post your struct that conforms to ParseCloud, that’s the most important part.

Did you look at the playgrounds link? It should answer the questions you’ve asked

LOL, ok, do you mean this:

struct Cloud: ParseCloud
{
	typealias ReturnType = String
	var functionJobName: String
	var arguments: [String : String] = [:]
}

Sorry about that. But that is the same as the playground sample. The playground sample cloud function only returns a string and does not show a version taking parms, but I figured that out.

The playground code also does not show anything about a return value that I could see. I would like to return the json returned from the function. It contains the ID and message. I was able to just return a string, like the playground code, but not the JSON, or string with the id and message.

Sorry about the confusion, I am new to SwiftUI and I do not know Node.js very well at all, BUT, I am learning, thank god!!

Thanks for the help.

Best

Can you post your whole cloud code function in JavaScript? I can’t see your cloud code function being defined.

If your return format is: {"id":"<[email protected]>","message":"Queued. Thank you."}.

Then you can define a return type of:

struct EmailType: Decodable {
    var id: String
    var message: String
}

struct Cloud: ParseCloud
{
	typealias ReturnType = EmailType // This can also be a [String: String], but the struct is probably more useful. If it's a specific ParseObject being returned, that should be here.
	var functionJobName: String
	var arguments: [String : String] = [:] // The comments in the playground and in the API documentation says how to create this
}

Ahh, ok yes, that makes sense and that is what I was missing and did not understand. Thank you. Everything works as expected now!!

So one other question, in my node.js cloud func, I use await as follows:

const result = await mg.messages().send(data)|
console.log(`************************* Sending Mail - result - ${JSON.stringify(result)}`);|
return result|

Is it ok to use await in my server node.js cloud function?

Thanks again for the help!!

Hey cbaker6, one other quick question about this struct:

struct Cloud: ParseCloud
{
	typealias ReturnType = EmailType
	var functionJobName: String
	var arguments: [String : String] = [:]
}

Is there a way to modify the arguments property so that I can use a [String : Any]?

Some of my parameters are a key with a value that is a list of string, or ints. so by using a string to any, I can pass that up and on the server it knows how to deal with the value. I used to pass string : any with the iOS parse sdk and PFCloud.

Can I have the arguments be a struct that is Encodable?

Thank you

Yes, it’s okay and even required in situations when you need to query the server or wait for some other async call to finish

Is there a way to modify the arguments property so that I can use a [String : Any]?

No, Any isn’t Encodable, this only works with types that conform to the Encodable protocol. The dictionary for an argument was just an example. Your arguments can be separated into multiple properties of your cloud struct:

struct Cloud: ParseCloud {
  typealias ReturnType = EmailType
  var functionJobName: String
  var argument1: String // Required parameter to your cloud function, `argument1` should match the name of your cloud function parameter. 
  var argument2: Int? // Optional parameter to your cloud function, `argument2` should match the name of your cloud function parameter. 
  // You can have as many parameters as your cloud function has
}

Can I have the arguments be a struct that is Encodable?

If you want something similar to Any, you can add the AnyCodable package via SPM and use type AnyEncodable. See more here and here. So it may look like:

struct Cloud: ParseCloud {
  typealias ReturnType = EmailType
  var functionJobName: String
  var argument1: String // Required parameter to your cloud function, `argument1` should match the name of your cloud function parameter. 
  var argument2: Int? // Optional parameter to your cloud function, `argument2` should match the name of your cloud function parameter. 
  var argument3: AnyEncodable // Required parameter to your cloud function, `argument3` should match the name of your cloud function parameter. 
  // You can have as many parameters as your cloud function has
}

You can also do this with a mixed dictionary. Though I prefer the aforementioned methods as opposed to putting everything in a dictionary:

struct Cloud: ParseCloud {
  typealias ReturnType = EmailType
  var functionJobName: String
  var arguments: [String: AnyEncodable] 
}

Thank you for the answer. I agree on the arguments, separate properties seems to be the cleanest approach. Thank you for the clarification!!

Have a great rest of your day!!

1 Like

Note that if you are using Xcode 13.2+, you probably want to start using the async/await instead of asynchronous callbacks. So instead of:

You would write:

let sendEmail = Cloud(functionJobName: "sendEmail", arguments: myParameters)
do {
    let response = try await sendEmail.runFunction()
    print("Response from cloud function: \(response)")
} catch {
    print("Error calling cloud function: \(error)")
}

You can also improve the call of your Cloud function call by adding inits:

struct SendEmail: ParseCloud {
  typealias ReturnType = EmailType
  var functionJobName: String // You can also do `var functionJobName = "sendEmail"`
  var arguments: String // Required parameter to your cloud function, `argument1` should match the name of your cloud function parameter. 
}

extension SendEmail {
  // Place all of your inits in an extension instead of the struct declaration
  init(arguments: [String: String) {
    functionJobName = "sendEmail"
    self.arguments = arguments
  }
}

let sendEmail = SendEmail(arguments: myParameters)
do {
    let response = try await sendEmail.runFunction()
    print("Response from cloud function: \(response)")
} catch {
    print("Error calling cloud function: \(error)")
}

cbaker6, thanks for all the info, it has been most helpful. I have one question about a response. So as you can see from the above, there are two possibilities, right… success and error. I defined the ReturnType as EmailType and defined the required properties and it works great. However, in an error case, the properties are different. So, I used one method of making the properties in the struct optional so then f they do not exist it is ok. But, I try to steer away from optionals if I can. Is there a better way to define the error case return type? Or, is using optionals the only choice?

Hope this makes sense!

Best

The ReturnType shouldn’t be related to the error, it should be exactly the way it is in How to return from Parse Cloud Function in Node.js - #7 by cbaker6. If you need to throw an error, you should be throwing errors in your CloudCode functions like so:

Parse.Cloud.beforeSave('BeforeSaveFailWithErrorCode', function () {
      throw new Parse.Error(999, 'Nope');
});

You can catch custom errors using otherCode:

If you know the specific errors you are trying to catch, you can do something similar to (more options in the documentation):

@dblythy may have more info about errors in CloudCode and catching them in ParseSwift.