We have successfully created a our base model and connected it to database, we also made a base structure that we'll count on in this article to start create our base crud operations.
CRUD Operations
CRUD is an acronym for Create, Read, Update and Delete. These are the four basic functions of persistent storage.
These operations will be all static methods so we don't need to get an instance of the model to use them as the model will be used when there is a document inside it.
For example, User.create
method will create a new user in the database and return a new instance of the user model with the data from the database.
Our base model will have the following methods:
-
create
: Create a new document in the database. -
update
: Update a document in the database by id. -
delete
: Delete a document in the database by id. -
find
: Find a single document in the database by id. -
findBy
: Find a single document in the database by a given column/field name. -
list
: Get list of documents in the database.
Remember, these all will be static methods.
Create
This method will receive an object of data that will be created in the database and return a new model instance with the data from the database.
// src/core/database/model/model.ts
import { Collection } from "mongodb";
import connection, { Connection } from "../connection";
import { Database } from "../database";
/**
* Base Model type that we need to define
* to tell typescript that we're going to return a child that extends that base model
* which will be the T (The child class)
*/
type BaseModel<T> = typeof Model & (new () => T);
export default abstract class Model {
// ...
/**
* Create a new document in the database.
*/
public static async create<T>(
// this define what does this will mean here
// T: the Type of child class
this: BaseModel<T>,
data: Record<string, any>,
): Promise<T> {
// get the collection query
const query = this.query();
// insert the data
const result = await query.insertOne(data);
// store the data in a new object
const modelData = { ...data };
// get the inserted id
modelData._id = result.insertedId;
// now return a new model instance with the data from the database
return new (this as any)(modelData);
}
}
Wooohoo, i know there a lot of typescript stuff here, but don't worry, we'll explain it in a bit.
First we defined BaseModel
type which we 're telling him that the T
is the child class type that will extend the Model
class and we'll use it to define what does this
will mean in the create
method.
Then we defined the create
method as a static method that will receive a data
object and return a new model instance with the data from the database.
Let's split it into parts:
The method will be async
, and of course, any method that will have an operation on the database will be async
.
It receives a an object with type Record
which means it must be an object with any key and value.
The return type is a promise and the type of the promise is a new instance of model itself.
typeof this.constructor which means it will return the typeof the model itself.
The workflow of the methods works as this:
- Get the collection query.
- Insert the data.
- Store the data in a new object.
- Get the inserted id.
- Return a new model instance with the data from the database.
this.constructor as any is a hack to get the type of the model itself.
But now when we return a new instance of the model, which will be an instance of the User model, the data are passed to the constructor, but we didn't create any constructors yet, so let's do it.
// src/core/database/model/model.ts
import { Collection } from "mongodb";
import connection, { Connection } from "../connection";
import { Database } from "../database";
export default abstract class Model {
// ...
/**
* Model constructor.
*/
public constructor(public data: Record<string, any> = {}) {
//
}
/**
* Create a new document in the database.
*/
public static async create<T>(
// this define what does this will mean here
// T: the Type of child class
this: BaseModel<T>,
data: Record<string, any>,
): Promise<T> {
// get the collection query
const query = this.query();
// insert the data
const result = await query.insertOne(data);
// store the data in a new object
const modelData = { ...data };
// get the inserted id
modelData._id = result.insertedId;
// now return a new model instance with the data from the database
return new (this as any)(modelData);
}
}
Actually we can make a protected method to return a new instnace of the model itself, so we don't need to do this every time.
// src/core/database/model/model.ts
import { Collection } from "mongodb";
import connection, { Connection } from "../connection";
import { Database } from "../database";
export default abstract class Model {
// ...
/**
* Model constructor.
*/
public constructor(public data: Record<string, any> = {}) {
//
}
/**
* Create a new document in the database.
*/
public static async create<T>(
// this define what does this will mean here
// T: the Type of child class
this: BaseModel<T>,
data: Record<string, any>,
): Promise<T> {
// get the collection query
const query = this.query();
// insert the data
const result = await query.insertOne(data);
// store the data in a new object
const modelData = { ...data };
// get the inserted id
modelData._id = result.insertedId;
// now return a new model instance with the data from the database
return this.self(data);
}
/**
* Create a new model instance.
*/
protected static self<T>(data: Record<string, any>): T {
return new (this as any)(data);
}
}
Now let's give it a try.
Open src/app/users/routes.ts
file and add the following code:
// src/app/users/routes.ts
import User from "./models/user";
// we will use set time out to wait for database connection first
// this is just for testing
setTimeout(async () => {
const user = await User.create({
name: "Test",
});
console.log(user.data);
}, 4000);
Now if we hovered over the const user
to see its return type it will appear like this:
Which exactly what we were doing earlier in that complex typescript definitions.
You should see now in your terminal something like this:
We'll stop at this point so you can digest what we did, and we'll continue in the next part.
🎨 Conclusion
In this article we learned how to work with typescript types to define the return type of same class from a static method, also we created a create
method that receives an object and insert it into the database for any model that extends the Model
class.
In the next part we'll continue with the Model
class and we'll add more methods to it.
🎨 Project Repository
You can find the latest updates of this project on Github
😍 Join our community
Join our community on Discord to get help and support (Node Js 2023 Channel).
🎞️ Video Course (Arabic Voice)
If you want to learn this course in video format, you can find it on Youtube, the course is in Arabic language.
💰 Bonus Content 💰
You may have a look at these articles, it will definitely boost your knowledge and productivity.
General Topics
- Event Driven Architecture: A Practical Guide in Javascript
- Best Practices For Case Styles: Camel, Pascal, Snake, and Kebab Case In Node And Javascript
- After 6 years of practicing MongoDB, Here are my thoughts on MongoDB vs MySQL
Packages & Libraries
- Collections: Your ultimate Javascript Arrays Manager
- Supportive Is: an elegant utility to check types of values in JavaScript
- Localization: An agnostic i18n package to manage localization in your project
React Js Packages
Courses (Articles)
Top comments (0)