Overview of Papr
mainmongodb driver, offering a more structured way to define models and schemas while maintaining high performance.repository·main·Indexed 19 days ago
https://github.com/plexinc/paprA lightweight TypeScript library for MongoDB that provides strong validation and type safety by leveraging MongoDB's native JSON Schema validation. It acts as a thin wrapper around the official mongodb NodeJS driver, offloading runtime validation to the database server for all operations, including inserts, updates, and bulkWrite.
mongodb driver, offering a more structured way to define models and schemas while maintaining high performance.A Model is the primary public interface in papr for interacting with a specific MongoDB collection. It provides a type-safe wrapper around standard MongoDB collection methods, ensuring that queries, updates, and insertions adhere to a defined schema.
To create a model, you first define a schema using schema() and then pass it to papr.model() along with the collection name.
const userSchema = schema({
active: types.boolean(),
age: types.number(),
firstName: types.string({ required: true }),
lastName: types.string({ required: true }),
});
const User = papr.model('users', userSchema);The defaults option in the schema function allows you to specify values that should be applied to documents. It supports three modes:
Date()).async function that returns a Promise resolving to a default object.Note on Enums: When using const enums in static defaults, you must use a full type cast to satisfy TypeScript.
import { schema, types } from 'papr';
// 1. Static defaults with Enum casting
const statuses = ['processing', 'shipped'] as const;
type Status = (typeof statuses)[number];
const orderSchema = schema(
{
_id: types.number({ required: true }),
user: types.objectId({ required: true }),
status: types.enum(statuses, { required: true }),
},
{
defaults: {
status: 'processing' as Status,
},
}
);
// 2. Synchronous dynamic defaults
const userSchemaSync = schema({
active: types.boolean(),
birthDate: types.date(),
firstName: types.string({ required: true }),
lastName: types.string({ required: true }),
}, {
defaults: () => ({
birthDate: new Date(),
})
});
// 3. Asynchronous dynamic defaults
const userSchemaAsync = schema({
active: types.boolean(),
birthDate: types.date(),
firstName: types.string({ required: true }),
lastName: types.string({ required: true }),
}, {
defaults: async () => ({
birthDate: await Promise.resolve(new Date()),
})
});Papr v11 uses enhanced strict types (PaprFilter and PaprUpdateFilter) to provide type safety for queries and updates, including support for dot notation.
Important Compatibility Note:
PaprFilter is not directly compatible with the standard Filter type from the mongodb driver. If you need to interact directly with a MongoDB collection via model.collection, you must cast your Papr filter to the standard Filter type.
import { Filter } from 'mongodb';
import { PaprFilter } from 'papr';
import User, { UserDocument } from './user';
const filter: PaprFilter<UserDocument> = {
firstName: 'John',
};
// Use directly with Papr model methods
await User.find(filter);
// Cast to Filter when using the underlying driver collection
await User.collection.find(filter as Filter<UserDocument>);While Mongoose is a full-featured ORM, Papr is designed as a 'paper-thin' layer over the MongoDB driver for performance and simplicity.
Key differences include:
populate (joins), conditional default values, and virtuals to maintain speed and simplicity.bulkWrite) by default.Papr uses a single schema definition to provide two layers of protection:
bulkWrite) are validated at the database level.This approach differs from libraries like Mongoose, which perform validation within the application runtime.
Papr is a lightweight TypeScript library that provides strong validation and type safety for MongoDB by leveraging MongoDB's native JSON Schema validation. It acts as a thin wrapper around the official mongodb NodeJS driver.
To use Papr, you initialize it with a MongoDB database instance, define models using papr.model() with a schema, and call updateSchemas() to apply those schemas to the MongoDB server. Once configured, you can use the model to perform queries like .find() with full TypeScript support.
import { MongoClient } from 'mongodb';
import Papr, { schema, types } from 'papr';
const papr = new Papr();
const connection = await MongoClient.connect('mongodb://localhost:27017');
// Initialize papr with a specific database
papr.initialize(connection.db('test'));
// Define a model with a schema
const User = papr.model('users', schema({
age: types.number(),
firstName: types.string({ required: true }),
lastName: types.string({ required: true }),
}));
// Apply the schema to the MongoDB collection
await papr.updateSchemas();
// Query the collection with type safety
const johnWick = await User.find({ firstName: 'John', lastName: 'Wick' });To use Papr, you must connect to a MongoDB server (v3.6+) and initialize the Papr instance with a MongoDB database object using papr.initialize(db). It is also recommended to call papr.updateSchemas() to ensure your MongoDB collections are synchronized with your defined schemas.
Prerequisites:
import { MongoClient } from 'mongodb';
import Papr from 'papr';
const papr = new Papr();
export async function connect() {
const client = await MongoClient.connect('mongodb://localhost:27017');
// Initialize with a specific database
papr.initialize(client.db('test'));
// Sync schemas with MongoDB
await papr.updateSchemas();
}To enforce schema validation at the database level, use papr to sync your TypeScript schemas to MongoDB's JSON Schema validator. This is done by calling updateSchema on a model.
Calling papr.updateSchema(UserModel) issues a collMod command to MongoDB, applying a $jsonSchema validator to the collection. Once applied, any write operations (like update) that violate the schema will either fail (if MongoDB's validationAction is set to error) or log an error (if validationAction is set to warn).
papr.updateSchema(UserModel);papr.updateSchema(UserModel);Models in Papr are created by defining a schema using schema() and the provided types helpers. You can then register this schema with the Papr instance using papr.model(collectionName, schema).
This approach provides two layers of validation:
import { types, schema } from 'papr';
import papr from './papr';
const userSchema = schema({
age: types.number(),
firstName: types.string({ required: true }),
lastName: types.string({ required: true }),
});
// Export the TypeScript type for use in your application
export type UserDocument = (typeof userSchema)[0];
// Create the model for the 'users' collection
const User = papr.model('users', userSchema);
export default User;Schemas in papr define the expected shape of documents in a MongoDB collection, providing both runtime validation (via JSON Schema) and compile-time type safety (via TypeScript).
To define a schema, use the schema function and the types object. You can specify if a field is required using the { required: true } option within the type definition.
After defining a schema, you should derive two key components:
papr model instance used to interact with the collection.import { schema, types } from 'papr';
const papr = new Papr();
const userSchema = schema({
age: types.number(),
firstName: types.string({ required: true }),
lastName: types.string({ required: true }),
});
// Derive the Document type
type UserDocument = (typeof userSchema)[0];
// Create the Model
const UserModel = papr.model('users', userSchema);import { schema, types } from 'papr';
const papr = new Papr();
const userSchema = schema({
age: types.number(),
firstName: types.string({ required: true }),
lastName: types.string({ required: true }),
});
type UserDocument = (typeof userSchema)[0];
const UserModel = papr.model('users', userSchema);When migrating from Mongoose to Papr, note that Papr is not a full ORM. You must manually handle custom instance/static methods, query helpers, and index definitions.
Key migration differences:
Timestamp support is identical to Mongoose. Enable it in the schema options.
In Mongoose, defaults are defined per-property. In Papr, defaults are defined in the schema options object. Defaults are only applied to paths where no value is set during insertion.
Mongoose automatically adds a versionKey (default __v). When migrating, you must either remove this key from your existing MongoDB collections or explicitly include it in your Papr schema to prevent conflicts.