Parse Migration: How to Change Cloud Code to Be Compatible with Parse Server (Updated on 23.01.17)

Migration to Parse Server brought not only infrastructure related issues but also cloud code compatibility ones. We described challenges we faced during refactoring and prepared the detailed checklist for you.

Special thank you to our iOS Engineer Igor for his significant contributions to this post.

When we started writing on Parse, we expected that at some point, we may change backend provider. We don't perceive Parse as long-term solution, but rather as a convenient tool to get things done. We have not taken fully to the backends on Parse. Nevertheless, we now have a dozen applications that use Parse.

We build our iOS applications in such a way as that if you suddenly needed to change the backend environment, you wouldn’t significantly modify the app's code. For this purpose, we hide a networking layer deep inside the app, covering PFObjects with our classes (on ObjC) or protocols (on Swift).

The business logic is mainly implemented in the server-side code (using the Cloud functions and Cloud jobs) rather than coded inside the app.

When Facebook announced Parse shutdown on Jan. 2017 our first intent was to migrate backend code to the Parse Server instead of writing it from scratch using another stack.

You can read about our migration adventures with Heroku in the previous post.

So we have some code, and we need to migrate it to a mostly identical environment, what could possibly go wrong? We're going to put our backend code to another filesystem, change some file paths to have less or more resources like RAM or CPU – nothing tricky.

Soon we figured out that we were trying to write code for iOS apps ‘right’: following SOLID principles, splitting logic into modules, writing tests and other such exciting things, but our backend code was far from this. This fact complicates migration a bit :)

We described what exactly we did to make our backend code more maintainable.

You can read about Parse and Parse Server compatibility on their Github repo.

We've created "Parse Server Cloud Code Compatibility Checklist"Download

SOLID principles everywhere

If we had a second chance at writing backend code from scratch, I believe we would pay more attention to code decomposition, writing small, easy-to-change modules instead of putting many Cloud Functions in one file.

Following these simple principles would allow us to migrate code more easily:

  • Split your code into modules that handle only one task.
  • Keep code for API requests (aka cloud functions) and background jobs (aka cloud jobs) separate.
  • Keep code, shared between both cloud functions and jobs, wrapped into small modules and separated.

Splitting large files that contain backend code, which handles requests and business logic to the separate modules, sounds rather easy, but in the end, the question arises, "How do I avoid dropping a ball again so that changing server environment doesn't lead to massive refactoring?"

At first it seems that cloud code itself is not a problem. Who knew that you can't use Parse.Cloud from cloud jobs running in the Parse Server environment?

Well, Robert Martin knew, when describing SRP:

A class should have only one reason to change.

If you think about this a little more deeply, R. Martin means not only class, but every component of the system. In our case, the cloud code javascript files are components.

Parse Cloud Jobs

We ran into some difficulties when we were migrating Parse Cloud Jobs.

Our backend code for Cloud Functions and Cloud Jobs is separated into different files, but often Jobs need to call the same code as Functions do. In this case, we add exports to the required functions to be able to call them from the Jobs file. The easy way works fine on Parse.

Our typical backend code structure on Parse.com

But on Parse Server, you run Job as the separate entity, and you can't use Parse.Cloud there, unless you create Parse instance. But we don't need Parse instance to run a Job. We need to refactor our code and put shared code to a separate module.

Refactored backend code structure on Parse Server

—> Don’t create Job with too many dependencies, because now Job is not linked to your Cloud Code at all, and if some modules are used, they should be delivered with Job.

—> Put Jobs into separate files to schedule them.

—> No need to write require for your jobs in main.js, otherwise, your job may be executed when server starts

—> You need to schedule your Jobs. We used crontab for Job scheduling. It’s really easy and looks like this:

0 */4 * * * docker exec parse_server bash -c 'node ./cloud/jobs/jobMarkVideosAsOld.js' >> /var/log/docker/jobs/jobRemoveOldVideos.log 2>&1

Which means that old videos are removed every 4 hours.

‘Parse’ is not available in files where you describe cloud jobs, so you can’t use code from files with API calls. Keep these things separate and independent. Read more about setting up Cloud Jobs.

Tests

All code without tests is legacy code!

We have API tests in Python, which are more like poke tests: they call Cloud functions via REST API and analyze the result. Sometimes we wrote test scenarios simulating user behavior (e.g., using Helium, SoapUI or PostMan), but had to deal with Parse authentication.

Unfortunately, poke tests cover only "public API", the functions that are available from the app, and don't cover internal cloud modules, because they are not very convenient to test. And of course, the code that is not covered by tests is harder to migrate.

Variable initialization

We established the rule: when you create a new entry in the database table, it's necessary to initialize all the columns with the default values (having null as default value is ok).

Screenshot of Parse Dashboard where you can see lots of undefined values

Unfortunately, sometimes we forgot to initialize objects with default values (you can see in the Dashboard that some cells have undefined values). When we were trying to migrate the database to the mLab, we got some weird crashes in the iOS client inside Parse SDK when accessing undefined fields, although the same application connected to the database on Parse worked fine. We fixed those crashes by initializing undefined values.

Authentication & ACL

Of course, the user authentication was broken. Now the server code doesn't understand which user has sent the request, and you need to grab the user's data from the request. Sometimes you need to add additional parameters to the requests.

Our Cloud Code used Parse.current.User() a lot, until it stopped working on Parse Server, now you need to use request.user.

As for REST API tests, you need to send userId in request params, and then fetch user in your Cloud Code:

// for triggering requests from tests
var user = request.user;
var userId = user ? user.id : request.params.userId;
var startingPromise = new Parse.Promise();

if (!user) {
    utils.fetchUserById(userId)
        .then(function (fetchedUser) {
            startingPromise.resolve(fetchedUser);
            return startingPromise;
        });
} else {
    startingPromise.resolve(user);
}

startingPromise
    .then(function (object) {
        userId = object.id;
        user = object;

      return 
    })

ACLs are broken too, so you may need to use sessionToken from request params. Read more about how to rewrite your Cloud Code in Parse Server compatibility guide.

UPD: with new updates of Parse Server, ACL are working “correctly”, but in a different way to how they were working before. Your code might work totally fine on parse.com, but return an “unauthorized” error on Parse Server. There are many ACL-related questions in Parse Server repo (like this, this or this), and different versions of Parse Server handle ACL in different ways.

Working with external modules

Parse.com supports several 3rd-party Cloud Modules to use from the server code (built-in node.js libraries). Of course, the Parse Server doesn't include these modules. Now you need to install them separately on your server (using npm install) or include libraries as sources.

You're lucky if you've added Cloud Modules as separate files: less pain with updating dependencies. Unfortunately, the code that was based on old 3rd-party modules may need to be rewritten (if the module has changed a lot).

Once again, a great Parse Server compatibility guide answers the question of which modules can be replaced by external ones, and which cannot.

Push notifications

If your app sends push notifications from the server code, this code should receive some minimal rewrites. Fortunately, Parse Server already supports push notifications! It's only necessary to configure provision profile and certificates and to edit Cloud Code slightly.

This is how we send pushes from Cloud Code on Parse.com:

Parse.Push.send({
    channels: [channel],
    data: data
});

You need to change code to this on Parse Server:

Parse.Push.send({
    channels: [channel],
    data: data
}, { useMasterKey: true });

Query limits

Do you remember, that each Query had a max items limit equal to 1000 on parse.com? There’s no such limit anymore on Parse Server! However, if you don’t provide any limit, the default value is 100. This may lead to confusing and hard-to-reproduce bugs, because your previous code was written according to other rules.

Of course, you can look through all your query-related code and check the limits. In our case, we were too lazy to do it, so we have overridden query limit in one place, using the javascript prototype function.

Overriding default query limit by overriding Query.find method
Overriding default query limit by overriding Query.find method

Lessons learnt

It's very useful to keep things clean in many areas: from the workplace to your code.

An attempt to migrate the code from one environment to another is better than any code review.

SOLID is the foundation of engineering culture that is applicable to any activity, not just engineering.

Parse is shutting down on January 30, 2017 and you might need help migrating your apps. We can help with that, let's talk!

Subscribe to updates
Share this article

Contact me About

By sending this form I confirm that I have read and accept Intellectsoft Privacy Policy

Something went wrong. Send form again, please.

Thank you for your message!

We will get in touch with you regarding your request within one business day.

Send again

What happens next?

  • Our sales manager reaches you out within a few days after analyzing your business requirements
  • Meanwhile, we sign an NDA to ensure the highest privacy level
  • Our pre-sale manager presents project estimates and approximate timeline