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.
- 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.
NPM:
npm install monarch-orm
Or Yarn:
yarn add monarch-orm
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(/** db uri **//)
const { collections } = createDatabase(client.db(), {
users: UserSchema,
});
const newUser = await collections.users
.insert()
.values({
name: "anon",
email: "[email protected]",
age: 0,
isVerified: true,
})
.exec();
const users = await collections.users.find().where({}).exec();
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,
});
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: "[email protected]",
age: 25,
isVerified: true,
})
.exec();
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);
// Or just...
const users = await collections.users.find({}).exec();
console.log(users);
// For finding one
const user = await collections.users.find().where({
name: "Alice"
}).exec();
console.log(users);
// Or...
const user = await collections.users.findOne({
name: "Alice"
}).exec();
console.log(users);
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: "[email protected]",
})
.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.
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);
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(),
});
Defines a field that accepts numeric values.
const UserSchema = createSchema("users", {
age: number().optional(),
});
Defines a field that accepts boolean values (true or false).
const UserSchema = createSchema("users", {
isVerified: boolean(),
});
Defines a field that accepts JavaScript Date objects.
const UserSchema = createSchema("users", {
birthDate: date(),
});
Defines a field that accepts date strings in ISO format.
const UserSchema = createSchema("users", {
registrationDate: dateString(),
});
.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.
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", // Valid
};
// Invalid example will throw a type error
const invalidUser = {
role: "guest", // Error: Type '"guest"' is not assignable to type '"admin" | "moderator" | "customer"'
};
// all properties are required by default
const UserSchema = object({
name: string(),
age: number(),
});
// extract the inferred type like this
type User = InferSchemaInput<typeof UserSchema>;
// equivalent to:
type User = {
name: string;
age: number;
};
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.
// Define the User schema with a record for grades
const UserSchema = createSchema("users", {
name: string().required(),
email: string().required(),
grades: record(number()), // Each subject will have a numeric grade
});
// Example of inserting a user with grades
const { collections } = createDatabase(client.db(), {
users: UserSchema,
});
// Inserting a new user with grades for different subjects
const newUser = await collections.users
.insert()
.values({
name: "Alice",
email: "[email protected]",
grades: {
math: 90,
science: 85,
history: 88,
},
})
.exec();
// Querying the user to retrieve grades
const user = await collections.users.findOne().where({ email: "[email protected]" }).exec();
console.log(user.grades);
// Output: { math: 90, science: 85, history: 88 }
// For Example
const ResultSchema = object({
name: string(),
scores: array(number()),
});
// extract the inferred type like this
type Result = InferSchemaInput<typeof ResultSchema>;
// equivalent to:
type Result = {
name: string;
scores: number[];
};
Unlike arrays, A tuple()
has a fixed number of elements but each element can have a different type.
// all properties are required by default
const ControlSchema = object({
location: tuple([number(), number()]),
});
// extract the inferred type like this
type Control = InferSchemaInput<typeof ControlSchema>;
// equivalent to:
type Control = {
location: [number, number];
};
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.
// You need:
// - a tag: A string identifying the type
// value: An object containing specific fields for that type.
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();
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()),
});
// Output Type : {
// phoneOrEmail: string | number
// }
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(),
});