Handle your application models in Node.js. Built on top of @secjs/database
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/corewill install everything once.
npm install @secjs/env @secjs/utils @secjs/exceptionsThen you can install the package using:
npm install @secjs/ormTo use @secjs/orm you need to set up @secjs/database first and connect it to Database.
Go to @secjs/database documentation
With config/database file configured and Database connection opened you can start using Model class
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}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)// falseconstproduct=awaitProduct.find({idName: 1})console.log(productinstanceofProduct)// trueconstproducts=awaitProduct.findMany({idName: 1})console.log(products[0]instanceofProduct)// trueconstwhere={idName: 1}constproduct=awaitProduct.update(where,{quantity: 50})console.log(productinstanceofProduct)// trueconsole.log(product.quantity)// 50If 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)// undefinedWe can use Decorators to define OneToOne, OneToMany and ManyToMany relations in our 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[]}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}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[]}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}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)// trueBut 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)// trueIf 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)// trueYou 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()}}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 thethis.fakerproperty 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})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})// trueMade with 🖤 by jlenon7 👋

