MongoDB
Koomi ORM
Overview
KoomiORM is a database mapper library developed by KoomiTeam, inspired by mongoosejs and built on top of NodeJS mongodb native driver. KoomiORM is easier to use with less advanced features than mongoose, so it allows KoomiTeam to control the speed of the queries and also can customize the features depend on the future projects requirement.
MongoDB Connector
Below is a example on how you can connect to a MongoDB Server using KoomiORM
import { koomiOrm } from "@koomi/koomiorm"
await koomiOrm.connect({
protocol: "mongodb",
host: "127.0.0.1:27017",
username: "app",
password: "",
params: "",
name: "database_name",
authenticateDb: "/" // if not provide the default authenticate db is /{database_name}
debug: true, // turn on to debug database executing
})
The method will return mongodb native MongoClient
Once connected, the server will open a connection to the database_name
DB located at mongodb://127.0.0.1:27017/
and cache the connection for later uses. . This is a required step before you want to do anything with the database.
Basic Usages
Types
Currently, KoomiOrm supports 16 different Types.
Types.String
: Text valueTypes.Int
: Integer number valueTypes.Float
: Float number valueTypes.Boolean
:true
orfalse
Types.Date
: Date value will automatically convert to timestamp (e.g. "01/01/2023" date will be stored as 1672506000000 in the database)Types.Object
: Embeded object valueTypes.ID
: MongoDB ObjectId valueTypes.ArrayOfString
: Array of String type valuesTypes.ArrayOfInt
: Array of Int type valuesTypes.ArrayOfFloat
: Array of Float type valuesTypes.ArrayOfBoolean
: Array of Boolean type valuesTypes.ArrayOfDate
: Array of Date type valuesTypes.ArrayOfObject
: Array of Object type valuesTypes.ArrayOfID
: Array of ID type values
Model
The most important concept in KoomiOrm is called Model
. KoomiOrm uses Model
to map it to the Collection
in MongoDB and expose methods allow you to execute database CRUD operations more convenient. For example, inside MongoDB you have collection users
you can create model User
with the model name users to map it to that collection.
Inside koomiOrm
instance, there is a method called createModel
which will allow you to create a Model
. The created model will be cached inside KoomiOrm and can be access anywhere in the application using the koomiOrm.model("modelName")
method.
createModel
method signature
// Signature
koomiOrm.createModel(dbCollectionName, schema, options)
dbCollectionName
:String
| The collection name in MongoDB you want to map (eg. users)schema
:Object
| KoomiOrm will use this schema for validation and mapping. You can define a field schema in two ways, using aString
or anObject
. Here"s the list of available properties in the field schema that you can set:
Name | Type | Description |
---|---|---|
type | String (Required) | The data type of this field. KoomiOrm currently supports 16 Types |
isRequired | Boolean | If this field is omitted when creating a new record, an Error will be thrown. |
default | Any | If the field has no value when creating record, the default value will be used instead. This value will be added before validation so make sure it is valid. |
length | Number or [Number] | Only valid for |
min | Number | Only valid for |
max | Number | Only valid for |
enum | [Any] | An array of valid values, the field value must be one of these to be valid. |
options
: extra configurations for this model. All available options are:
Name | Type | Description |
---|---|---|
resolvers | Object | You can also call them calculated fields. These fields are not stored in the database but instead will be calculated base on other fields when calling |
relationships | Object | These are special resolvers, allow you to define relationship fields which can also be resolved and included in when calling |
indexes | Array | Indexes which will be passed to MongoDB Driver createIndexes method. |
Below is an example of how to create a Model
// server/models/User.js
import { koomiOrm, Types } from "@koomi/koomiorm"
const User = mongodb.createModel(
"users",
{
username: { type: Types.String , isRequired: true, length: [5] },
email: { type: Types.String, isRequired: true },
password: { type: Types.String, isRequired: true },
firstName: { type: Types.String, isRequired: true },
lastName: Types.String,
friendIds: Types.ArrayOfID,
role: { type: Types.Enum, enum: ["user", "admin"], default: "user" }
},
{
resolvers: {
fullName: (doc) => {
return `${doc.firstName} ${doc.lastName}`.trim()
}
},
relationships: {
friends: { ref: "users", keys: "friendIds" }
posts: { ref: "posts", foreignKey: "authorId" }
},
indexes: [
{ key: { username: 1 }, unique: true },
{ key: { email: 1 }, unique: true }
]
}
)
export default User
Best Practice
Before you can interact with the collections, you need to define the Model
. So it's a best practive to just import all the Model
files right after you connect
to the database, which allow koomiOrm to create and cache all the models and later part you just need to call koomiOrm.model("modelName")
to get the model you want to interact with.
import { koomiOrm } from "@koomi/koomiorm"
const initMongo = async() => {
await koomiOrm.connect({
protocol: "mongodb",
host: "127.0.0.1:27017",
username: "app",
password: "",
params: "",
name: "database_name",
authenticateDb: "/" // if not provide the default authenticate db is /{database_name}
debug: true, // turn on to debug database executing
})
import("server/models/User")
import("server/models/Post")
}
...
initMongo()
...
CRUD Operations
Once the model is created, you can interact with the corresponding Mongodb collection using the following methods:
async model.findMany(filter, options)
: get an array of records that match thefilter
criteria. ReturnCursor<[Object]>
async model.findOne(filter, options)
: get the first record that match thefilter
criteria. ReturnCursor<Object>
async model.findById(id, options)
: wrappingfindOne()
to get the record which has the id matched withid
. ReturnCursor<Object>
async model.findByIds(ids, options)
: wrappingfind()
to get the array of records which have the id appeared in theids
. ReturnCursor<[Object]>
async model.countDocuments(filter, options)
: count the total of records that match thefilter
criteria. ReturnNumber
async model.createOne(doc, options)
: validate and insert thedoc
into the database. ReturnCursor<Object>
of created recordasync model.createMany(docs, options)
: validate and insert all thedocs
into the database. ReturnCursor<Array>
of created recordsasync model.updateOne(filter, newDoc, options)
: validate and update the first record that match thefilter
criteria, new values innewDoc
will replace old value in the database and keep the others the same. ReturnCursor<Object>
of updated recordasync model.updateMany(filter, newDoc, options)
: validate and update all the records that match thefilter
criteria, new values innewDoc
will replace old value in the database and keep the others the same. ReturnCursor<Array>
of updated recordsasync model.softDeleteMany(filter, options)
: wrappingupdateMany()
to soft-delete (upsert thedeletedAt
field) all the records that match thefilter
criteria. Return the Ids of deleted recordsasync model.deleteMany(filter, options)
: permanently delete all the records that match thefilter
criteria. Return the result of deleteMany
Note
Besides the above CRUD Operations, model
instance also publish the (Native Mongodb Collection)[https://www.mongodb.com/docs/manual/reference/method/js-collection/], allow you to perform any native operations. You can get the collection
by calling model.getCollection()
Cursor
Cursor
is a special Class
extends Object
or Array
class based on which methods are called. Basically, Cursor
is the same as Object
and Array
classes but has a special method:
async resolve(includes)
: a method to tell theCursor
to resolveresolver
andrelationships
based on theincludes
passed in.includes
is anObject
to tell theCursor
which fields to keep, which to be omited after resolving.
import { koomiOrm } from "@koomi/koomiorm"
let post = await koomiOrm.models("posts").findOne({ ... })
console.log(post.author) // undefined
post = await post.resolve({ author: true })
console.log(post.author) // { id: ..., email: ..., ...}
Relationship and Data Loader
Data Loader is a generic utility to be used as part of your application data fetching layer to provide a simplified and consistent API over various remote data sources such as databases or web services via batching and caching. You can check out how the Data Loader works in the Official DataLoader Repo
Data Loader allow KoomiOrm to batch resolving relationship fields among multiple documents, instead of resolving one by one which will cost significant amount of time and database work load.
To define a relationship, you can use the relationship option when creating the Model. relationships
is an Object
containing all the relationship fields which will be included in the record when call resolve()
on the Cursor. Below is how you can define relationships
:
// relationships signature
// { ref: relatedCollection, [key | keys | foreignKey]: keyToLookup, filter: findFilter }
const Post = mongodb.createModel("posts", {
title: "String",
content: "String",
authorId: "ID"
published: "Boolean"
}, {
includes: {
author: { ref: "users", key: "authorId" }
}
})
const User = mongodb.createModel("users", {
email: "String",
fullName: "String",
}, {
includes: {
posts: { ref: "posts", foreignKey: "authorId" },
publishedPosts: {
ref: "posts", foreignKey: "authorId",
filter: { published: true }
},
}
})
ref
: the collection name of the related Model you want to establish the relationshipkey | keys | foreignKey
: the name of the field whose value will be looked up when resolving the relationship with the_id
field.key
is when the current Model is belong to related Model (Post
belong toUser
withauthorId
key). Will returnObject
instance of the related record when resolve.keys
is also when the current Model is belong to related Model but the key field value is an array of ID ([ID]
). Will returnArray
of related records when resolve.foreignKey
is when the related Model is belong to current Model (User
want to get thePosts
belong to it). Will returnArray
of related records when resolve.
filter
: the filter you want to apply when resolving the related records (User
want to get only thepublishedPosts
fromPost
Model)