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
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
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
to the required functions to be able to call them from the Jobs file. The easy way works fine on Parse.But on Parse Server, you run Job as the separate entity, and you can't use
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.—> 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
for your jobs in , otherwise, your job may be executed when server starts—> You need to schedule your Jobs. We used
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.
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 aboutTests
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
as default value is ok).Unfortunately, sometimes we forgot to initialize objects with default values (you can see in the Dashboard that some cells have mLab, we got some weird crashes in the iOS client inside Parse SDK when accessing fields, although the same application connected to the database on Parse worked fine. We fixed those crashes by initializing undefined values.
values). When we were trying to migrate the database to theAuthentication & 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
a lot, until it stopped working on Parse Server, now you need to use .As for REST API tests, you need to send
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 Parse Server compatibility guide.
from request params. Read more about how to rewrite your Cloud Code inUPD: 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
) 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.

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!