Parse Server: The Best Practices Guide
Contents
Introduction
This guide aims to provide a set of good practices that will be helpful to beginners as well as expert developers who are starting to work with Back4App. If you are an experienced user of the platform, we recommend you to at least take a quick look at this tutorial, as it will surely help you find something informative that you didn’t know before.
Most of the explanations of this article can already be found in our Docs Portal, or in the Parse Server Official Documentation, but they contain so much information that it can be difficult to know where to start.
We aim to be as clear as possible, and we’ll include both good and bad examples applied to real-life scenarios, in order to give a sense for how certain things work when writing your own Parse specific code. Not everything explained here is mandatory to start using your app, but if you begin by knowing the best practices, it will soon become a habit, and quite frankly, it will be hard for you to do it wrong
We’ll be covering three main topics: security, performance, and productivity. The table of contents of this guide can be found below:
- Security
- Protect your master key
- Use https to make connections
- Validate data sent by users
- Don’t let users have access to sensitive data from others
- Require authentication
- Performance
- Use efficient queries
- Write restrictive queries
- Avoid count operations
- Use client-side caching
- Productivity
- Use the command line interface
- Connect to a Continuous Integration Server
- Know how to use the Back4app Logs
- Use Back4App Analytics
Prerequisites
While you can read this guide without any prior knowledge about Parse Server, it is recommended for you to read the Creating new App tutorial and at least one specific platform: Android quickstart guide. You should also see our cloud code for Android guide. Then, you’ll have a running app in Back4App and sufficient knowledge about Android Parse Server, which is fundamental to follow this tutorial.
1. Security
Security is a part of an app’s development progress that is often overlooked or simply forgotten. If your app is compromised, it’s not only the developers who suffers, but potentially the users of you app as well. Don’t release your app without setting up a proper security!
In this section, we’ll show you different ways on how to secure your app and on how to safeguard data. We’ll discuss which one is better in what scenario.
1.1. Protect your master key
Your master key, unlike client keys, including REST key or JavaScript key, is definitely a security mechanism. Using the master key allows you to bypass ALL of your app’s security mechanisms, such as class and object level permissions. However, it requires a few precautions:
- Never include your master key in any binary or source code you ship to customers;
- Only use master key in the server side code;
- Never give your master key to untrusted people.
1.2. Use https to make connections
Https is a secure protocol for transferring information from the client’s browser to your server. Its existence is necessary to prevent hackers from accessing sensitive information with attacks like MITM. To properly use https connections with your app, check the following:
- You need to use https over http to make connections with Parse Server. Written below is an example given by the name Android that shows how to use https correctly to establish an active connection. Note that the wrong way would be by using http:// instead of https://.
- In order to enable https for your domain and make it classified, it is necessary to verify it using SSL. This means that you should always redirect your client to the https version of your site, even if they choose to go to the unprotected one. A problem when you may face is that Parse will redirect you to the unprotected version of your site, which means that you cannot just change your cloud code to make the redirection to the secured version of your site. A way to bypass that is to just change your URL on the front end to “https://”. Below is an example, in Angular, on how to achieve that security:
1.3. Validate data sent by users
It is important to prevent invalid or malicious data, sent by a malicious client, from being stored in your database.
A very common use of this validation is to make sure your client fills all the necessary fields when making a registration. Below is an example of what you should be implemented in your cloud code, this will make sure that your client has a valid email before saving it into your database.
function validateEmail(email) { var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return re.test(email); } Parse.Cloud.beforeSave(Parse.User, function(request, response) { var user = request.object; if (!user.get("email")) { response.error("Every user must have an email address."); } else { if(validateEmail(user.get("email"))) { response.success(); } else { response.error("Email address is not a valid email"); } } });
You can also implement similar functions to ensure that:
- All phone numbers have the same formatting;
- An age is within a particular range;
- And to not let users change a calculated field.
1.4. Don’t let users have access to sensitive data from others
In an app, it is often necessary to keep user’s data separated from others, because sometimes different objects in a class need to be accessible by different people. If you don’t implement the Object Level Access Control, one user could change a personal data from other users, which will eventually lead to a break in the security!
An easy yet effective way of using this security method (which should be used in almost every app) is to ensure that the user’s sensitive data, like phone numbers, isn’t readable and writable by everyone. This can be done by adding just two lines in your cloud code.
ParseUser user = ParseUser.getCurrentUser(); user.setACL(new ParseACL(user));
You can also make some information about the user private and some public. This is very useful when you want to let the user choose which information goes public. In your cloud code, you can make just the “phoneNumber” field private to the user.
ParseObject privateData = new ParseObject("PrivateUserData"); privateData.setACL(new ParseACL(ParseUser.getCurrentUser())); privateData.put("phoneNumber", "555-5309"); ParseUser.getCurrentUser().put("privateData", privateData);
1.5. Require authentication
Often it is a good idea to certify that your users are logged in, before letting them having permissions to certain queries. This can be done by using the “requiresAuthentication” field.
A practical example of this property is when creating an “Announcement” class in your app. You want only authenticated users to read objects in that class and users with the admin to perform all operations. This can be done by implementing the following permissions to your Announcement class.
{ classLevelPermissions: { "find": { "requiresAuthentication": true, "role:admin": true }, "get": { "requiresAuthentication": true, "role:admin": true }, "create": { "role:admin": true }, "update": { "role:admin": true }, "delete": { "role:admin": true } } }
Just to remember, you can set these permissions using an example from the REST API.
2. Performance
Performance is an important part of app development that is often neglected at the beginning, which causes the app to perform considerably worse than its maximum capability when exposed to increased load and usage in the later stages of the development. This means that you have to pay way more for server usage to achieve the desired speed of requests and, most likely, your clients will suffer from slower server response times.
In this section, we’ll show you some performance guidelines to remember when designing your app. We’ll discuss a few ways on how to improve your app’s performance. While not all of them may apply to your app, it’s always a good idea to have them in mind, so that you are prepared when any such situation occurs in the future.
2.1. Use efficient queries
To make queries, Parse uses indexes which makes it possible for your query to look just at a subset of your database, instead of the entire data. This can make the performance of your queries much better, which means that you can take the most advantage of Parse Indexing.
Queries that use Indexing very well include: “Equal to”, “Contained in” and comparative queries, like “Greater than”.
Queries that use Indexing poorly and must be avoided include: “Not equal to”, “Not contained in” and regular expressions.
Now, we will go through an example, taken from Parse Android documentation that shows you how to transform a poorly indexing query into better and faster ones.
Let’s say your User class has a column called state which has values “SignedUp”, “Verified” or “Invited”. If you want to find all users that are not invited, the easier way to think is to do a query using the following code:
ParseQuery<ParseUser> query = ParseQuery.getQuery(ParseUser.class); query.whereNotEqualTo("state", "Invited");
The problem with this query is the inappropriate usage of the “Not equal to”. A quick way to solve this problem is by using a indexing query, like the “Contained in”:
query.ContainedIn("state", Array.asList("SignedUp", "Verified"));
For “Not contained in” queries, you can just use a similar reasoning as given in the above example for getting better results of indexing.
Lastly, you should avoid the use of regular expressions. They could prove to be very expensive in practice and, therefore, should be replaced by other ways that are more efficient and achieve similar results. One way is using implementing search. To learn more about it, visit: Parse Android Implement Search. Apart from this, many other languages are available in the Parse Official Documentation.
2.2. Write restrictive queries
Writing restrictive queries is the practice of returning just the data that the client needs. This is crucial in a mobile environment where network connectivity can be unreliable and data usage limited. Your app will also appear to be more responsive if you have faster queries, which can be achieved by adding constraints to them.
A query limit is 100 by default. If you don’t need to load all the data that satisfies your query, like when you want to show just a few search results, you can limit the number of results returned.
query.setLimit(10); // limit your query to at most 10 results
You can also restrict returned data by calling selectKeys with the array of keys you want. To retrieve just the playerName and the score fields, you can add the following code.
ParseQuery<ParseObject> query = ParseQuery.getQuery("GameScore"); query.selectKeys(Arrays.asList("playerName", "score"));; List<ParseObject> results = query.find();
The remaining fields can be fetched later by calling a fetchIfNeededInBackground function.
ParseObject object = results.get(0); object.fetchIfNeededInBackground(new GetCallback<ParseObject>() { public void done(ParseObject object, ParseException e) { // all fields of the object will now be available here. } });
2.3. Avoid count operations
The count operation query.count() and its variants (for example asynchronous one) take a hefty amount of time, which increases as your database grows.
There is an easy way to retrieve the number of objects without having to call these methods, which is to simply have a count variable stored in your database, which is incremented each time an object is added and decreased each time one is deleted. Moreover, it is possible to retrieve the number of objects, which can be done by fetching the variable and making the query very fast even on large datasets.
Let’s say you have a Review class and you need to track the number of objects it has. You can implement the Parse’s afterSave and afterDelete methods to update the counter, which will be represented as a separate Parse Object, called ReviewCount, which has a field called count.
Parse.Cloud.afterSave("Review", function(request) { var ReviewCount = Parse.Object.extend("ReviewCount"); var query = new Parse.Query(ReviewCount); query.first({ success: function(object) { object.increment("count"); object.save(); }, error: function(error) { console.log("Error: " + error.code + " " + error.message); } }); }); Parse.Cloud.afterDelete("Review", function(request) { var ReviewCount = Parse.Object.extend("ReviewCount"); var query = new Parse.Query(ReviewCount); query.first({ success: function(object) { object.decrement("count"); object.save(); }, error: function(error) { console.log("Error: " + error.code + " " + error.message); } }); });
2.4. Use client-side caching
For Parse Code running in IOS or Android, it is possible to use query caching. Caching queries will increase your mobile app’s performance, especially in cases where you want to display data while fetching the latest data from Parse. This will let you show data even when the user’s device is offline or when the app has just started and network requests have not yet completed.
To enable caching queries you need to change your query.cachePolicy. For Android you can write:
query.setCachePolicy(ParseQuery.CachePolicy.NETWORK_ELSE_CACHE);
For IOS Swift:
query.cachePolicy = .CacheElseNetwork
For IOS Objective-C:
query.cachePolicy = kPFCachePolicyNetworkElseCache;
Then you can continue to use your queries normally while having the better performance of caching queries.
To see all available cache policies, we recommend you to read the Parse Android or iOS documentation.
3. Productivity
Productivity is directly related to how your developers optimize their time to the development progress continuous and steady. A proper setup of the environment can make up for the loss of time in the beginning, by having more code being written and not just bug fixing, which tends to happen a lot in the later stages of your software. Productivity also allows for quicker deliveries of updates to your clients, thus improving their satisfaction.
In this section, we’ll show you some productivity tips that are some of the best guidelines to remember when designing your app. We’ll discuss a few ways on how to improve your app’s development productivity.
3.1. Use the command line interface
Back4App has its own Command Line Interface (CLI), which allows you to interact with your cloud code from the terminal. With it, you can perform actions in your Parse App like create a new app, develop and deploy cloud code, manage app releases, set the SDK version, etc.
This CLI is a concise and powerful way to control and interact with your Parse Server running on Back4App. It also makes scripting much easier, which opens possibilities for automating various processes, such as the deployment and the test routines.
To learn how to properly setup and how to use the Back4App CLI, we recommend you to read our Official CLI Docs.
3.2. Connect to a Continuous Integration Server
A Continuous Integration (CI) server is a place where the developer can use the features presented in CI easily. These features include merging all developer working copies to a shared repository several times a day.
CI is meant to prevent integration problems, ensure quality and eliminate rework. It also reduces breaking of code and ensures that the software is always in a state that can be deployed to users, thus making the deployment process much quicker.
To enable all these features, Back4App recommend you to use Travis CI server. More information on how to make this integration can be found at our Back4App CI Tutorial.
3.3. Know how to use the Back4App Logs
The Back4App Logs can help you in the process of finding and resolving bugs in your app. It can be used to see print statements made in the cloud code so that it’s possible for you to see the flow of execution of your code and find exactly what is happening.
To see your Logs, go to the Server Settings of your App, then click on the Settings button located in the Logs feature. There are two types of logs, the “Server Access Log” and the “Server System Log”.
The Server Access Log refers to EndPoint Access, like GET and POST requests. It is responsible for printing the type and received time of all REST requests your App has received. You can see if they are working correctly through the status code and the IP that is shown for each request call.
The Server System Log will log whenever your server is restarted, print the time it happened and the port the server is listening. It will also print any console.log(); type of print in your cloud code, effectively making it possible for you to print values of variables and debug your cloud code.
3.4. Use Back4App Analytics
Back4App Analytics is used to capture data from your app, such as its traffic, the push notifications sent, and the number of API calls made. These reports help you make informed decisions regarding your app’s performance.
Push related analytics can be seen in the Push menu on your Parse Dashboard, while requests related can be found in the Analytics menu. This analytics menu is made exclusively by Back4App and is not available on Parse.
Regarding requests, the analytics show values like Total Requests, Request Limit, Dropped Requests and Served Requests, which can be used to see if you are not losing performance because of your maximum plan usages. To see more information about Back4App plans, see our pricing page.
Final words
We hope you learned lots of new ideas and concepts from this article. As mentioned in the beginning, most of this information already exists in the docs, which should be consulted for more detailed reference info.
Now you can build your app more confidently and work in a safer, more stable and faster server, besides ensuring that the developing process is quicker and continuous.
Did you like this post? Sign Up Back4App for FREE.
What are the main security best practices working with Parse?
– Protect your master key
– Use https to make connections
– Validate data sent by users
What are the main performance best practices working with Parse?
– Use efficient queries
– Write restrictive queries
– Use client-side caching
What are the main productivity best practices working with Parse?
– Use the CLI – Command Line Interface
– Connect to a Continuous Integration Server
– Use your logs for troubleshooting