Skip to content

Create, read, update and delete Parse objects

Save, query, update and delete objects in your Back4app database, with the same four operations in every SDK, REST and GraphQL.

SDK
How-to guide9 min readUpdated Parse Server ≥ 6.0, not yet executed

On Back4app, every row is a Parse object stored in a class. You create one by setting fields on a Parse.Object and calling save(), read it with a Parse.Query, update it by changing fields and saving again, and remove it with destroy(). The same four operations exist in every SDK and over REST and GraphQL; only the syntax changes.

This guide uses a class called Todo with two fields, title (String) and done (Boolean). You do not need to create the class first: Back4app creates the class and its columns the first time you save an object, unless you have disabled client class creation in App Settings → Security.

A Back4app app and its keys, from Dashboard → App Settings → Security & Keys. Every SDK needs the Application ID plus one more key, and which one depends on the SDK:

SDK Second key Sent as
JavaScript, React, React Native JavaScript Key Parse.initialize(appId, jsKey)
Flutter, Android, iOS Client Key clientKey / X-Parse-Client-Key
REST REST API Key X-Parse-REST-API-Key
GraphQL Client Key X-Parse-Client-Key

Keep both values in environment variables. Never commit them, and never ship the Master Key in a client app: it bypasses every permission check.

The variable names differ per toolchain, because bundlers only expose variables carrying their own prefix:

SDK Variables
JavaScript (Node.js) PARSE_APP_ID, PARSE_JS_KEY
React (Vite) VITE_PARSE_APP_ID, VITE_PARSE_JS_KEY
React (Next.js) NEXT_PUBLIC_PARSE_APP_ID, NEXT_PUBLIC_PARSE_JS_KEY
React Native (Expo) EXPO_PUBLIC_PARSE_APP_ID, EXPO_PUBLIC_PARSE_JS_KEY
Flutter PARSE_APP_ID, PARSE_CLIENT_KEY via --dart-define
Android PARSE_APP_ID, PARSE_CLIENT_KEY via buildConfigField
iOS PARSE_APP_ID, PARSE_CLIENT_KEY via an .xcconfig file
REST PARSE_APP_ID, PARSE_REST_KEY
GraphQL PARSE_APP_ID, PARSE_CLIENT_KEY

Install the SDK if you have not done it yet:

Terminal window
npm install parse@^7 # Node.js 20 or later
  1. Initialize the SDK once, when the app starts. The server URL is the same for every app on Back4app.

    parse.js
    // Browser and bundlers: 'parse'. Node.js: 'parse/node.js'.
    import Parse from 'parse/node.js';
    Parse.initialize(process.env.PARSE_APP_ID, process.env.PARSE_JS_KEY);
    Parse.serverURL = 'https://parseapi.back4app.com';
    export default Parse;

    In Node.js the variables are PARSE_APP_ID and PARSE_JS_KEY. Other toolchains need their own prefix, listed in the table above.

  2. Set the fields and save. The server returns the objectId and createdAt of the new row.

    const todo = new Parse.Object('Todo');
    todo.set('title', 'Buy milk');
    todo.set('done', false);
    const saved = await todo.save();
    console.log(saved.id, saved.createdAt);
    Output
    “xKue915KBG” 2026-09-30T14:02:11.318Z
  3. Fetch one object by its objectId, or run a query with conditions, ordering and a limit. Queries return at most 100 objects by default; set limit for more, up to 1000. For anything beyond that, page through the results with skip, covered in Queries.

    find() returns an empty array when nothing matches, never null, so always check the length before reading the first element. If the array is exactly as long as your limit, there are probably more rows: call count() for the total, or fetch the next page with skip.

    // One object by id
    const todo = await new Parse.Query('Todo').get('xKue915KBG');
    console.log(todo.get('title')); // "Buy milk"
    // Open todos, newest first
    const query = new Parse.Query('Todo');
    query.equalTo('done', false);
    query.descending('createdAt');
    query.limit(10);
    const open = await query.find();
    open.forEach((t) => console.log(t.id, t.get('title')));
  4. Fetch the object, change the fields you need, and save. Only the changed fields are sent to the server, and updatedAt is refreshed.

    const todo = await new Parse.Query('Todo').get('xKue915KBG');
    todo.set('done', true);
    await todo.save();
    console.log(todo.updatedAt);

    If you already know the objectId, skip the fetch. createWithoutData builds a local reference to an existing row without reading it first, so this is one request instead of two. Use it when you only need to write:

    const Todo = Parse.Object.extend('Todo');
    const todo = Todo.createWithoutData('xKue915KBG');
    todo.set('done', true);
    await todo.save();
  5. Deleting is permanent. If other objects point to this one, the pointers keep the objectId but resolve to nothing.

    const todo = await new Parse.Query('Todo').get('xKue915KBG');
    await todo.destroy();

Open Dashboard → Database → Browser and select the Todo class. After step 2 you should see one row with title = "Buy milk" and done = false; after step 4 the same row shows done = true; after step 5 the class is empty.

Every operation on this page can fail, and the failure carries a numeric code that tells you what to do. Branch on the code rather than on the message, because messages change between Parse Server versions.

The code is always in the same place; the human-readable text is not:

Transport Shape Text field
SDKs (JS, Flutter, Android, Swift) { code, message } message
REST { "code": 101, "error": "Object not found." } error
GraphQL { message, extensions: { code } } message

A failure that never reached the server, such as no network or a wrong serverURL, is not a Parse.Error and has no code. Check for that first, otherwise you log Parse error undefined.

try {
await todo.save();
} catch (err) {
if (!(err instanceof Parse.Error)) {
// Network failure, wrong serverURL, or unset credentials.
console.error('Could not reach Back4app:', err.message);
throw err;
}
switch (err.code) {
case Parse.Error.OBJECT_NOT_FOUND: // 101
console.error('That todo no longer exists.');
break;
case Parse.Error.OPERATION_FORBIDDEN: // 119
console.error('Permission denied. Check the class CLP.');
break;
case Parse.Error.DUPLICATE_VALUE: // 137
console.error('A unique index rejected that value.');
break;
case Parse.Error.INVALID_SESSION_TOKEN: // 209
// Only meaningful when a user is logged in; a keys-only script has no session.
if (Parse.User.current()) await Parse.User.logOut();
break;
default:
console.error(`Parse error ${err.code}: ${err.message}`);
}
}
Code Meaning Cause and fix
101 Object not found The objectId does not exist, or the current user has no read access under the class’s ACL or CLP.
119 Permission denied Client class creation is disabled, or the CLP blocks the operation for this user. Enable it in App Settings → Security or adjust the CLP in the Database Browser.
137 Duplicate value for a unique field A unique index rejected the value. Query first, or catch the error and update instead.
209 Invalid session token The stored session expired or was revoked. If a user is logged in, log them out and in again. A keys-only script has no session and should not call logOut().

A wrong Application ID or key does not produce a Parse code at all: the request is rejected with HTTP 403 before it reaches the application layer. REST needs the REST API Key; Flutter, Android and iOS need the Client Key.

A minimal Node.js script that runs the four operations in order. Save it as crud.mjs and run node crud.mjs with PARSE_APP_ID and PARSE_JS_KEY set.

crud.mjs
import Parse from 'parse/node.js';
Parse.initialize(process.env.PARSE_APP_ID, process.env.PARSE_JS_KEY);
Parse.serverURL = 'https://parseapi.back4app.com';
try {
// Create
const todo = new Parse.Object('Todo');
todo.set('title', 'Buy milk');
todo.set('done', false);
await todo.save();
console.log('created', todo.id);
// Read
const open = await new Parse.Query('Todo')
.equalTo('done', false)
.descending('createdAt')
.limit(10)
.find();
console.log('open todos', open.map((t) => t.get('title')));
// Update the first result, if there is one
const first = open[0];
if (!first) {
console.log('nothing to update');
} else {
first.set('done', true);
await first.save();
console.log('updated', first.id, first.updatedAt);
// Delete
await first.destroy();
console.log('deleted', first.id);
}
} catch (err) {
if (err instanceof Parse.Error && err.code === Parse.Error.OPERATION_FORBIDDEN) {
console.error('Permission denied. Check the class CLP in the dashboard.');
} else if (err instanceof Parse.Error) {
console.error(`Parse error ${err.code}: ${err.message}`);
} else {
console.error('Could not reach Back4app:', err.message);
}
process.exitCode = 1;
}
Output

created xKue915KBG open todos [ ‘Buy milk’ ] updated xKue915KBG 2026-09-30T14:05:40.102Z deleted xKue915KBG

Prompt for Claude Code, Cursor or Copilot

Add Parse CRUD to this project using the Back4app backend. Read https://www.back4app.com/docs/guides/database/create-read-update-delete.md first. Use the SDK that matches this project’s language. Read PARSE_APP_ID and the SDK key from environment variables; never hardcode them. Server URL: https://parseapi.back4app.com Create a Todo class with title (String) and done (Boolean), then implement create, list open todos (newest first, limit 10), mark done, and delete. Handle Parse error codes 101, 119, 137 and 209 with clear messages.