Skip to content

Commit

Permalink
feat: Asynchronous initialization of Parse Server (parse-community#8232)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: This release introduces the asynchronous initialization of Parse Server to prevent mounting Parse Server before being ready to receive request; it changes how Parse Server is imported, initialized and started; it also removes the callback `serverStartComplete`; see the [Parse Server 6 migration guide](https://github.com/parse-community/parse-server/blob/alpha/6.0.0.md) for more details (parse-community#8232)
  • Loading branch information
dblythy authored Dec 21, 2022
1 parent db9941c commit 99fcf45
Show file tree
Hide file tree
Showing 21 changed files with 493 additions and 309 deletions.
5 changes: 3 additions & 2 deletions .babelrc
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
"presets": [
["@babel/preset-env", {
"targets": {
"node": "14"
}
"node": "14",
},
"exclude": ["proposal-dynamic-import"]
}]
],
"sourceMaps": "inline"
Expand Down
63 changes: 63 additions & 0 deletions 6.0.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Parse Server 6 Migration Guide <!-- omit in toc -->

This document only highlights specific changes that require a longer explanation. For a full list of changes in Parse Server 6 please refer to the [changelog](https://github.com/parse-community/parse-server/blob/alpha/CHANGELOG.md).

---

- [Import Statement](#import-statement)
- [Asynchronous Initialization](#asynchronous-initialization)

---

## Import Statement

The import and initialization syntax has been simplified with more intuitive naming and structure.

*Parse Server 5:*
```js
// Returns a Parse Server instance
const ParseServer = require('parse-server');

// Returns a Parse Server express middleware
const { ParseServer } = require('parse-server');
```

*Parse Server 6:*
```js
// Both return a Parse Server instance
const ParseServer = require('parse-server');
const { ParseServer } = require('parse-server');
```

To get the express middleware in Parse Server 6, configure the Parse Server instance, start Parse Server and use its `app` property. See [Asynchronous Initialization](#asynchronous-initialization) for more details.

## Asynchronous Initialization

Previously, it was possible to mount Parse Server before it was fully started up and ready to receive requests. This could result in undefined behavior, such as Parse Objects could be saved before Cloud Code was registered. To prevent this, Parse Server 6 requires to be started asynchronously before being mounted.

*Parse Server 5:*
```js
// 1. Import Parse Server
const { ParseServer } = require('parse-server');

// 2. Create a Parse Server instance as express middleware
const server = new ParseServer(config);

// 3. Mount express middleware
app.use("/parse", server);
```

*Parse Server 6:*
```js
// 1. Import Parse Server
const ParseServer = require('parse-server');

// 2. Create a Parse Server instance
const server = new ParseServer(config);

// 3. Start up Parse Server asynchronously
await server.start();

// 4. Mount express middleware
app.use("/parse", server.app);
```
57 changes: 42 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ A big *thank you* 🙏 to our [sponsors](#sponsors) and [backers](#backers) who

---

- [Flavors & Branches](#flavors--branches)
- [Flavors \& Branches](#flavors--branches)
- [Long Term Support](#long-term-support)
- [Getting Started](#getting-started)
- [Running Parse Server](#running-parse-server)
Expand All @@ -55,6 +55,8 @@ A big *thank you* 🙏 to our [sponsors](#sponsors) and [backers](#backers) who
- [Running Parse Server elsewhere](#running-parse-server-elsewhere)
- [Sample Application](#sample-application)
- [Parse Server + Express](#parse-server--express)
- [Parse Server Health](#parse-server-health)
- [Status Values](#status-values)
- [Configuration](#configuration)
- [Basic Options](#basic-options)
- [Client Key Options](#client-key-options)
Expand Down Expand Up @@ -136,13 +138,13 @@ Parse Server is continuously tested with the most recent releases of Node.js to

Parse Server is continuously tested with the most recent releases of MongoDB to ensure compatibility. We follow the [MongoDB support schedule](https://www.mongodb.com/support-policy) and [MongoDB lifecycle schedule](https://www.mongodb.com/support-policy/lifecycles) and only test against versions that are officially supported and have not reached their end-of-life date. We consider the end-of-life date of a MongoDB "rapid release" to be the same as its major version release.

| Version | Latest Version | End-of-Life | Compatible |
|-------------|----------------|---------------|--------------|
| MongoDB 4.0 | 4.0.28 | April 2022 | ✅ Yes |
| MongoDB 4.2 | 4.2.19 | April 2023 | ✅ Yes |
| MongoDB 4.4 | 4.4.13 | February 2024 | ✅ Yes |
| MongoDB 5 | 5.3.2 | October 2024 | ✅ Yes |
| MongoDB 6 | 6.0.2 | July 2025 | ✅ Yes |
| Version | Latest Version | End-of-Life | Compatible |
|-------------|----------------|---------------|------------|
| MongoDB 4.0 | 4.0.28 | April 2022 | ✅ Yes |
| MongoDB 4.2 | 4.2.19 | April 2023 | ✅ Yes |
| MongoDB 4.4 | 4.4.13 | February 2024 | ✅ Yes |
| MongoDB 5 | 5.3.2 | October 2024 | ✅ Yes |
| MongoDB 6 | 6.0.2 | July 2025 | ✅ Yes |

#### PostgreSQL

Expand Down Expand Up @@ -282,11 +284,11 @@ We have provided a basic [Node.js application](https://github.com/parse-communit
You can also create an instance of Parse Server, and mount it on a new or existing Express website:

```js
var express = require('express');
var ParseServer = require('parse-server').ParseServer;
var app = express();
const express = require('express');
const ParseServer = require('parse-server').ParseServer;
const app = express();

var api = new ParseServer({
const server = new ParseServer({
databaseURI: 'mongodb://localhost:27017/dev', // Connection string for your MongoDB database
cloud: './cloud/main.js', // Path to your Cloud Code
appId: 'myAppId',
Expand All @@ -295,8 +297,11 @@ var api = new ParseServer({
serverURL: 'http://localhost:1337/parse' // Don't forget to change to https if needed
});

// Start server
await server.start();

// Serve the Parse API on the /parse URL prefix
app.use('/parse', api);
app.use('/parse', server.app);

app.listen(1337, function() {
console.log('parse-server-example running on port 1337.');
Expand All @@ -305,6 +310,27 @@ app.listen(1337, function() {

For a full list of available options, run `parse-server --help` or take a look at [Parse Server Configurations](http://parseplatform.org/parse-server/api/master/ParseServerOptions.html).

## Parse Server Health

Check the Parse Server health by sending a request to the `/parse/health` endpoint.

The response looks like this:

```json
{
"status": "ok"
}
```

### Status Values

| Value | Description |
|---------------|-----------------------------------------------------------------------------|
| `initialized` | The server has been created but the `start` method has not been called yet. |
| `starting` | The server is starting up. |
| `ok` | The server started and is running. |
| `error` | There was a startup error, see the logs for details. |

# Configuration

Parse Server can be configured using the following options. You may pass these as parameters when running a standalone `parse-server`, or by loading a configuration file in JSON format using `parse-server path/to/configuration.json`. If you're using Parse Server on Express, you may also pass these to the `ParseServer` object as options.
Expand Down Expand Up @@ -461,7 +487,7 @@ The following paths are already used by Parse Server's built-in features and are
It’s possible to change the default pages of the app and redirect the user to another path or domain.

```js
var server = ParseServer({
const server = ParseServer({
...otherOptions,
customPages: {
Expand Down Expand Up @@ -851,7 +877,7 @@ Then, create an `index.js` file with the following content:
```js
const express = require('express');
const { default: ParseServer, ParseGraphQLServer } = require('parse-server');
const { ParseServer, ParseGraphQLServer } = require('parse-server');

const app = express();

Expand All @@ -875,6 +901,7 @@ app.use('/parse', parseServer.app); // (Optional) Mounts the REST API
parseGraphQLServer.applyGraphQL(app); // Mounts the GraphQL API
parseGraphQLServer.applyPlayground(app); // (Optional) Mounts the GraphQL Playground - do NOT use in Production

await parseServer.start();
app.listen(1337, function() {
console.log('REST API running on http://localhost:1337/parse');
console.log('GraphQL API running on http://localhost:1337/graphql');
Expand Down
3 changes: 2 additions & 1 deletion spec/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
"jequal": true,
"create": true,
"arrayContains": true,
"databaseAdapter": true
"databaseAdapter": true,
"databaseURI": true
},
"rules": {
"no-console": [0],
Expand Down
81 changes: 45 additions & 36 deletions spec/CLI.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,17 +219,14 @@ describe('execution', () => {
}
});

it('shoud start Parse Server', done => {
childProcess = spawn(binPath, [
'--appId',
'test',
'--masterKey',
'test',
'--databaseURI',
'mongodb://localhost/test',
'--port',
'1339',
]);
it('should start Parse Server', done => {
const env = { ...process.env };
env.NODE_OPTIONS = '--dns-result-order=ipv4first';
childProcess = spawn(
binPath,
['--appId', 'test', '--masterKey', 'test', '--databaseURI', databaseURI, '--port', '1339'],
{ env }
);
childProcess.stdout.on('data', data => {
data = data.toString();
if (data.includes('parse-server running on')) {
Expand All @@ -241,18 +238,24 @@ describe('execution', () => {
});
});

it('shoud start Parse Server with GraphQL', done => {
childProcess = spawn(binPath, [
'--appId',
'test',
'--masterKey',
'test',
'--databaseURI',
'mongodb://localhost/test',
'--port',
'1340',
'--mountGraphQL',
]);
it('should start Parse Server with GraphQL', async done => {
const env = { ...process.env };
env.NODE_OPTIONS = '--dns-result-order=ipv4first';
childProcess = spawn(
binPath,
[
'--appId',
'test',
'--masterKey',
'test',
'--databaseURI',
databaseURI,
'--port',
'1340',
'--mountGraphQL',
],
{ env }
);
let output = '';
childProcess.stdout.on('data', data => {
data = data.toString();
Expand All @@ -267,19 +270,25 @@ describe('execution', () => {
});
});

it('shoud start Parse Server with GraphQL and Playground', done => {
childProcess = spawn(binPath, [
'--appId',
'test',
'--masterKey',
'test',
'--databaseURI',
'mongodb://localhost/test',
'--port',
'1341',
'--mountGraphQL',
'--mountPlayground',
]);
it('should start Parse Server with GraphQL and Playground', async done => {
const env = { ...process.env };
env.NODE_OPTIONS = '--dns-result-order=ipv4first';
childProcess = spawn(
binPath,
[
'--appId',
'test',
'--masterKey',
'test',
'--databaseURI',
databaseURI,
'--port',
'1341',
'--mountGraphQL',
'--mountPlayground',
],
{ env }
);
let output = '';
childProcess.stdout.on('data', data => {
data = data.toString();
Expand Down
42 changes: 42 additions & 0 deletions spec/CloudCode.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';
const Config = require('../lib/Config');
const Parse = require('parse/node');
const ParseServer = require('../lib/index').ParseServer;
const request = require('../lib/request');
const InMemoryCacheAdapter = require('../lib/Adapters/Cache/InMemoryCacheAdapter')
.InMemoryCacheAdapter;
Expand Down Expand Up @@ -39,6 +40,47 @@ describe('Cloud Code', () => {
});
});

it('can load cloud code as a module', async () => {
process.env.npm_package_type = 'module';
await reconfigureServer({ appId: 'test1', cloud: './spec/cloud/cloudCodeModuleFile.js' });
const result = await Parse.Cloud.run('cloudCodeInFile');
expect(result).toEqual('It is possible to define cloud code in a file.');
delete process.env.npm_package_type;
});

it('cloud code must be valid type', async () => {
await expectAsync(reconfigureServer({ cloud: true })).toBeRejectedWith(
"argument 'cloud' must either be a string or a function"
);
});

it('should wait for cloud code to load', async () => {
await reconfigureServer({ appId: 'test3' });
const initiated = new Date();
const parseServer = await new ParseServer({
...defaultConfiguration,
appId: 'test3',
masterKey: 'test',
serverURL: 'http://localhost:12668/parse',
async cloud() {
await new Promise(resolve => setTimeout(resolve, 1000));
Parse.Cloud.beforeSave('Test', () => {
throw 'Cannot save.';
});
},
}).start();
const express = require('express');
const app = express();
app.use('/parse', parseServer.app);
const server = app.listen(12668);
const now = new Date();
expect(now.getTime() - initiated.getTime() > 1000).toBeTrue();
await expectAsync(new Parse.Object('Test').save()).toBeRejectedWith(
new Parse.Error(141, 'Cannot save.')
);
await new Promise(resolve => server.close(resolve));
});

it('can create functions', done => {
Parse.Cloud.define('hello', () => {
return 'Hello world!';
Expand Down
2 changes: 1 addition & 1 deletion spec/DefinedSchemas.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -631,7 +631,7 @@ describe('DefinedSchemas', () => {
const logger = require('../lib/logger').logger;
spyOn(DefinedSchemas.prototype, 'wait').and.resolveTo();
spyOn(logger, 'error').and.callThrough();
spyOn(Parse.Schema, 'all').and.callFake(() => {
spyOn(DefinedSchemas.prototype, 'createDeleteSession').and.callFake(() => {
throw error;
});

Expand Down
Loading

0 comments on commit 99fcf45

Please sign in to comment.