Monarch ORM
Monarch ORM is a type-safe ORM for MongoDB, designed to provide a seamless and efficient way to interact with your MongoDB database in a type-safe manner. Monarch ensures that your data models are strictly enforced, reducing the risk of runtime errors and enhancing code maintainability.
Table of Contents
Features
- Strongly Typed: Ensures type safety across your MongoDB operations.
- Easy Integration: Simple setup and configuration.
- Powerful Schema Modifiers: Define schemas with optional and required fields.
- Intuitive API: Designed to be easy to use and understand.
Installation
NPM:
npm install monarch-orm
Or Yarn:
yarn add monarch-orm
Basic Usage
import { boolean, createClient, createDatabase, createSchema, number, string } from "monarch-orm";
const UserSchema = createSchema("users", {
name: string().nullable(),
email: string().lowercase().optional(),
age: number().optional().default(10),
isVerified: boolean(),
});
const client = createClient(/)
const { collections } = createDatabase(client.db(), {
users: UserSchema,
});
const newUser = await collections.users
.insert()
.values({
name: "anon",
email: "anon@gmail.com",
age: 0,
isVerified: true,
})
.exec();
const users = await collections.users.find().where({}).exec();
Quick Start
Defining Schemas and connecting to the database
Use the createSchema function to define the structure of your model. Specify the fields and their types, using the available types and modifiers.
const UserSchema = createSchema("users", {
name: string(),
isVerified: boolean(),
});
Create a database instance using any client you deem fit and drop it into the createDatabase function
Or you can use the built-in createClient function.
Then you pass your schemas to the second arguement
const { collections } = createDatabase(client.db(), {
users: UserSchema,
});
Inserting Documents
You can insert new documents into your collection using the insert method. Ensure that the data conforms to the defined schema.
Example: Inserting a new user
const newUser = await collections.users
.insert()
.values({
name: "Alice",
email: "alice@example.com",
age: 25,
isVerified: true,
})
.exec();
Querying Documents
Retrieve documents from your collection using the find or findOne methods.
Example: Querying all users
const users = await collections.users.find().where({}).exec();
console.log(users);
const users = await collections.users.find({}).exec();
console.log(users);
const user = await collections.users.find().where({
name: "Alice"
}).exec();
console.log(users);
const user = await collections.users.findOne({
name: "Alice"
}).exec();
console.log(users);
Updating Documents
Update documents in your collection using the update method. You can update a single document or multiple documents based on a filter.
Example: Updating a single user's email
const updatedUser = await collections.users
.updateOne()
.set({
email: "alice.updated@example.com",
})
.where({
name: "Alice",
})
.exec();
console.log(updatedUser);
Example: Updating multiple users' isVerified field
const updatedUsers = await collections.users
.updateMany()
.set({
isVerified: true,
})
.where({
isVerified: false,
})
.exec();
console.log(updatedUsers);
Note: The update method returns the number of documents updated.
Alternative setup
You can also decentralize the models
const { db } = createDatabase(client);
const UserSchema = createSchema("users", {
name: string(),
isVerified: boolean(),
});
const UserModel = db(UserSchema);
export default UserModel;
And use it like this
const user = await UserModel.findOne({
name: "Alice"
}).exec();
console.log(users);
Types
Primitives
String - string()
Defines a field that accepts string values.
const UserSchema = createSchema("users", {
name: string().required(),
});
.lowercase()
: Transforms the value to lowercase before storing..uppercase()
: Transforms the value to uppercase before storing.
const UserSchema = createSchema("users", {
name: string().lowercase(),
});
Number - number()
Defines a field that accepts numeric values.
const UserSchema = createSchema("users", {
age: number().optional(),
});
Boolean - boolean()
Defines a field that accepts boolean values (true or false).
const UserSchema = createSchema("users", {
isVerified: boolean(),
});
Date - date()
Defines a field that accepts JavaScript Date objects.
const UserSchema = createSchema("users", {
birthDate: date(),
});
Date String - dateString()
Defines a field that accepts date strings in ISO format.
const UserSchema = createSchema("users", {
registrationDate: dateString(),
});
General Modifiers
.nullable()
: Allows the field to accept null values..default()
: Sets a default value if none is provided..optional()
: Makes the field optional, allowing it to be omitted.
Literals
The literal()
type allows you to define a schema with fixed possible values, similar to enums in TypeScript. This is useful for enforcing specific, predefined values for a field.
const UserRoleSchema = createSchema("userRoles", {
role: literal("admin", "moderator", "customer"),
});
const user = {
role: "admin",
};
const invalidUser = {
role: "guest",
};
Objects
const UserSchema = object({
name: string(),
age: number(),
});
type User = InferSchemaInput<typeof UserSchema>;
type User = {
name: string;
age: number;
};
Records
A record()
allows you to define a flexible schema where each user can have a varying number of subjects and grades without needing to define a fixed schema for each subject.
const UserSchema = createSchema("users", {
name: string().required(),
email: string().required(),
grades: record(number()),
});
const { collections } = createDatabase(client.db(), {
users: UserSchema,
});
const newUser = await collections.users
.insert()
.values({
name: "Alice",
email: "alice@example.com",
grades: {
math: 90,
science: 85,
history: 88,
},
})
.exec();
const user = await collections.users.findOne().where({ email: "alice@example.com" }).exec();
console.log(user.grades);
Arrays
const ResultSchema = object({
name: string(),
scores: array(number()),
});
type Result = InferSchemaInput<typeof ResultSchema>;
type Result = {
name: string;
scores: number[];
};
Tuples
Unlike arrays, A tuple()
has a fixed number of elements but each element can have a different type.
const ControlSchema = object({
location: tuple([number(), number()]),
});
type Control = InferSchemaInput<typeof ControlSchema>;
type Control = {
location: [number, number];
};
Tagged Union
The taggedUnion()
allows you to define a schema for related types, each with its own structure, distinguished by a common "tag" field. This is useful for representing variable types in a type-safe manner.
const NotificationSchema = createSchema("notifications", {
notification: taggedUnion({
email: object({
subject: string(),
body: string(),
}),
sms: object({
phoneNumber: string(),
message: string(),
}),
push: object({
title: string(),
content: string(),
}),
}),
});
const notification = ;
await collections.notifications.insert().values({ notification: {
tag: "email",
value: {
subject: "Welcome!",
body: "Thank you for joining us.",
},
} }).exec();
Union
The union()
type allows you to define a field that can accept multiple different types. It's useful when a field can legitimately contain values of different types. Each type provided to union()
acts as a possible variant for the field.
const MilfSchema = createSchema("milf", {
phoneOrEmail: union(string(), number()),
});
Mixed
Mixed
The mixed()
type allows you to define a field that can accept any type of value. This is useful when you need maximum flexibility for a field's contents. However, use it sparingly as it bypasses TypeScript's type checking.
const AnythingSchema = createSchema("help", {
anything: mixed(),
});