MiniORM: Paket NPM ORM Ringan untuk Pengembang JavaScript Tingkat Lanjut
Sebagai pengembang JavaScript berpengalaman, kita sering kali bergulat dengan interaksi database yang kompleks. ORM (Object-Relational Mapping) muncul sebagai penyelamat, menyederhanakan proses ini dengan memetakan objek ke tabel database. Namun, banyak ORM populer yang berat dan sulit dikonfigurasi, terutama untuk proyek kecil atau menengah. Di sinilah MiniORM hadir – paket NPM ORM ringan dan intuitif yang saya buat untuk mengatasi tantangan ini.
Apa itu MiniORM?
MiniORM adalah paket NPM Object-Relational Mapping (ORM) yang dirancang untuk menyederhanakan interaksi database dalam aplikasi JavaScript dan TypeScript. Berfokus pada keringkasan, kemudahan penggunaan, dan kinerja, MiniORM menyediakan abstraksi yang ramping di atas operasi database mentah, memungkinkan pengembang untuk berinteraksi dengan database mereka menggunakan paradigma berorientasi objek.
Mengapa Saya Membuat MiniORM?
Motivasi di balik MiniORM berasal dari pengalaman frustasi saya sendiri dengan ORM yang ada. Saya menemukan mereka seringkali terlalu kompleks untuk kebutuhan saya, dengan kurva belajar yang curam dan banyak fitur yang tidak saya butuhkan. Saya menginginkan sesuatu yang sederhana, mudah dipahami, dan dapat diskalakan dengan kebutuhan proyek saya. Jadi, saya mulai membangun MiniORM untuk mengisi celah itu.
Berikut adalah beberapa alasan utama mengapa saya membuat MiniORM:
- Sederhana dan Ringan: Saya ingin ORM yang tidak terasa berlebihan dan mudah dipahami.
- Mudah Digunakan: Konfigurasi dan penggunaan harus mudah, tanpa boilerplate yang berlebihan.
- Kinerja: Efisiensi sangat penting, dan saya ingin memastikan bahwa MiniORM tidak memperkenalkan overhead kinerja yang signifikan.
- Fleksibilitas: Meskipun sederhana, itu harus cukup fleksibel untuk menangani berbagai kasus penggunaan umum.
- Kontrol: Saya ingin mempertahankan kontrol atas kueri SQL yang dihasilkan, memungkinkan pengoptimalan manual jika diperlukan.
Fitur Utama MiniORM
MiniORM dikemas dengan fitur-fitur yang dirancang untuk menyederhanakan interaksi database Anda:
- Pemetaan Objek ke Relasional: Memetakan kelas JavaScript ke tabel database, menyederhanakan operasi CRUD.
- Konfigurasi Sederhana: Konfigurasi yang mudah digunakan dengan sedikit boilerplate.
- Dukungan untuk Berbagai Database: Mendukung PostgreSQL, MySQL, SQLite, dan lainnya (dengan adaptor).
- Builder Kueri: Builder kueri yang lancar dan intuitif untuk membuat kueri yang kompleks.
- Migrasi: Dukungan dasar untuk migrasi database.
- Transaki: Dukungan untuk transaksi database untuk memastikan integritas data.
- Validasi: Fitur validasi bawaan untuk memastikan data sesuai dengan batasan yang ditentukan.
- Hubungan: Mendukung hubungan satu-ke-satu, satu-ke-banyak, dan banyak-ke-banyak antar model.
Memulai dengan MiniORM
Mari kita mulai menggunakan MiniORM dengan contoh sederhana:
1. Instalasi
Instal MiniORM menggunakan NPM:
npm install mini-orm
2. Konfigurasi
Konfigurasikan koneksi database Anda:
// Import module
import { MiniORM, Model, DataTypes } from 'mini-orm';
// Initialize ORM with connection options
const orm = new MiniORM({
dialect: 'sqlite', // or 'postgres', 'mysql', etc.
storage: 'database.sqlite' // Path to SQLite database file
});
3. Mendefinisikan Model
Definisikan model Anda yang sesuai dengan tabel database:
// Define a Model
class User extends Model {
static tableName = 'users';
static fields = {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
unique: true,
allowNull: false
}
};
}
// Initialize Model
User.init(orm);
4. Membuat Tabel
Buat tabel yang sesuai di database:
// Sync Model with the database to create table if not exists
await User.sync();
5. Melakukan Operasi CRUD
Sekarang, mari lakukan beberapa operasi CRUD:
Membuat Data
// Creating new user
const newUser = await User.create({
name: 'John Doe',
email: 'john.doe@example.com'
});
console.log("Created user:", newUser);
Membaca Data
// Find user by ID
const user = await User.findByPk(1);
console.log("User found:", user);
// Find user by email
const userByEmail = await User.findOne({
where: {
email: 'john.doe@example.com'
}
});
console.log("User found by email:", userByEmail);
// Find all users
const allUsers = await User.findAll();
console.log("All users:", allUsers);
Memperbarui Data
// Update user
await User.update({ name: 'Jane Doe' }, {
where: {
id: 1
}
});
const updatedUser = await User.findByPk(1);
console.log("Updated user:", updatedUser);
Menghapus Data
// Delete user
await User.destroy({
where: {
id: 1
}
});
const deletedUser = await User.findByPk(1);
console.log("Deleted user:", deletedUser); // Should return null
Builder Kueri
MiniORM dilengkapi dengan builder kueri yang kuat untuk membuat kueri yang kompleks. Berikut adalah beberapa contoh:
Kueri Dasar
// Select all users
const users = await User.findAll();
Klausul Where
// Select users with age greater than 25
const users = await User.findAll({
where: {
age: {
[Op.gt]: 25 // Op adalah singkatan dari Operator (Op.gt = greater than)
}
}
});
Urutan
// Select users ordered by name
const users = await User.findAll({
order: [['name', 'ASC']]
});
Limit dan Offset
// Select the first 10 users
const users = await User.findAll({
limit: 10
});
// Select users starting from the 11th user
const users = await User.findAll({
offset: 10
});
Bergabung
Asumsikan kita memiliki model `Post` yang terkait dengan model `User`:
// Select all users with their posts
const users = await User.findAll({
include: [Post]
});
Manajemen Relasi
MiniORM menyederhanakan pengelolaan relasi database, memungkinkan Anda menentukan relasi antar model dengan mudah.
Satu-ke-Satu
Contoh: `User` memiliki `Profile`:
// Define Profile model
class Profile extends Model {
static tableName = 'profiles';
static fields = {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
userId: {
type: DataTypes.INTEGER,
allowNull: false
},
bio: {
type: DataTypes.STRING
}
};
static associate() {
// Define one-to-one relation
User.hasOne(this, {
foreignKey: 'userId',
as: 'profile'
});
this.belongsTo(User, {
foreignKey: 'userId',
as: 'user'
});
}
}
// Initialize Model
Profile.init(orm);
Satu-ke-Banyak
Contoh: `User` memiliki banyak `Post`:
// Define Post model
class Post extends Model {
static tableName = 'posts';
static fields = {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
userId: {
type: DataTypes.INTEGER,
allowNull: false
},
title: {
type: DataTypes.STRING,
allowNull: false
},
content: {
type: DataTypes.TEXT
}
};
static associate() {
// Define one-to-many relation
User.hasMany(this, {
foreignKey: 'userId',
as: 'posts'
});
this.belongsTo(User, {
foreignKey: 'userId',
as: 'user'
});
}
}
// Initialize Model
Post.init(orm);
Banyak-ke-Banyak
Contoh: `User` dapat memiliki banyak `Role`, dan `Role` dapat memiliki banyak `User`. Kita memerlukan tabel penghubung (junction table) `UserRoles`:
// Define Role model
class Role extends Model {
static tableName = 'roles';
static fields = {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING,
allowNull: false
}
};
static associate() {
User.belongsToMany(this, {
through: 'UserRoles',
foreignKey: 'userId',
otherKey: 'roleId',
as: 'roles'
});
this.belongsToMany(User, {
through: 'UserRoles',
foreignKey: 'roleId',
otherKey: 'userId',
as: 'users'
});
}
}
// Define junction table UserRoles.
// This model is special because it does not get the init method
const UserRoles = orm.define('UserRoles', {
userId: {
type: DataTypes.INTEGER,
primaryKey: true,
},
roleId: {
type: DataTypes.INTEGER,
primaryKey: true,
}
}, {
tableName: 'user_roles', // Manually set table name
timestamps: false // Disable timestamps
});
// Initialize Model
Role.init(orm);
Validasi Data
MiniORM menyediakan fitur validasi bawaan untuk memastikan data sesuai dengan batasan yang ditentukan sebelum disimpan ke database.
// Define model with validation rules
class Product extends Model {
static tableName = 'products';
static fields = {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING,
allowNull: false,
validate: {
len: [2, 50] // Nama harus antara 2 dan 50 karakter
}
},
price: {
type: DataTypes.DECIMAL,
allowNull: false,
validate: {
min: 0 // Harga harus lebih besar atau sama dengan 0
}
}
};
}
// Initialize Model
Product.init(orm);
// Create product
try {
const newProduct = await Product.create({
name: 'A', // Akan gagal karena validasi panjang nama
price: -10 // Akan gagal karena validasi harga
});
} catch (error) {
console.error("Validation error:", error);
}
Migrasi
Meskipun MiniORM tidak memiliki sistem migrasi yang lengkap, ia menyediakan utilitas dasar untuk menjalankan migrasi. Anda dapat membuat dan menjalankan file migrasi untuk memperbarui skema database Anda secara terprogram.
Contoh:
// migration.js
export async function up(orm) {
await orm.getQueryInterface().addColumn('users', 'age', {
type: DataTypes.INTEGER,
allowNull: true
});
}
export async function down(orm) {
await orm.getQueryInterface().removeColumn('users', 'age');
}
// Run migration
import { up, down } from './migration';
async function runMigration() {
try {
await up(orm);
console.log("Migration successful!");
} catch (error) {
console.error("Migration error:", error);
}
}
runMigration();
Transaksi
MiniORM mendukung transaksi database untuk memastikan integritas data. Transaksi memungkinkan Anda untuk mengelompokkan beberapa operasi database menjadi satu unit logis kerja. Jika salah satu operasi gagal, seluruh transaksi akan digulung balik, menjaga konsistensi data.
// Run transaction
async function createOrder(userId, productIds) {
const transaction = await orm.transaction();
try {
// Create order
const order = await Order.create({
userId: userId,
orderDate: new Date()
}, { transaction });
// Create order items
for (const productId of productIds) {
await OrderItem.create({
orderId: order.id,
productId: productId,
quantity: 1
}, { transaction });
}
// Commit transaction
await transaction.commit();
console.log("Order created successfully!");
} catch (error) {
// Rollback transaction
await transaction.rollback();
console.error("Error creating order:", error);
}
}
Perbandingan dengan ORM Lainnya
Berikut adalah perbandingan singkat antara MiniORM dan beberapa ORM populer lainnya:
ORM | Ukuran | Kompleksitas | Fitur | Kinerja |
---|---|---|---|---|
MiniORM | Kecil | Rendah | Esensial | Tinggi |
Sequelize | Sedang | Sedang | Lengkap | Sedang |
TypeORM | Besar | Tinggi | Sangat lengkap | Sedang |
Prisma | Sedang | Sedang | Lengkap dengan generator kode | Tinggi |
Studi Kasus: Menggunakan MiniORM dalam Aplikasi Blog
Mari kita lihat bagaimana MiniORM dapat digunakan dalam aplikasi blog sederhana.
Model
// Post Model
class Post extends Model {
static tableName = 'posts';
static fields = {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
title: {
type: DataTypes.STRING,
allowNull: false
},
content: {
type: DataTypes.TEXT,
allowNull: false
},
authorId: {
type: DataTypes.INTEGER,
allowNull: false
}
};
static associate() {
this.belongsTo(User, {
foreignKey: 'authorId',
as: 'author'
});
}
}
// User Model
class User extends Model {
static tableName = 'users';
static fields = {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password: {
type: DataTypes.STRING,
allowNull: false
}
};
static associate() {
this.hasMany(Post, {
foreignKey: 'authorId',
as: 'posts'
});
}
}
// Initialize Models
Post.init(orm);
User.init(orm);
Rute API
// Get all posts
app.get('/posts', async (req, res) => {
const posts = await Post.findAll({
include: [{
model: User,
as: 'author',
attributes: ['username']
}]
});
res.json(posts);
});
// Create a new post
app.post('/posts', async (req, res) => {
try {
const newPost = await Post.create({
title: req.body.title,
content: req.body.content,
authorId: req.body.authorId
});
res.status(201).json(newPost);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
Roadmap Masa Depan
Saya terus bekerja untuk meningkatkan MiniORM. Berikut adalah beberapa fitur yang direncanakan untuk rilis mendatang:
- Sistem Migrasi yang Lebih Baik: Sistem migrasi yang lebih lengkap untuk pengelolaan skema database yang mudah.
- Dukungan yang Lebih Baik untuk TypeScript: Tingkat integrasi TypeScript yang lebih tinggi dengan tipe dan deklarasi yang lebih baik.
- Dukungan untuk Fitur Database Tingkat Lanjut: Dukungan untuk fitur khusus database seperti fungsi JSON dan indeks spasial.
- Dokumentasi yang Lebih Baik: Dokumentasi yang lebih komprehensif dengan lebih banyak contoh dan panduan.
- Dukungan Komunitas: Membangun komunitas yang kuat di sekitar MiniORM untuk umpan balik, kontribusi, dan dukungan.
Berkontribusi ke MiniORM
MiniORM adalah proyek sumber terbuka, dan saya menyambut baik kontribusi dari komunitas. Jika Anda tertarik untuk berkontribusi, silakan:
- Fork repositori di GitHub.
- Buat cabang fitur.
- Buat perubahan Anda.
- Kirim permintaan tarik.
Anda juga dapat berkontribusi dengan:
- Melaporkan masalah dan bug.
- Mengusulkan fitur baru.
- Meningkatkan dokumentasi.
- Membantu menjawab pertanyaan di forum komunitas.
Kesimpulan
MiniORM adalah paket NPM ORM ringan dan kuat yang dirancang untuk menyederhanakan interaksi database dalam aplikasi JavaScript dan TypeScript. Dengan konfigurasi yang mudah, builder kueri yang intuitif, dan dukungan untuk relasi dan validasi, MiniORM adalah pilihan yang sangat baik untuk proyek kecil dan menengah. Saya harap artikel ini telah memberi Anda pemahaman yang baik tentang MiniORM dan bagaimana Anda dapat menggunakannya dalam proyek Anda. Jangan ragu untuk mencobanya dan memberikan umpan balik!
Kata kunci: ORM, JavaScript, Node.js, NPM, Database, PostgreSQL, MySQL, SQLite, Builder Kueri, Migrasi, Transaksi, Validasi, Relasi, MiniORM, Ringan
“`