Parse is a Mobile Backend as a Service platform, owned by Facebook since 2013. In January of 2016, Parse announced that its hosted services would shut down completely on January 28, 2017.
Fortunately, Parse has also released an open source API server, compatible with the hosted service’s API, called Parse Server. Parse Server is under active development, and seems likely to attract a large developer community. It can be be deployed to a range of environments running Node.js and MongoDB.
This guide focuses on migrating a pre-existing Parse application to a standalone instance of Parse Server running on Ubuntu 14.04. It uses TLS/SSL encryption for all connections, using a certificate provided by Let’s Encrypt, a new Certificate Authority which offers free certificates. It includes a few details specific to DigitalOcean and Ubuntu 14.04, but should be broadly applicable to systems running recent Debian-derived GNU/Linux distributions.
Warning: It is strongly recommended that this procedure first be tested with a development or test version of the app before attempting it with a user-facing production app. It is also strongly recommended that you read this guide in conjunction with the official migration documentation.
This guide builds on How To Run Parse Server on Ubuntu 14.04. It requires the following:
sudo
userThe target server should have enough storage to handle all of your app’s data. Since Parse compresses data on their end, they officially recommend that you provision at least 10 times as much storage space as used by your hosted app.
Parse provides a migration tool for existing applications. In order to make use of it, we need to open MongoDB to external connections and secure it with a copy of the TLS/SSL certificate from Let’s Encrypt. Start by combining fullchain1.pem
and privkey1.pem
into a new file in /etc/ssl
:
You will have to repeat the above command after renewing your Let’s Encrypt certificate. If you configure auto-renewal of the Let’s Encrypt certificate, remember to include this operation.
Make sure mongo.pem
is owned by the mongodb user, and readable only by its owner:
Now, open /etc/mongod.conf
in nano
(or your text editor of choice):
Here, we’ll make several important changes.
First, look for the bindIp
line in the net:
section, and tell MongoDB to listen on all addresses by changing 127.0.0.1
to 0.0.0.0
. Below this, add SSL configuration to the same section:
# network interfaces
net:
port: 27017
bindIp: 0.0.0.0
ssl:
mode: requireSSL
PEMKeyFile: /etc/ssl/mongo.pem
Next, under # security
, enable client authorization:
# security
security:
authorization: enabled
Finally, the migration tool requires us to set the failIndexKeyTooLong
parameter to false
:
setParameter:
failIndexKeyTooLong: false
Note: Whitespace is significant in MongoDB configuration files, which are based on YAML. When copying configuration values, make sure that you preserve indentation.
Exit and save the file.
Before restarting the mongod
service, we need to add a user with the admin
role. Connect to the running MongoDB instance:
Create an admin user and exit. Be sure to replace sammy with your desired username and password with a strong password.
use admin
db.createUser({
user: "sammy",
pwd: "password",
roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]
})
exit
Restart the mongod
service:
Now that you have a remotely-accessible MongoDB instance, you can use the Parse migration tool to transfer your app’s data to your server.
We’ll begin by connecting locally with our new admin user:
You should be prompted to enter the password you set earlier.
Once connected, choose a name for the database to store your app’s data. For example, if you’re migrating an app called Todo, you might use todo
. You’ll also need to pick another strong password for a user called parse.
From the mongo
shell, give this user access to database_name
:
In a browser window, log in to Parse, and open the settings for your app. Under General, locate the Migrate button and click it:
You will be prompted for a MongoDB connection string. Use the following format:
mongodb://parse:password@your_domain_name:27017/database_name?ssl=true
For example, if you are using the domain example.com
, with the user parse
, the password foo
, and a database called todo
, your connection string would look like this:
mongodb://parse:foo@example.com:27017/todo?ssl=true
Don’t forget ?ssl=true
at the end, or the connection will fail. Enter the connection string into the dialog like so:
Click Begin the migration. You should see progress dialogs for copying a snapshot of your Parse hosted database to your server, and then for syncing new data since the snapshot was taken. The duration of this process will depend on the amount of data to be transferred, and may be substantial.
Once finished, the migration process will enter a verification step. Don’t finalize the migration yet. You’ll first want to make sure the data has actually transferred, and test a local instance of Parse Server.
Return to your mongo
shell, and examine your local database. Begin by accessing database_name and examining the collections it contains:
Sample Output for Todo AppTodo
_Index
_SCHEMA
_Session
_User
_dummy
system.indexes
You can examine the contents of a specific collection with the .find()
method:
Sample Output for Todo App> db.Todo.find()
{ "_id" : "hhbrhmBrs0", "order" : NumberLong(1), "_p_user" : "_User$dceklyR50A", "done" : false, "_acl" : { "dceklyR50A" : { "r" : true, "w" : true } }, "_rperm" : [ "dceklyR50A" ], "content" : "Migrate this app to my own server.", "_updated_at" : ISODate("2016-02-08T20:44:26.157Z"), "_wperm" : [ "dceklyR50A" ], "_created_at" : ISODate("2016-02-08T20:44:26.157Z") }
Your specific output will be different, but you should see data for your app. Once satisfied, exit mongo
and return to the shell:
With your app data in MongoDB, we can move on to installing Parse Server itself, and integrating with the rest of the system. We’ll give Parse Server a dedicated user, and use a utility called PM2 to configure it and ensure that it’s always running.
Use npm
to install the parse-server
utility, the pm2
process manager, and their dependencies, globally:
Instead of running parse-server
as root or your sudo
user, we’ll create a system user called parse:
Now set a password for parse:
You’ll be prompted to enter a password twice.
Now, use the su
command to become the parse user:
Change to parse’s home directory:
Create a cloud code directory:
Edit /home/parse/cloud/main.js
:
For testing purposes, you can paste the following:
Parse.Cloud.define('hello', function(req, res) {
res.success('Hi');
});
Alternatively, you can migrate any cloud code defined for your application by copying it from the Cloud Code section of your app’s settings on the Parse Dashboard.
Exit and save.
PM2 is a feature-rich process manager, popular with Node.js developers. We’ll use the pm2
utility to configure our parse-server
instance and keep it running over the long term.
You’ll need to retrieve some of the keys for your app. In the Parse dashboard, click on App Settings followed by Security & Keys:
Of these, only the Application ID and Master Key are required. Others (client, JavaScript, .NET, and REST API keys) may be necessary to support older client builds, but, if set, will be required in all requests. Unless you have reason to believe otherwise, you should begin by using just the Application ID and Master Key.
With these keys ready to hand, edit a new file called /home/parse/ecosystem.json
:
Paste the following, changing configuration values to reflect your MongoDB connection string, Application ID, and Master Key:
{
"apps" : [{
"name" : "parse-wrapper",
"script" : "/usr/bin/parse-server",
"watch" : true,
"merge_logs" : true,
"cwd" : "/home/parse",
"env": {
"PARSE_SERVER_CLOUD_CODE_MAIN": "/home/parse/cloud/main.js",
"PARSE_SERVER_DATABASE_URI": "mongodb://parse:password@your_domain_name:27017/database_name?ssl=true",
"PARSE_SERVER_APPLICATION_ID": "your_application_id",
"PARSE_SERVER_MASTER_KEY": "your_master_key",
}
}]
}
The env
object is used to set environment variables. If you need to configure additional keys, parse-server
also recognizes the following variables:
PARSE_SERVER_COLLECTION_PREFIX
PARSE_SERVER_CLIENT_KEY
PARSE_SERVER_REST_API_KEY
PARSE_SERVER_DOTNET_KEY
PARSE_SERVER_JAVASCRIPT_KEY
PARSE_SERVER_DOTNET_KEY
PARSE_SERVER_FILE_KEY
PARSE_SERVER_FACEBOOK_APP_IDS
Exit and save ecosystem.json
.
Now, run the script with pm2
:
Sample Output...
[PM2] Spawning PM2 daemon
[PM2] PM2 Successfully daemonized
[PM2] Process launched
┌───────────────┬────┬──────┬──────┬────────┬─────────┬────────┬─────────────┬──────────┐
│ App name │ id │ mode │ pid │ status │ restart │ uptime │ memory │ watching │
├───────────────┼────┼──────┼──────┼────────┼─────────┼────────┼─────────────┼──────────┤
│ parse-wrapper │ 0 │ fork │ 3499 │ online │ 0 │ 0s │ 13.680 MB │ enabled │
└───────────────┴────┴──────┴──────┴────────┴─────────┴────────┴─────────────┴──────────┘
Use `pm2 show <id|name>` to get more details about an app
Now tell pm2
to save this process list:
Sample Output[PM2] Dumping processes
The list of processes pm2
is running for the parse user should now be stored in /home/parse/.pm2
.
Now we need to make sure the parse-wrapper
process we defined earlier in ecosystem.json
is restored each time the server is restarted. Fortunately, pm2
can generate and install a script on its own.
Exit to your regular sudo
user:
Tell pm2
to install initialization scripts for Ubuntu, to be run as the parse user, using /home/parse
as its home directory:
[PM2] Spawning PM2 daemon
[PM2] PM2 Successfully daemonized
[PM2] Generating system init script in /etc/init.d/pm2-init.sh
[PM2] Making script booting at startup...
[PM2] -ubuntu- Using the command:
su -c "chmod +x /etc/init.d/pm2-init.sh && update-rc.d pm2-init.sh defaults"
System start/stop links for /etc/init.d/pm2-init.sh already exist.
[PM2] Done.
We’ll use the Nginx web server to provide a reverse proxy to parse-server
, so that we can serve the Parse API securely over TLS/SSL.
In the prerequisites, you set up the default
server to respond to your domain name, with SSL provided by Let’s Encrypt certificates. We’ll update this configuration file with our proxy information.
Open /etc/nginx/sites-enabled/default
in nano
(or your editor of choice):
Within the main server
block (it should already contain a location /
block) add another location
block to handle the proxying of /parse/
URLs:
. . .
# Pass requests for /parse/ to Parse Server instance at localhost:1337
location /parse/ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://localhost:1337/;
proxy_ssl_session_reuse off;
proxy_set_header Host $http_host;
proxy_redirect off;
}
Exit the editor and save the file. Restart Nginx so that changes take effect:
Output * Restarting nginx nginx
...done.
At this stage, you should have the following:
parse-server
running under the parse user on port 1337, configured with the keys expected by your apppm2
managing the parse-server
process under the parse user, and a startup script to restart pm2
on bootnginx
, secured with the Let’s Encrypt certificate, and configured to proxy connections to https://your_domain_name/parse
to the parse-server
instanceIt should now be possible to test reads, writes, and cloud code execution using curl
.
Note: The curl
commands in this section should be harmless when used with a test or development app. Be cautious when writing data to a production app.
You’ll need to give curl
several important options:
Option | Description |
---|---|
-X POST |
Sets the request type, which would otherwise default to GET |
-H "X-Parse-Application-Id: your_application_id" |
Sends a header which identifies your application to parse-server |
-H "Content-Type: application/json" |
Sends a header which lets parse-server know to expect JSON-formatted data |
-d '{json_data} |
Sends the data itself |
Putting these all together, we get:
curl -X POST \
-H "X-Parse-Application-Id: your_application_id" \
-H "Content-Type: application/json" \
-d '{"score":1337,"playerName":"Sammy","cheatMode":false}' \
https://your_domain_name/parse/classes/GameScore
{"objectId":"YpxFdzox3u","createdAt":"2016-02-18T18:03:43.188Z"}
Since curl
sends GET requests by default, and we’re not supplying any data, you should only need to send the Application ID in order to read some sample data back:
{"results":[{"objectId":"BNGLzgF6KB","score":1337,"playerName":"Sammy","cheatMode":false,"updatedAt":"2016-02-17T20:53:59.947Z","createdAt":"2016-02-17T20:53:59.947Z"},{"objectId":"0l1yE3ivB6","score":1337,"playerName":"Sean Plott","cheatMode":false,"updatedAt":"2016-02-18T03:57:00.932Z","createdAt":"2016-02-18T03:57:00.932Z"},{"objectId":"aKgvFqDkXh","score":1337,"playerName":"Sean Plott","cheatMode":false,"updatedAt":"2016-02-18T04:44:01.275Z","createdAt":"2016-02-18T04:44:01.275Z"},{"objectId":"zCKTgKzCRH","score":1337,"playerName":"Sean Plott","cheatMode":false,"updatedAt":"2016-02-18T16:56:51.245Z","createdAt":"2016-02-18T16:56:51.245Z"},{"objectId":"YpxFdzox3u","score":1337,"playerName":"Sean Plott","cheatMode":false,"updatedAt":"2016-02-18T18:03:43.188Z","createdAt":"2016-02-18T18:03:43.188Z"}]}
A simple POST with no real data to https://your_domain_name/parse/functions/hello
will run the hello()
function defined in /home/parse/cloud/main.js
:
curl -X POST \
-H "X-Parse-Application-Id: your_application_id" \
-H "Content-Type: application/json" \
-d '{}' \
https://your_domain_name/parse/functions/hello
{"result":"Hi"}
If you have instead migrated your own custom cloud code, you can test with a known function from main.js
.
Your next step will be to change your client application itself to use the Parse Server API endpoint. Consult the official documentation on using Parse SDKs with Parse Server. You will need the latest version of the SDK for your platform. As with the curl
-based tests above, use this string for the server URL:
https://your_domain_name/parse
Return to the Parse dashboard in your browser and the Migration tab:
Click the Finalize button:
Your app should now be migrated.
This guide offers a functional starting point for migrating a Parse-hosted app to a Parse Server install on a single Ubuntu system, such as a DigitalOcean droplet. The configuration we’ve described should be adequate for a low-traffic app with a modest userbase. Hosting for a larger app may require multiple systems to provide redundant data storage and load balancing between API endpoints. Even small projects are likely to involve infrastructure considerations that we haven’t directly addressed.
In addition to reading the official Parse Server documentation and tracking the GitHub issues for the project when troubleshooting, you may wish to explore the following topics:
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
Parse Server is an open source implementation of the Parse API for mobile applications. It can be be deployed to a range of environments running Node.js and MongoDB. This series focuses on running Parse Server infrastructure in generic GNU/Linux environments such as those provided by DigitalOcean.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Thank you for your guide. I have followed it step by step, omitting the ssl part as we’ll be using http.
When I try to call Parse server on my droplet using my appId i get always get a 404. If I don´t provide the appId a just get {“error”:“unauthorised”}
Can you help point me in the right direction?
Could you elaborate on your setup a bit? Are you still proxying through Nginx, or just hitting the
parse-server
endpoint directly?PM2 is setup to start parse-server on reboot. I hit the parse-server endpoint directly.
One thing that comes to mind is that, as written,
parse-server
is expecting to serve from the root ofhttp://your_domain_or_ip:1337/
, not fromhttp://your_domain_or_ip:1337/parse
, so if you’ve got the/parse
in your requests, it’ll fail. This distinction tripped me up a few times.If that’s not it, a Pastebin or Gist of your
ecosystem.json
(minus Master Key, database URI, and any other sensitive data) and some samplecurl
requests might be helpful.I think that must be it! I’ll test it out and get back to you. Thank you for your quick replies and sorry for my slow ones.
I have now tested it, and it works like a charm, thank you for your help!
This looks great Brennen! :)
Thanks a lot for all the work involved, I hope to give this a test soon.
Just a small note, under “Reading Data with a GET”, in the first example text, you have your testing domain instead of “<your_domain>”. Might trip people copy/pasting who don’t see it :)
Nice catch. Fixed. :)
I keep getting
script not found: /usr/bin/parse-server
Any way to fix this? This is the only thing stopping the server from running
Assuming that you installed
parse-server
globally, like so:…I can imagine there being a scenario where the script winds up somewhere else. If it’s anywhere in your path, you can try:
This comment has been deleted
I had this issue also. Turns out the correct path is
installed parse-server in /usr/lib/node_modules for me
I setup my parse server according to Parse’s website, then I found this article and followed the instruction to setup Nginx so I can communicate using HTTPS. However, I found some strange behaviour and wondering if you can provide some insight.
I am certain that I am hitting my Parse server as if I turn my server off the GET will receive a different response. Also, if I define a route in my index.js (as according to Parse’s example) I can actually reach it, for example, with the following code inserted in index.js (again, as according to Parse’s example), I get the desired response: app.get(‘/’, function(req, res) { res.status(200).send(‘I dream of being a web site.’); });
Wondering if you have some insight into this strange behaviour.
Thanks!
I ran the Parse server according to this article and now it works. Still wondering why it wasn’t working before though…
This guide focuses on using a globally-installed version of the
parse-server
script, rather than writing your own to mount the underlying module on an Express application (which is covered a bit in the preceding guide). There’s probably a bit of a disconnect between some of the specifics there.The technique here of using an Nginx
proxy_pass
directive should probably work fine with a custom Express application, but all the endpoints would need to match up.Got it, thank you!!! :)
I’ve followed this tutorial to the letter and I have ran into the same issue. It only works without SSL. How did you fix it?
I did everything as described in tutorial, but keep getting
Cannot POST /functions/hello
orCannot GET ...
. I haveufw
enabled, but I allowed 1337/tcp port, so this shouldn’t be related. How to get this working?Finally figured this out. Nginx configured to redirect from https://example.com/parse/ to http://localhost:1337/, but all the classes/functions etc. are located at http://localhost:1337/parse/. So in cURL I have to use this url: https://example.com/parse/parse/functions/hello. Or simply change nginx config line from this:
to this:
Thanks for posting your fix up here. It helps!
Thanks for fix! Why the tutorial is not updated accordingly?
Thanks for your comment! I figured out the same problem in parallel, and came back to share the same advice with the community only to see that you solved it before :) Thanks!
Thanks for this tutorial. I have managed to get all the way through, up to the last step. When I call my cloud functions or
https://mydomain.com/parse
I only get502 Bad Gateway
response.When I run
tail -f /var/log/nginx/error.log
I get
I’ve ran into the same issue after finishing the tutorial. Any ideas?
In my case was an error due to the change of the Path of the code during the migration. I was able to troubleshoot it using:
pm2 logs 0
Hope it helps.
http://stackoverflow.com/questions/35855175/parse-server-on-digitalocean-error-502/36124101#36124101
I also have the same issue. Did you manage to fix it?
No, I couldn’t resolve this issue. This is just one of many problems I’ve had trying to migrate to Parse server. Since many Parse users like myself are not server-savvy enough to set this up, let alone maintain or troubleshoot it, I am now looking at having to move to Firebase :(
In order to make Push Notification work, I had to go into my parse-server (/usr/local/bin/parse-server) and add something like:
Is there a way to do this in ecosystem.json?
Thanks in advance!
Good question. I didn’t think so at first, but the situation has changed quite a bit since I wrote this guide in February. From a quick glance at the parse-server repo and the wiki page on Push configuration, it seems like you could define a
PARSE_SERVER_PUSH
containing some JSON and accomplish the same thing.I’m specifically looking at this bit of option handling:
…so maybe try something like:
…with the latest
parse-server
. This gets a little bit silly, since it’s JSON inside a string in a JSON file which defines an environment variable. At this stage you’re likely better off defining options as command-line parameters, or (probably best) using Parse Server by mounting the module on a custom Express app, as described in the Parse Server Guide on the official wiki.This comment has been deleted
I finished this tutorial and everything works except cloud code. When I do a query in my cloud code the code just stops. When I Executed Example Cloud Code from this tutorial It worked well. Any help would be appreciated… The problem is not with just this query, everytime I set a query in cloud code it just stops… not even going to Success or Error
Thank you for this amazing guide. I have followed every step (i left encryption part away, don’t need it for the time being). i 've managed to migrate from Parse to my mongodb with success. The problem is that it seems impossible to get to the parse server. i’ve configured ecosystem.json with my application’s info then run pm2. the daemon seems good but still i can’t see any proccess listening to 1337 or 80 (80 wouldnt work i’ve skipped nginx). so i cannot test it properly. any kind of help would be so much appreciated! Thank you in advance
try to use a command pm2 logs (on the parse user) and it will show you where it is running, or if it is running…
Hello MazelTov the error i got is: parse-wrapper-0 (err): You must provide a serverURL! and its obvious. but where should i input this information? Thank you for all your help
i have problem with this one two, because serverURL is required if you wanna run cloud code and I have been strugling with this for over a week… try here sudo nano /usr/lib/node_modules/parse-server/bin/parse-server
i can see the file that ecosystem passes the vars to. . . but where should i put the serverUrl. and by serverURL it means something like “http://domain/parse” ??? Thank you a lot MazelTov
I really dont know… my ecosystem looks exactly the same as here and everything is running except cloud code, I think you should set this url only in nginx
OK I just updated my parse server to the latest version and I am getting the same error as you :-D if you will find the answer please share it
ok i will :)
i just tried to add it here sudo nano /usr/bin/parse-server (i did the global instalation) and added this line in the else statement options.serverURL = ‘http://…’; and I get rid of this error and parse is running again, the cloud code not…
This solved the error and now while i am trying to execute a curl code to get results from db i get {“error”:“unauthorized”}. ring any bells?
I used this article to install parse server & migrate. I am able to run the curl command in the localhost:1337 and it works like a charm. However when I run from outside the server I get Cannot POST /functions/hello - I looked up the nginx logs and I see a 404. Nginx config is exactly the same as described in this post.
Any help would be greatly appreciated. Thanks!
Thanks for tutorial. I need to validate User list. How one lists _User and _Role collections?
this seems to do the work: