Wednesday

18-06-2025 Vol 19

My new ORM NPM package MiniORM

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:

  1. Sederhana dan Ringan: Saya ingin ORM yang tidak terasa berlebihan dan mudah dipahami.
  2. Mudah Digunakan: Konfigurasi dan penggunaan harus mudah, tanpa boilerplate yang berlebihan.
  3. Kinerja: Efisiensi sangat penting, dan saya ingin memastikan bahwa MiniORM tidak memperkenalkan overhead kinerja yang signifikan.
  4. Fleksibilitas: Meskipun sederhana, itu harus cukup fleksibel untuk menangani berbagai kasus penggunaan umum.
  5. 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:

  1. Pemetaan Objek ke Relasional: Memetakan kelas JavaScript ke tabel database, menyederhanakan operasi CRUD.
  2. Konfigurasi Sederhana: Konfigurasi yang mudah digunakan dengan sedikit boilerplate.
  3. Dukungan untuk Berbagai Database: Mendukung PostgreSQL, MySQL, SQLite, dan lainnya (dengan adaptor).
  4. Builder Kueri: Builder kueri yang lancar dan intuitif untuk membuat kueri yang kompleks.
  5. Migrasi: Dukungan dasar untuk migrasi database.
  6. Transaki: Dukungan untuk transaksi database untuk memastikan integritas data.
  7. Validasi: Fitur validasi bawaan untuk memastikan data sesuai dengan batasan yang ditentukan.
  8. 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:

  1. Fork repositori di GitHub.
  2. Buat cabang fitur.
  3. Buat perubahan Anda.
  4. 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

“`

omcoding

Leave a Reply

Your email address will not be published. Required fields are marked *