npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

parse-server-kit

v3.1.0

Published

Decorator-driven toolkit for Parse Server: models, cloud functions, triggers, cron, REST routing, ACL, transactions, optimistic locking and Swagger, from TypeScript decorators.

Readme

parse-server-kit

A working backend in an afternoon, not a fortnight.

Decorator-driven toolkit for Parse Server. Write models and endpoints as TypeScript classes — get REST routes, OpenAPI docs, schema, indexes, validation, transactions and row-level security for free.

CI npm license


Why

Most backends are the same backend: users, roles, permissions, CRUD, file uploads, a search endpoint, some scheduled jobs, and an admin panel that needs to see more than everyone else.

Parse Server already ships that — auth, sessions, roles, row-level ACL, real-time subscriptions, file storage, push. What it doesn't ship is a pleasant way to write against it. Cloud code is untyped functions in a folder.

This kit is the missing layer:

@ParseClass('Product', {clp: {find: {[roleKey(UserRoles.ADMIN)]: true}}})
export default class Product extends BaseModel {
  @ParseField({type: 'String', required: true, unique: true})
  sku!: string;

  @ParseField({type: 'Number', min: 0})
  price!: number;

  @ParseField({type: 'Pointer', targetClass: 'Category'})
  category!: Category;
}

That one class gives you a typed model, a database schema, a unique index, a MongoDB validator, class-level permissions, and an OpenAPI schema. No migrations to write, no route file to update, no DTO to duplicate.

Honestly, compared to NestJS

| | NestJS | parse-server-kit | |---|---|---| | Auth, sessions, users, roles | you build it | ✅ built in | | Row-level permissions (ACL) | you build it | ✅ built in | | Real-time subscriptions | you wire websockets | ✅ LiveQuery | | File storage + adapters | you build it | ✅ built in | | Schema & migrations | Prisma/TypeORM | ✅ from decorators | | REST routes | @Controller | ✅ from method names | | OpenAPI | @nestjs/swagger | ✅ automatic | | DI container, modules | ✅ | ❌ | | Guards / interceptors / pipes | ✅ | ❌ (roles only) | | Rich DTO validation | ✅ class-validator | ⚠️ field-level only | | Ecosystem size | enormous | small |

Use NestJS if you need a DI container, module boundaries, or a large team with enforced architecture. Use this if you want the boring 80% of a backend to already exist and you'd rather ship features today.


Install

npm install parse-server-kit parse-server parse express reflect-metadata
npm install -D @types/parse

experimentalDecorators is mandatory — TypeScript 5 defaults to standard (TC39) decorators, which are a different feature. Without it every decorator silently misbehaves:

{"compilerOptions": {"experimentalDecorators": true}}

Prefer declare for model fields:

@ParseField({type: 'String'}) declare title: string;   // ✅ preferred
@ParseField({type: 'String'}) title!: string;          // works — repaired at runtime

@ParseField puts a getter/setter on the prototype; the declaration exists only to give TypeScript the type. The ! form emits a real class field that shadows that accessor whenever useDefineForClassFields is on — its default from target: ES2022 up.

Since 3.0.0 @ParseClass repairs that automatically, so either form works: the shadowing property is removed on construction and any value it held is routed through the accessor. Before 3.0.0 the ! form meant reads returned undefined and save() quietly sent nothing.

declare is still the form to write — it emits no field at all, so there is nothing to repair, and it is correct under every target. emitDecoratorMetadata is not needed.

psk new generates models with declare already.


Configuration

Optional. Every default matches what the library did before these were settings, so you can skip this entirely:

import {configureKit} from 'parse-server-kit';

configureKit({
  mountPath: '/api',          // where Parse Server is mounted (default '/parse')
  adminRole: 'Owner',         // role that manages _Role (default 'SuperAdmin')
  excludedPointerClasses: ['Attachment'],  // skipped by fromParams()
});

Roles are yours to define. roleKey accepts any string and keeps the literal type, so use your own enum:

export enum Roles {OWNER = 'Owner', MEMBER = 'Member', BILLING = 'Billing'}

@ParseClass('Invoice', {clp: {find: {[roleKey(Roles.BILLING)]: true}}})

Quickstart

npx parse-server-kit new my-api   # scaffolds and installs
cd my-api
npm run db:up             # MongoDB as a replica set, so transactions work
npm run dev

That's a working API with OpenAPI docs at /api-docs, a seeded user, and both mandatory tsconfig flags already set. The server prints a ready-to-paste curl on startup:

✓ Created my-api — 15 files

  Your API is running.
  Docs   http://localhost:1337/api-docs

    curl "http://localhost:1337/api/notes/listNotes" \
      -H "X-Parse-Application-Id: my-api"

No Docker? Put a MongoDB Atlas connection string in .env as DATABASE_URI and skip docker compose.

Why a replica set? A default MongoDB install is a standalone, and standalones refuse transactions — so @Transactional and @ParseVersionField would fail against one. The generated docker-compose.yml configures a single-node replica set to avoid that.

Once installed globally the command is psk:

npm i -g parse-server-kit
psk new my-api
psk --help

Writing it by hand

1. A model

import {ParseClass, ParseField, BaseModel, BeforeSave} from 'parse-server-kit';

@ParseClass('Product')
export default class Product extends BaseModel {
  @ParseField({type: 'String', required: true})
  name!: string;

  @ParseField({type: 'Number', min: 0})
  price!: number;

  @BeforeSave()
  static async onBeforeSave(req: Parse.Cloud.BeforeSaveRequest<Product>) {
    validateOrThrow(req.object);
  }
}

2. Endpoints — the method name is the route

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

@Route(Product)                                   // → /api/products/*
class ProductFunctions {
  @CloudFunction({methods: ['POST'], validation: {requireUser: true}})
  static async createProduct(req: Parse.Cloud.FunctionRequest) {
    const product = Product.fromParams(req.params);
    const [err, saved] = await catchError(product.save(null, {sessionToken: req.user!.getSessionToken()}));
    if (err) throw err;
    return saved;                                 // POST /api/products/createProduct
  }

  @CloudFunction({methods: ['GET']})
  static async listProducts(req: Parse.Cloud.FunctionRequest) {
    return new Parse.Query(Product).limit(Number(req.params.limit) || 20).find();
  }                                               // GET /api/products/listProducts
}

3. Boot — order is load-bearing; see CLAUDE.md.

importFiles(join(__dirname, 'models'));           // models FIRST
const parseServer = await initializeParseServer();

app.use(removeResultMiddleware);
app.use(mountPath, validateEntityRoutes);
app.use(conditionalJsonMiddleware);
app.use(mountPath, restrictRoutes);               // blocks /classes, /schemas, /batch
app.use(mountPath, parseServer.app);

CloudFunctionRegistry.initialize();               // registries AFTER mount
TriggerRegistry.initialize();
CronRegistry.initialize();
setupSwagger(app, {title: 'My API', version: '1.0.0'});

Your API is now at /api/products/*, documented at /api-docs, with /classes and /schemas closed off.


What's in the box

| | | |---|---| | Models | @ParseClass · @ParseField · BaseModel.fromParams() · typed getters | | Endpoints | @CloudFunction · @Route · role checks · per-function rate limiting | | Triggers | all 21 Parse trigger types, including file, config and LiveQuery | | Cron | @Cron with CronSchedule presets, timezones, runtime control | | Transactions | @Transactional() — every write in the call chain joins automatically | | Optimistic locking | @ParseVersionField() — one line, enforced in the database adapter | | ACL | implementACL() · role/owner/public rules · nested-image ACL sync | | Indexes | unique, compound, TTL, 2dsphere, text — all from decorators | | Validation | field constraints, enforced in code and as MongoDB $jsonSchema | | OpenAPI | generated from your decorators, no annotations needed |

Transactions that follow the call, not the request

Parse Server's own transaction keeps its session on the shared DatabaseController, so one cloud function's transaction swallows every unrelated request running at the same moment. This one lives in AsyncLocalStorage, so two callers each get their own:

@CloudFunction({methods: ['POST']})   // must be ABOVE
@Transactional()                      // must be BELOW — decorators apply bottom-up
static async placeOrder(req) {
  await order.save(null, {useMasterKey: true});
  await inventory.save(null, {useMasterKey: true});   // joins automatically
}                                                     // both land, or neither

Optimistic locking in one line

@ParseVersionField()
version!: number;

That's the whole feature. Every read carries its version, every save asserts it, the adapter turns the assertion into the write's filter, and a stale save is refused with CONFLICT — with no endpoint reading or writing the field.


Documentation

CLAUDE.md is the complete API reference: every export with its real signature, the boot-order contract, and — most usefully — a table of every way this library fails without raising an error. It ships inside the package, so it's also available to coding agents at node_modules/parse-server-kit/CLAUDE.md.

Requirements

| | | |---|---| | Node | ≥ 20.19 | | parse-server | ≥ 8.3 (optional peer) | | MongoDB | ≥ 7.0.16 for parse-server 9; replica set for transactions | | TypeScript | any, with experimentalDecorators: true |

Transactions and @ParseVersionField are MongoDB only. On Postgres the version field is declared but never enforced — and says so at boot.

Contributing

See CONTRIBUTING.md. Bug reports are most useful with your parse-server, parse, Node and MongoDB versions, and whether experimentalDecorators is set.

License

MIT © Waseem Alhalabi — see LICENSE.