Guide

Errors

What to throw, what the client sees, and which code means what.

The convention

This library uses catchError rather than try/catch around every await. It turns a rejected promise into a tuple, so the failure is a value you handle rather than a jump.

import {
  catchError, BeforeSave, validateOrThrow, CloudFunction,
} from 'parse-server-kit';

const [err, saved] = await catchError(product.save(null, { sessionToken }));
if (err) throw err;
return saved;

Use try/catch for synchronous work — JSON.parse, buffer handling — and for a whole-function boundary that must never throw, such as a cron job.

Throwing something useful

throw new Parse.Error(142, 'Price must be greater than zero');

A Parse.Error reaches the client as {code, error} with a sensible HTTP status. Anything else becomes a generic 141 with the message attached, which is fine for genuine bugs and poor for expected refusals.

CodeNameUse for
101OBJECT_NOT_FOUNDMissing — and what Parse returns when the ACL hides something
102INVALID_QUERYA malformed constraint
119OPERATION_FORBIDDENSigned in, but not allowed
141SCRIPT_FAILEDThe default for an uncaught error
142VALIDATION_ERRORInput a caller can correct. validateOrThrow uses this
202 / 203USERNAME_TAKEN / EMAIL_TAKENSignup collisions
209INVALID_SESSION_TOKENExpired or revoked session
5001CONFLICTThis library — somebody else changed it first
101 is deliberately ambiguous Parse answers "not found" for a row that does not exist and for one the ACL hides, on purpose — a distinct "forbidden" would confirm the record exists. When debugging, re-run with useMasterKey: if it appears, it was permissions.

Validation errors

@BeforeSave()
static async onBeforeSave(req: Parse.Cloud.BeforeSaveRequest<Product>) {
  validateOrThrow(req.object);   // 142, listing every failure at once
}

validateOrThrow checks every rule declared with @ParseField and reports them together, rather than one per round-trip. For a partial check without throwing, validateObject returns {valid, errors[]}.

Write conflicts

try {
  await job.save(null, { useMasterKey: true });
} catch (error) {
  if (error instanceof Parse.Error && error.code === CONFLICT) {
    // Reload, re-apply, retry - or surface CONFLICT_MESSAGE,
    // which is already phrased for an end user.
  }
  throw error;
}

A lost optimistic lock and a lost transaction race report the same code, because to the person looking at the screen they are the same event.

What the client receives

removeResultMiddleware unwraps Parse's {result: …} envelope on success. Errors pass through unchanged:

// success
{ "objectId": "abc123", "name": "Widget" }

// failure
{ "code": 142, "error": "name is required" }
StatusComes from
400Validation, and most Parse.Errors
403restrictRoutes refusing a blocked path
404Unknown route, or 101
405The HTTP method is not in methods
429rateLimit exceeded, with Retry-After

In practice

@CloudFunction({ methods: ['POST'], validation: { requireUser: true } })
static async publishArticle(req: Parse.Cloud.FunctionRequest) {
  const user = req.user!;

  const [findErr, article] = await catchError(
    new Parse.Query(Article).get(req.params.id, {
      sessionToken: user.getSessionToken(),
    })
  );
  // 101 already means "missing, or not yours" - let it through.
  if (findErr) throw findErr;

  if (article!.get('status') === 'published') {
    throw new Parse.Error(142, 'This article is already published');
  }
  if (!article!.get('body')) {
    throw new Parse.Error(142, 'Add some content before publishing');
  }

  article!.set('status', 'published');
  const [saveErr] = await catchError(
    article!.save(null, { sessionToken: user.getSessionToken() })
  );
  if (saveErr) throw saveErr;

  return article;
}

Three habits worth keeping: let permission errors stay ambiguous, use 142 for anything the caller can fix, and write messages a person could act on.

Logging

parse-server logs an uncaught error from a cloud function with the function name, the parameters and the user. Deliberate Parse.Error throws are logged too, so a noisy validation path is visible without extra instrumentation.

parse-server 8.5+ offers enableSanitizedErrorResponse, which strips detail from responses while keeping it in the logs — worth turning on in production so an internal failure does not describe itself to a caller.