Advanced Guides
Upgrade to Parse 3.1
24 min
more power to your app with parse server 3 1 introduction the parse community recently released version 3 1 of the parse server https //docs parseplatform org/parse server/guide/ this update has cleaned up cloud code syntax it is far more amenable to leveraging the es6 async and await constructs additionally, some idiosyncrasies associated with using parse were dropped, for example, cloud functions simply return a promise rather than using the error error or success success messages on the response object you can upgrade your apps on back4app easily on your dashboard this guide will demonstrate how to upgrade your code to leverage the new features of 3 1 to follow this guide you are welcome to take a look at the example project provided this is a guest tutorial written by john considine https //github com/considine , lead developer at k optional https //koptional com/ goals to update your back4app parse server to 3 1 and migrate your cloud code accordingly prerequisites to complete this tutorial, you need ° an existing back4app application that’s using parse server 2 x ° follow the create new app tutorial to learn how to create an app at back4app summary of changes the most notable changes are the following 1\ cloud code runs with parse sdk 2 x previously, cloud code ran with parse sdk 1 x with parse server 3 1, it runs parse sdk 2 x look at parse sdk releases to understand better what this entails this major version bump mostly involves bug fixes it also adds containedby and includeall query methods, as well as abilities to fetch an object with includes 2 aggregate update since parse 2 7 1, you can use the aggregate method on a query this allows you to leverage the underlying database a little bit more now, the syntax for the aggregate method on parse query parse query has been updated you can execute a query using two stages the match match and the group group stage previously, you didn’t need the pipeline key in the pipeline object due to the underlying api, you must now explicitly include the pipeline key the value should be an array of one or two stages, featuring group group and match match look at parse official documentation for more specific examples 3 under the hood optimizations some under the hood optimizations have been made for example, a parse livequery will fetch class level permissions (clps) along with data to prevent double database access 4\ parse reset email when requesting a password reset email, the server will return success even if that email is not saved additionally, password reset tokens expire when a user’s email is reset 5\ cloud triggers update with this release, you can share data between the beforesave and aftersave triggers on the same object for example 1 parse cloud beforesave('comment', async request => { 2 request context = { 3 foo 'bar' 4 }; 5 }); 6 7 parse cloud aftersave('comment', async request => { 8 console log(request context foo); //bar 9 }); you can see more about changes on parse server in the official parse 3 1 changelog by clicking here 6\ livequery improvement the parse livequery client allows you to subscribe to queries, and receive updates from the server as they come in traditional queries are executed once by the client, so this is very helpful for cases like messaging, etc with back4app you can also take advantage of this technology https //www back4app com/docs/platform/parse server live query example with the release of 3 x, the parse community has improved the system for livequery acls https //docs parseplatform org/js/guide/#security for other objects you can pass a session token now to the subscribe method of a live query, and the parse server will manage only returning results that this user has access to for example, a user may have read/write access to certain ‘messages’, but not all 1 let query = new parse query('message'); 2 // you can get session token with 3 // parse user current() getsessiontoken() when logged in 4 let subscription = client subscribe(query, sessiontoken); the above code will automatically subscribe to all messages that the user has access to, relieving you from the responsibility of querying specific messages the main part of the changes pertains to how cloud code is handled for this, see the migration guide below 1 aligning technical fundamentals we’ll start with an example cloud project using the 2 x release that way we can navigate through the appropriate changes of syntax you can see the repository for this example project if you’re familiar with async and await, you can skip this section the evolution of asynchronous code in javascript looks something like this callback functions promises async / await any modern javascript application will likely use all three callback functions involve passing a function as an argument to another function this second function can execute the first at some point 1 // callbacks 2 // executes callback function after waiting 100 milliseconds 3 settimeout(function() { 4 alert('my callback function'); 5 }, 100); callbacks are essential but can be unwieldy when it comes to chaining many of them specifically, nesting several layers can be difficult to read, and error handling proves difficult hence in es2015 the promise was introduced 1 // promises 2 // executes several promises in a row with no significant nesting 3 const mypromise = new promise(function(resolve, reject) { 4 settimeout(function() { 5 if (math random() < 0 2) reject(new error('random failure!')); 6 resolve('finished'); 7 }, 100); 8 }); 9 10 // executes this promise 4 times and catches errors involved 11 mypromise 12 then(() => mypromise) 13 then(() => mypromise) 14 then(() => mypromise) 15 then(() => mypromise) 16 catch(e => console log(e)); promises improve the readability of asynchronous programming they also make pipelines more explicit but even bigger strides were made in es2017 with the async / await constructs your code can now wait for the results of a promise without relying on the then / catch blocks (which can also become tough to read) 1 // using the definition of mypromise from the above code 2 async function foo() { 3 try { 4 let result = await mypromise; 5 result = await mypromise; 6 result = await mypromise; 7 } catch (e) { 8 console log(e); 9 } 10 } perhaps for a very simple example, this may not seem more elegant than plain promises but awaiting the results of an asynchronous function is often precisely what we want to do hence, it truly optimizes the readability of our code support for async/await as aforementioned, async/await was included in the ecmascript 2017 specification (es8) for server code, versioning is hardly an issue since you can update to the version of node js that supports these features rest assured, back4app’s environment supports recent stable versions for browser code, transpilers like babel will produce an es2016 compatible with code that uses async / await and works in modern browsers 2 thinking about your code differently the main change with cloud code involves what the developer does versus what the library does previously, you would explicitly manage the response since most cloud code will execute asynchronously making database queries and writes it makes more sense to return a promise, reducing the boilerplate code the intuition behind cloud functions is that there is minimal setup and configuration involved with writing server side code this release embodies that idea; keep this in mind as you’re refactoring and creating new functions to show how cloud code functions work in parse server 3 1, we rewrote a functional cloud code sample from a version of parse server before migration you can find this code by clicking here the same cloud code function is written in parse 3 1 as shown below 1 // cloud code before migration 2 // full code found in link above 3 const post = 'post'; 4 parse cloud define('posts\ get', function(request, response) { 5 // needs a post id 6 return new parse query(post) 7 get(request params id, { usemasterkey true }) 8 then(post => { 9 response success(post); 10 }) 11 catch(e => { 12 response error({ message e message }); 13 }); 14 }); 3 adding all async markers any function that uses await must be declared with the async modifier this simple refactoring will attach async to all cloud functions it will also replace them with arrow functions as they are more succinct in this case (and what the updated official parse guide uses) 1 // snippet of step 2 code refactoring see full code 2 // here in the link at the top of this step 3 const post = 'post'; 4 const comment = 'comment'; 5 6 parse cloud define('posts\ get', async (request) => { 7 // needs a post id 8 return new parse query(post) 9 get(request params id, { usemasterkey true }); 10 } your code will look like this after this refactoring nothing crazy so far in the next step, we’ll get our money’s worth for this change 4 removing references to response, employ await your code will look like this after this refactoring this step is a little bit trickier we need to remove all references to the ‘response’ variable, returning a promise instead in the case of multiple query/save functions, await the response check out the comment create method to see how this is done 1 // snippet of step 3 code refactoring see full code 2 // here in the link at the top of this step 3 parse cloud define('comment\ create', async request => { 4 // post should have text and should have a user and a post id 5 if (!request user) { 6 throw new error('unauthenticated!'); 7 } 8 9 if (!request params text) { 10 throw new error('a comment needs text!'); 11 } 12 if (!request params post id) { 13 throw new error('a comment needs a post!'); 14 } 15 16 // get the post 17 18 const post = await new parse query(post) get(request params post id, { 19 usemasterkey true 20 }); 21 return new parse object(comment, { 22 text request params text, 23 user request user, 24 post post 25 }) save(null, { usemasterkey true }); 26 }); note that now a javascript error is thrown instead of calling response error parse will handle transforming this into a response for us deleting a ‘post’ or ‘comment’ involves grabbing the object first, then destroying it by using ‘await’, the destroy method can access the saved object outside of a block that completes all necessary refactoring for migration that’s to say if you do up until this step, congrats! your code will run on back4app parse 3 1! 5 advanced tricks (optional) the following changes are optional but can shorten your code significantly i find they reduce boilerplate you probably noticed a lot of manual checks for parameters or the authenticated user these ensure that strange behavior doesn’t occur for example, we’ve decided our ‘post’ object needs text, so if no ‘text’ parameter is passed, the object will get saved without it one way to prevent this is to check that text was passed but manual checks can be time consuming and inelegant in this section, we take advantage of object destructuring to implicitly complete these checks you should see the documentation above if you don’t know what destructuring is but in short, it allows you to turn an object’s property into a variable with very concise syntax 1 // this 2 var obj = { 3 hi true, 4 bye false 5 }; 6 var hi = obj hi; 7 var bye = obj bye; 8 9 // is equivalent to this 10 var obj = { 11 hi true, 12 bye false 13 }; 14 var { hi, bye } = obj; 15 16 console log(hi); 17 // true 18 console log(bye); 19 // false destructuring is less verbose than manual assignment it also allows you to declare parameter variables on the fly which is nice 1 parse cloud define('posts\ get', async ({ params { id } }) => { 2 // id is declared 3 }); when combined with an es2015 notation for object initialization we can optimize our parameter checks we can do this 1 // iterates through object's keys makes sure, for each key, the value is set 2 const assertparams = parameter obj => { 3 for (var key of object keys(parameter obj)) { 4 if (typeof parameter obj\[key] === 'undefined') 5 throw new error(`missing parameter ${key}`); 6 } 7 }; 8 9 var obj = { 10 hi true, 11 bye false 12 }; 13 var { hi, undef, bye } = obj; // undef will be undefined 14 var check 1 = { hi, bye }; 15 var check 2 = { hi, undef }; 16 17 // check = { hi true, no undefined } 18 assertparams(check 1); // passes 19 assertparams(check 2); // throws error so for our parse code, we can do this 1 // snippet of advanced code refactoring see full code 2 // here in the link at the top of this step 3 parse cloud define('posts\ delete', async ({ user, params { id } }) => { 4 // makes sure user is authenticated, and id parameter is passed 5 assertparams({ user, id }); 6 const post = await new parse query(post) get(id, { 7 usemasterkey true 8 }); 9 return post destroy({ usemasterkey true }); 10 }); if this is daunting, don’t fret the full code example might help in short, ‘assertparams’ is a utility function for throwing an error if a value is undefined we can check for parameters in one motion by combining object destructuring and es2015 object initialization this removes eight or nine manual checks which start to become unsightly after a while 6 upgrading on back4app once you have migrated your code, you must do two more things to have it running on back4app upgrade your back4app server version upload your new cloud code i’ve briefly mentioned the first step before all you need to do is log into back4app and go to your app’s dashboard from there, just select “server settings” on the left, followed by the “settings” button on the “manage parse server” card you may then select the parse server 3 1 1 radio button and hit “save” last but not least, you can upload your code via the back4app cli https //blog back4app com/2017/01/20/cli parse server/ , or your dashboard using the manual upload see this guide https //www back4app com/docs/android/parse cloud code if you need more information on this step notes on launching a new version if your code is complex, it might be a good idea to run some tests before doing these two steps back4app has some guides on how to do this here and here finally, it’s important to note that these changes are breaking using the code we wrote on a 2 x parse server will fail, and using 2 x code on a 3 1 server will also fail therefore, you must make sure to upgrade your back4app parse version right when you upload your upgraded cloud code if you have many users and are concerned about the code upload and version upgrade being slightly out of sync, you can do something like this 1 const serverversion = 2 parse coremanager get('version') === 'js1 11 1' ? 'old' 'new'; 3 4 if (serverversion === 'new') { 5 parse cloud define('posts\ get', async ({ params { id } }) => { 6 assertparams({ id }); 7 // needs a post id 8 return new parse query(post) get(id, { 9 usemasterkey true 10 }); 11 }); 12 } else if (serverversion === 'old') { 13 // old definition here 14 } this code dynamically figures out the version and defines cloud functions based on that after uploading this, you would change to 3 1, and then you could re upload the code with the old part removed including the check at first ensures there isn’t a point where your code will crash since you can upgrade and upload within a couple of seconds, it’s usually not necessary conclusion at this guide, we demonstrated simply how to migrate your cloud code to the parse 3 1 release remember to bump your back4app version to 3 1 after you make these changes also importantly, this guide demonstrated an improved syntax that parse 3 1 leverages our refactorings cut the codebase from 160 lines to 90 and made it much more readable for actual applications, this will pay dividends