Skip to content

Repository files navigation

Orm ⏹️

Handle your application models in Node.js. Built on top of @secjs/database

GitHub followersGitHub stars

Buy Me A Coffee

GitHub language countRepository sizeLicenseCommitizen

Installation

To use the high potential from this package you need to install first this other packages from SecJS, it keeps as dev dependency because one day @secjs/core will install everything once.

npm install @secjs/env @secjs/utils @secjs/exceptions

Then you can install the package using:

npm install @secjs/orm

Usage

Configuring @secjs/database

To use @secjs/orm you need to set up @secjs/database first and connect it to Database.

Go to @secjs/database documentation

Model

With config/database file configured and Database connection opened you can start using Model class

Setting up your Model

First you need to create your model class extending Model class and use decorators to map your columns

import{Model,Column}from'@secjs/orm'exportclassProductextendsModel{/** * Defines the table name in database. Default is the * name of your model in snake_case and plural format */statictable='products'/** * Defines the connection that this Model will use * to handle the database operations. This string will be used * in Database.connection method from @secjs/database. * Default is default */staticconnection='default'/** * Defines the primary key of the Model. You will always * set the value in Model and not the database columName. * Example: primaryKey will be idName and not id! * Default value is id */staticprimaryKey='idName'/** * Defines the values that are authorized to be persisted * in database. Example: if you try to create/update the * createdAt column, it will not be created/updated if it's * not present in persistOnly array. Default is ['*'] */staticpersistOnly=['name','description']/** * The columnName option defines that idName === id in database. * Default is the same name of the property, in this case, idName */
@Column({columnName: 'id'})publicidName: number
@Column()publicname: string
@Column()publicdescription: string/** * The defaultValue option defines that when creating an Product, * Model will auto set quantity as '1' if nothing is set on create */
@Column({defaultValue: 1})publicquantity: number/** * The isCreatedAt option defines that when creating an Product, * Model will auto set createdAt as 'new Date()' */
@Column({isCreatedAt: true})publiccreatedAt: Date/** * The isUpdatedAt option defines that when updating an Product, * Model will auto set updatedAt as 'new Date()' */
@Column({isUpdatedAt: true})publicupdatedAt: Date/** * The isDeletedAt option defines that when deleting an Product, * Model will soft delete instead of delete and auto set deletedAt * as 'new Date()' */
@Column({isDeletedAt: true})publicdeletedAt: Date}

Creating a new Product

With our database connection established and our model defined, we can start using Product model

constproduct=awaitProduct.create({name: 'iPhone 10',description: 'Nice iPhone'})console.log(productinstanceofProduct)// trueconsole.log(product.idName)// 1console.log(product.name)// 'iPhone10'console.log(product.quantity)// 5console.log(product.createdAt)// 2022-03-19T16:23:35.897Zconsole.log(product.updatedAt)// 2022-03-19T16:23:35.897Zconsole.log(product.deletedAt)// null// Product instance has the toJSON method that will // transform the model to objectconstproductJson=product.toJSON()console.log(productJsoninstanceofProduct)// false

Get one and get many products

constproduct=awaitProduct.find({idName: 1})console.log(productinstanceofProduct)// trueconstproducts=awaitProduct.findMany({idName: 1})console.log(products[0]instanceofProduct)// true

Update products

constwhere={idName: 1}constproduct=awaitProduct.update(where,{quantity: 50})console.log(productinstanceofProduct)// trueconsole.log(product.quantity)// 50

Delete products

If you set a Column with isDeletedAt property, delete method will always soft delete your data, setting your column as 'new Date()'

// If Product Model didn't have isDeletedAt, it would be deletedconstproduct=awaitProduct.delete({idName: 1})console.log(productinstanceofProduct)// trueconsole.log(product.deletedAt)// 2022-03-19T16:30:17.130Z// You can force the delete setting the force parameter as trueconstforce=trueconstvoidProduct=awaitProduct.delete({idName: 1},force)console.log(voidProductinstanceofProduct)// falseconsole.log(voidProduct)// undefined

Defining Relationships

We can use Decorators to define OneToOne, OneToMany and ManyToMany relations in our Model

User Model

import{Role}from'./Role'import{Product}from'./Product'import{Model,Column,HasMany,ManyToMany}from'@secjs/orm'exportclassUserextendsModel{staticpersistOnly=['name']
@Column()publicid: number
@Column()publicname: string/** * The primaryKey option defines the primaryKey of your Model. * In this case the default value would be id, because the * primaryKey of User model is id. * * The foreignKey options defines the foreignKey of your RelationModel. * In this case the default value would be userId. The name of * your Model in lower case (user) with Id in the end. */
@HasMany(()=>Product,{primaryKey: 'id',foreignKey: 'userId'})publicproducts: Product[]/** * WARN - In ManyToMany relations you don't need to define the Pivot model * * pivotTableName: The name of your pivot table - Default is user_role * localPrimaryKey: The PK of your Model - Default is id * pivotLocalForeignKey: The FK of your Model in the Pivot Table - Default is userId * relationPrimaryKey: The PK of your RelationModel - Default is id * pivotRelationForeignKey: The FK of your RelationModel in the Pivot Table - Default is roleId */
@ManyToMany(()=>Role,{pivotTableName: 'user_role'})publicroles: Role[]}

Product Model

import{User}from'./User'import{Model,Column,BelongsTo}from'@secjs/orm'exportclassProductextendsModel{staticpersistOnly=['name','quantity']
@Column()publicid: number
@Column({defaultValue: 'Product'})publicname: string
@Column({defaultValue: 1})publicquantity: number// The FK
@Column()publicuserId: number/** * The primaryKey option defines the primaryKey of your RelationModel. * In this case the default value would be id, because the * primaryKey of User model is id. * * The foreignKey options defines the foreignKey of your Model. * In this case the default value would be userId. The name of * your RelationModel in lower case (user) with Id in the end. */
@BelongsTo(()=>User,{foreignKey: 'userId',primaryKey: 'id'})publicuser: User}

Role Model

import{User}from'./User'import{Model,Column,ManyToMany}from'@secjs/orm'exportclassRoleextendsModel{staticpersistOnly=['name']
@Column()publicid: number
@Column({defaultValue: 'Customer'})publicname: string/** * WARN - To map the other side of a ManyToMany relation you need to set the * pivotTableName property. If you do not set the pivotTableName here and * try to make an include query it would make the query in role_user pivotTable */
@ManyToMany(()=>User,{pivotTableName: 'user_role'})publicusers: User[]}

ProductDetail Model

import{Product}from'./Product'import{Model,Column,BelongsTo}from'@secjs/orm'exportclassProductDetailextendsModel{staticpersistOnly=['detail']
@Column()publicid: number
@Column()publicdetail: string// The FK
@Column()publicproductId: number
@BelongsTo(()=>Product)publicproduct: Product}

Get relationships using load and query builder

We can use load instance method to get the relations from User

constuser=awaitUser.find()// Load roles and products with productDetails from Userawaituser.load('roles','products.productDetails')console.log(user.roles[0]instanceofRole)// trueconsole.log(user.products[0]instanceofProduct)// trueconsole.log(user.products[0].productDetails[0]instanceofProductDetail)// true

But we can use query builder from User too

constuser=awaitUser.query().includes('roles')// Sub query.includes('products',async(query)=>{query.orderBy('detail','asc').includes('productDetails')}).get()console.log(user.roles[0]instanceofRole)// trueconsole.log(user.products[0]instanceofProduct)// trueconsole.log(user.products[0].productDetails[0]instanceofProductDetail)// true

If you have mapped right your models with HasMany and BelongsTo you can also take the user from Product model. Example using paginate method

constpage=0constlimit=1const{ meta, links, data }=awaitProduct.query().includes('user').paginate(page,limit,'/products')console.log(meta)/** * itemCount: 1 * totalItems: 1 * totalPages: 1 * currentPage: 0 * itemsPerPage: 1 */console.log(links)/** * first: '/products?limit=1' * previous: '/products?page=0&limit=1' * next: '/products?page=1&limit=1' * last: '/products?page=1&limit=1' */console.log(datainstanceofProduct)// trueconsole.log(data.userinstanceofUser)// true

Creating your own methods/static methods using the query builder

You can define your own methods in your Model and use the query builder to get what you want

import{User}from'./User'import{Model,Column,BelongsTo}from'@secjs/orm'exportclassProductextendsModel{staticpersistOnly=['name','quantity']
@Column()publicid: number
@Column({defaultValue: 'Product'})publicname: string
@Column({defaultValue: 1})publicquantity: number
@Column()publicuserId: number
@BelongsTo(()=>User,{foreignKey: 'userId',primaryKey: 'id'})publicuser: User/** * You can define it as static and use it like this -> Product.getAllWithUser(1, 2, 3) */staticasyncgetAllWithUser(...quantityIn: number[]){returnthis.query().select('id','name','quantity').whereIn('quantity', ...quantityIn).orderBy('name','ASC').includes('user').getMany()}/** * Or you can define it as an instance method and use it like this * -> const product = await Product.find() -> await product.getAllWithUser(1, 2, 3) */asyncgetAllWithUser(quantityIn: number[]){// WARN - Do not use Model here, it wont work. // You need to use your defined model, in this case, Product.returnProduct.query().select('id','name','quantity').whereIn('quantity',quantityIn).orderBy('name','ASC').includes('user').getMany()}}

Factory method

The factory method from models can be used to generate a massive number of models. You can use this to work in the tests of your application

const{ id }=awaitUser.create({name: 'João'})// Will create ten products in database with name Test and the owner will the user Joãoconstproducts=awaitProduct.factory().count(10).create<Product[]>({name: 'Test',userId: id})// Will make ten products fake objects with name Test and the owner will the user JoãoconstfakeProducts=awaitProduct.factory().count(10).make<Product[]>({name: 'Test',userId: id})

To solve the problem of repetitive data you can define in your model the static method definition. And use the this.faker property to mock values

import{User}from'./User'import{Model,Column,BelongsTo}from'@secjs/orm'exportclassProductextendsModel{// Factories ignore the persistOnly rulestaticpersistOnly=['name','quantity']staticasyncdefinition(){return{name: this.faker.name.firstName(),quantity: this.faker.datatype.number(),/** * Define that User factory should return only the value from idPrimary. * If you don't set the returning key, factory will return all the data from User */userId: User.factory('idPrimary'),}}
@Column()publicid: number
@Column({defaultValue: 'Product'})publicname: string
@Column({defaultValue: 1})publicquantity: number
@Column()publicuserId: number
@BelongsTo(()=>User,{foreignKey: 'userId',primaryKey: 'id'})publicuser: User}

Now you can use create method normally

import{Product}from'./Product'const{ id }=awaitUser.create({name: 'João'})// Will create ten products in database with mocked values and // the owner will be different for each one of thenconstproductsDifOwner=awaitProduct.factory().count(10).create<Product[]>()// Will create ten products in database with mocked values and // the owner will be the same for each one of thenconstproductsSameOwner=awaitProduct.factory().count(10).create<Product[]>({userId: id})

Assertions with Factory

Factory has some assertions that you can make to use in tests

constfactory=User.factory()awaitfactory.count(10).create<User[]>()// Assert that user table has twenty usersawaitfactory.assertCount(10)// true// Assert that exists at least one user with name 'João'awaitfactory.assertHas({name: 'João'})// true// Assert that does not exist any user with name 'Victor'awaitfactory.assertMissing({name: 'Victor'})// true// Assert that exists one user with id 1awaitfactory.assertExists({id: 1})// true// Assert that does not exists one user with id 9999awaitfactory.assertNotExists({id: 9999})// true

License

Made with 🖤 by jlenon7 👋

About

⏹️ Handle your application models in Node.js. Built on top of @secjs/database

Topics

Resources

Contributing

Stars

3 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages