For use cases in which a pre-defined schema is beneficial or required, you can define class fields, indexes, Class Level Permissions and more.
You can use Defined Schema as in the following example.
const { ParseServer } = require("parse-server");
const UserSchema = {
className: "_User",
fields: {
birthDate: { type: "Date" },
firstname: { type: "String", required: true },
lastname: { type: "String", required: true },
tags: { type: "Array" },
location: { type: "GeoPoint" },
city: { type: "Pointer", targetClass: "City" },
friends: { type: "Relation", targetClass: "_User" },
zone: { type: "Polygon" },
},
indexes: {
tagsIndex: { tags: 1 },
// The special prefix _p_ is used to create indexes on pointer fields
cityPointerIndex: { _p_city: 1 },
tagAndCityIndex: { _p_city: 1, tags: 1 },
},
classLevelPermissions: {
find: { requiresAuthentication: true },
count: { "role:Admin": true },
get: { requiresAuthentication: true },
update: { requiresAuthentication: true },
create: { "role:Admin": true },
delete: { "role:Admin": true },
protectedFields: {
// These fields will be protected from all other users. AuthData and password are already protected by default
"*": ["authData", "emailVerified", "password", "username"],
},
},
};
const City = {
className: "City",
fields: {
name: { type: "String", required: true },
location: { type: "GeoPoint" },
country: { type: "Pointer", targetClass: "Country" },
},
classLevelPermissions: {
find: { requiresAuthentication: true },
count: { requiresAuthentication: true },
get: { requiresAuthentication: true },
// Only a user linked to the Admin Parse Role is authorized to manage cities
update: { "role:Admin": true },
create: { "role:Admin": true },
delete: { "role:Admin": true },
},
};
const Country = {
className: "Country",
fields: {
name: { type: "String", required: true },
},
classLevelPermissions: {
find: { requiresAuthentication: true },
count: { requiresAuthentication: true },
get: { requiresAuthentication: true },
// An empty object means that only master key is authorized to manage countries
update: {},
create: {},
delete: {},
},
};
ParseServer.start({
databaseURI: "mongodb://your.mongo.uri",
appId: "myAppId",
masterKey: "mySecretMasterKey",
serverURL: "http://localhost:1337/parse",
port: 1337,
publicServerURL: "http://localhost:1337/parse",
// Define schemas of Parse Server
schema: {
definitions: [User, City, Country],
// If set to `true`, the Parse Server API for schema changes is disabled and schema
// changes are only possible by redeployingParse Server with a new schema definition
lockSchemas: true,
// If set to `true`, Parse Server will automatically delete non-defined classes from
// the database; internal classes like `User` or `Role` are never deleted.
strict: true,
// If set to `true`, a field type change will cause the field including its data to be
// deleted from the database, and then a new field to be created with the new type
recreateModifiedFields: false,
// If set to `true`, Parse Server will automatically delete non-defined class fields;
// internal fields in classes like User or Role are never deleted.
deleteExtraFields: false,
},
serverStartComplete: () => {
// Parse Server is ready with up-to-date schema
parseServer.expressApp.get("/ready", (req: any, res: any) => {
res.send("true");
});
},
});
Defined Schemas is a feature in Parse Server that allows you to define the structure of your data in the database and ensure that it stays consistent. When the server starts up, it compares the current existing schemas to the Defined Schemas that you have provided, and applies any necessary changes to bring them in line with each other.
To use Defined Schemas on your existing Parse Server installation, follow the steps below carefully:
If you already have an hosted or deployed Parse Dashboard, you can skip to step 2. If not, you can follow the “Getting Started” guide for the Parse Dashboard: Parse Dashboard Getting Started
To access the API Console and JS Console in the Parse Dashboard:
const schemas = await Parse.Schema.all();
console.log(JSON.stringify(schemas));
After running this code, you should see a JSON string of your current schemas displayed in the console, including their CLPs (Class Level Permissions), indexes, and fields. This will give you an overview of the current structure of your data in the database.
Follow the “Getting Started” section of Defined Schema and paste your extracted JSON string. With luck your IDE, will reformat the JSON for you. Otherwise you can easily find online tools to convert JSON string to JS Array/Object.
As a best practice, before any deployment that involves Schema changes, perform a full database backup to prevent any data loss and to be able to roll back if something goes wrong.
Once the backup is done, you can deploy your new Parse Server. After the deployment, you can check the Parse Dashboard to ensure that all of your classes, fields, indexes, and CLPs are correct.
If something goes wrong during the deployment, you can roll back your database, deploy the previous version of your Parse Server, adjust and review your Defined Schemas, and then repeat steps 4 and 5.
If you are using Defined Schemas, it is recommended to set up a CI/CD (Continuous Integration/Continuous Deployment) process to automate your deployments. In this case, you should use the lockSchemas: true option to prevent any conflicts with create/update API calls sent to the Schema API from the Parse Dashboard or other SDKs.
To create or update schemas (fields, indexes, CLPs) in the future, you will need to make the necessary changes in your Parse Server code and deploy a new version of your app.
Parse will never delete these fields on ALL classes if not provided in a class schema
objectId
createdAt
updatedAt
ACL
Parse Server will never delete the following fields from any class, even if these fields are not defined in a class schema.
_User
username
password
email
emailVerified
authData
_Installation
installationId
deviceToken
channels
deviceType
pushType
GCMSenderId
timeZone
localeIdentifier
badge
appVersion
appName
appIdentifier
parseVersion
_Role
name
users
roles
_Session
user
installationId
sessionToken
expiresAt
createdWith
_Product
productIdentifier
download
downloadName
icon
order
title
subtitle
_PushStatus
pushTime
source
query
payload
title
expiry
expiration_interval
status
numSent
numFailed
pushHash
errorMessage
sentPerType
failedPerType
sentPerUTCOffset
failedPerUTCOffset
count
_JobStatus
jobName
source
status
message
params
finishedAt
_JobSchedule
jobName
description
params
startAfter
daysOfWeek
timeOfDay
lastRun
repeatMinutes
_Audience
objectId
name
query
lastUsed
timesUsed
_Idempotency
reqId
expire
These field types are available on a Parse Schema.
required
: boolean
, forces the field to be set on create and update, is false
by default.
defaultValue
: any
, a value used by Parse Server when you create a Parse Object if the field is not provided.
targetClass
: string
, a Parse Class name used by Parse Server to validate the Pointer
/Relation
✅: Supported ❌: Not Supported
Type | – required – | – defaultValue – | – targetClass – |
---|---|---|---|
String | ✅ | ✅ | ❌ |
Boolean | ✅ | ✅ | ❌ |
Date | ✅ | ✅ | ❌ |
Object | ✅ | ✅ | ❌ |
Array | ✅ | ✅ | ❌ |
GeoPoint | ✅ | ✅ | ❌ |
File | ✅ | ✅ | ❌ |
Bytes | ✅ | ✅ | ❌ |
Polygon | ✅ | ✅ | ❌ |
Relation | ❌ | ❌ | ✅ (required) |
Pointer | ✅ | ❌ | ✅ (required) |
Example:
const UserSchema = {
className: "_User",
fields: {
birthDate: { type: "Date" },
firstname: { type: "String", required: true },
lastname: { type: "String", required: true },
tags: { type: "Array" },
location: { type: "GeoPoint" },
city: { type: "Pointer", targetClass: "City" },
friends: { type: "Relation", targetClass: "_User" },
zone: { type: "Polygon" },
},
};
To optimize the Parse Server performance you can define indexes and compound indexes. Parse Server does not support indexes on special _Join
classes used under the hood by the Relation
type.
To define an index on a Pointer
field you need to use the special notation _p_<FIELDNAME>
. For example, if you define city: { type: "Pointer", targetClass: "City" }
in your fields
you can define an index on this pointer with cityIndexExample: { _p_city: true }
.
Example:
const UserSchema = {
className: "_User",
fields: {
tags: { type: "Array" },
city: { type: "Pointer", targetClass: "City" },
},
indexes: {
tagsIndex: { tags: 1 },
cityPointerIndex: { _p_city: 1 },
tagAndCityIndex: { _p_city: 1, tags: 1 },
},
};
Setting Class Level Permissions through Defined Schema is a good first step into security systems available on Parse Server.
These CLP parameters are available:
find
: Control search permissionsget
: Control direct ID get permissioncount
: Control counting objects permissioncreate
: Create permissionupdate
: Update permissiondelete
: Delete permissionprotectedFields
: Control get permission at field levelYou can set each CLP parameter to add a first strong security layer. This security layer will be applied on the Parse Class and will cover all Parse Objects of the Parse Class.
Note: If you update CLP you do not need to update Parse Objects. CLP is a security layer at Class Level not Object Level. For Object Level permission you can look to ALCs. Use CLPs combined with ACLs to deeply secure your Parse Server.
Available options for CLP parameters:
role:<roleName>
: If you are making use of Parse Roles you can set the permission based on a role.”requiresAuthentication
: If set to true
only authenticated users will have the permission.*
: Everybody has the permission.{}
: If you set the CLP key to {}
, for example: create: {}
. Then only calls with Parse Server Master Key will have the permission.This CLP parameter allows you to restrict access to fields to specific Parse users.
We will take the Parse User Class as an example.
// className: '_User'
{
protectedFields: {
"*": ["authData", "emailVerified", "password", "username"],
},
}
Listed keys under *
will be protected from all users. By default, authData
, emailVerified
, password
are protected. But in the above example we protect username
from all users. So a Parse User, even authenticated will not be able to get the username
of a another Parse User.
protectedFields
could be also combined as in the following example:
{
protectedFields: {
"*": ["authData", "emailVerified", "password", "username", "phone", "score"],
"role:Admin": ["password", "authData", "emailVerified"],
"role:VerifiedUser": ["password", "authData", "emailVerified", "score"],
},
}
In the example above, a Parse User who is a member of the Parse Role Admin
will be able to get the phone
and score
of another Parse User. A Parse User member of the Parse Role VerifiedUser
can only get phone
.
If a Parse User is member of VerifiedUser
and Admin
, he will have access to phone
and score
.
An array of your Defined Parse Classes.
You can set the strict
option to true
if you want Parse Server to delete all Parse Objects when you remove a Defined Parse Class from your definitions
. Data stored in removed classes will be lost.
By default strict
is false
. If you often change your schemas be aware that you will have stale data classes in your database. You will need to delete these classes (collection for MongoDB, table for Postgres) manually, through your database CLI/UI.
You can set the deleteExtraFields
option to true
if you want Parse Server to delete a removed Defined Parse Class field from your database. Data stored in the removed field will be lost.
By default deleteExtraFields
is false
. Be aware that you will have stale data fields in your database since Parse Server will not delete field data automatically. You will need to delete these fields manually.
You can set the recreateModifiedFields
option to true
if you want Parse Server to clean field data before updating the field type when the field type is modified. For example when changing from String
to Number
. Data stored under the modified field will be lost.
recreateModifiedFields
defaults to false
. Be aware that if you set recreateModifiedFields
to true
and do not perform some data migration, you can result with data type inconsistency on the modified field.
Good practice would be to create the new field of the new type, and then create a Parse Cloud Job to migrate old field data to the newly created field.
You can set the lockSchemas
option to true
if you want to prevent any schema manipulation and to lock the schema as defined in the Parse Server configuration. If this options is true
any create, update and delete request will be denied by the Parse Server API, even with the master key. You will not be able to manipulate indexes
, classLevelPermissions
, fields
.
A function called before Parse Server performs schema updates based on the definitions
option
A function called after Parse Server performed schema updates based on the definitions
option