Tutorial ORM Objection.js di Node.js Fastify
Halo devlopah, kali ini gue mau berbagi insight mengenai cara menggunakan ORM Objection.js di Node.js fastify. Tapi tentu hari gini pasti ada yang make doang tapi ngga tau ORM tuh apasih?
“Object-relational mapping (ORM) is a programming technique in which a metadata descriptor is used to connect object code to a relational database.”
— techopedia.com
Jadi intinya, ORM ini adalah sebuah objek kode yang akan terhubung dengan database kita agar developer semakin mudah dalam membuat query.
Seperti yang kita ketahui, ORM itu sangat mempermudah proses dalam melakukan query ke database, rather than writing “SELECT * FROM table_name”, sebagian developer lebih milih sesuatu yang singkat dengan menggunakan ORM. Tujuannya apasih? Tentu saja untuk mempermudah proses peng-query-an ke DB, karena memang cukup menyingkat dan enak dibaca.
Namun ada beberapa developer yang concern terkait masalah performa. Bagi beberapa developer yang “masa bodoh” mungkin ngga akan peduli, tapi sebenernya ngaruh ngga sih ke performa? Jawabannya “Ngaruh”. Lah terus ngapain saya pake kalo ngaruh ke performa? Tunggu sebentar gan, ngaruh ini tergantung dari library yang agan gunakan, kalau agan menggunakan library yang sudah banyak digunakan dan di repositorynya belum ada issue banyak terkait performa saya kira library yang agan gunakan aman. Kalaupun agan ingin membuat library agan sendiri akan jauh lebih baik, karena agan bisa “men-tweak” performa sesuai kadar kebutuhan agan, tapi tentu itu akan memakan waktu kan? Selain itu isu performa juga banyak tidak hanya selalu dari code, bisa saja dari struktur DB (struktur tabel, indexing, length column), bandwidth, I/O, maupun RAM. Oke skip dulu pembahasan lengkapnya.
Mungkin bagi agan-agan yang menggunakan Node.js ngga akan asing sama yang namanya “Sequelize”, yap itu adalah ORM yang kerap digunakan untuk Express dengan library mysql2.
Namun karena berhubung library yang saya gunakan adalah mysql, dan saya belum pernah (serta malas) untuk mencoba dengan Sequelize dan pengen sesuatu yang baru, maka saya cari-cari di internet ada library yang bagus bernama Objection.js.
Disini saya ngga akan compare antara dua library tersebut, jadi skip harapannya untuk melihat hasil komparasi.
Oke lanjut, kita buat project baru dulu lalu jangan lupa
npm init -y
Install Fastify, Objection.js, MySQL, beserta kroninya yaitu Knex.
npm install objection knex fastify fastify-formbody dotenv mysql --save
buat file bernama server.js dan isi file tersebut dengan code dibawah ini
require('dotenv').config();
const fastify = require('fastify')({
logger: true
});
fastify.register(require('fastify-formbody'));
fastify.register(require('./routes'), { prefix: '/v1' });
const start = async () => {
try {
await fastify.listen(process.env.PORT || 3000);
console.log(`Server listening on ${fastify.server.address().port}`)
} catch (err) {
console.log(err);
process.exit(1)
}
};
start();
lanjut, sekarang kita buat connection.js yang akan jadi file untuk koneksi ke database.
require('dotenv').config();
var knex = require('knex')({
client: 'mysql',
connection: {
host: process.env.DB_HOST,
user: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE
},
pool: { min: 0, max: 3 } //Menggunakan fungsi pool agar menjaga koneksi ke DB tetep tersambung
});
module.exports = knex;
Jangan lupa untuk membuat file .env dan isi dengan data dibawah
PORT=3000
DB_HOST=127.0.0.1
DB_DATABASE=[Masukkan nama DB]
DB_USERNAME=homestead
DB_PASSWORD=secret
Kasus yang akan kita kerjakan adalah kita akan membuat dua table.
Table users akan menyimpan nama pengguna dan table phone akan menyimpan nomor telfon dari pengguna kita yang bisa saja memiliki nomor telfon lebih dari satu.
CREATE TABLE `users` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(45) NULL,
`email` VARCHAR(45) NULL,
PRIMARY KEY (`id`));CREATE TABLE `phone` (
`id` INT NOT NULL AUTO_INCREMENT,
`user_id` INT NOT NULL,
`phone_number` VARCHAR(15) NOT NULL,
`description` VARCHAR(50) NULL,
PRIMARY KEY (`id`),
INDEX `fk_phone_1_idx` (`user_id` ASC),
CONSTRAINT `fk_phone_1`
FOREIGN KEY (`user_id`)
REFERENCES `users` (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION);
Oke setelah berhasil membuat kedua table yuk kita lanjut.
Kita akan membuat UserModel.js yang akan menjadi Model ORM ke database.
const { Model } = require('objection');
const knex = require('./connection');
const PhoneModel = require('./PhoneModel');
Model.knex(knex);
class UserModel extends Model {
static get tableName() {
return 'users';
}
static get relationMappings() {
return {
phone: {
relation: Model.HasManyRelation,
modelClass: PhoneModel,
join: {
from: 'users.id',
to: 'phone.user_id'
}
}
};
}
}
module.exports = UserModel;
pada relationMappings(), kita akan membuat relational yang berfungsi untuk memudahkan kita melihat relasi antar table.
Ada beberapa tipe relation, tapi disini saya ngga jabarin satu-persatu ya bedanya many to many, one to many, dsbnya karena saya anggap ketika anda membaca tutorial ini anda sudah tingkat intermediate dan sudah paham dengan konsep dasar database.
HasOneThroughRelation
: Use this relation when the model is related to a single model through a join table
ManyToManyRelation
: Use this relation when the model is related to a list of other models through a join table
HasOneRelation
: Just like HasManyRelation
but for one related row
HasManyRelation
: Use this relation when the related model has the foreign key
BelongsToOneRelation
: Use this relation when the source model has the foreign key
Sekarang buat PhoneModel.js
const { Model } = require('objection');
const knex = require('./connection');
const UserModel = require('./UserModel');
Model.knex(knex);
class PhoneModel extends Model {
static get tableName() {
return 'phone';
}
static get relationMappings() {
return {
users: {
relation: Model.BelongsToOneRelation,
modelClass: UserModel,
join: {
from: 'phone.user_id',
to: 'users.id'
}
}
}
}
}
module.exports = PhoneModel;
Seperti biasa kita akan membuat dulu response controller yang dapat di “reusable”, buatlah ResponseController.js dan masukkan kode dibawah ini.
'use strict'
function ok (values, message, reply) {
return reply
.code(200)
.header('Content-Type', 'application/json; charset=utf-8')
.send({
code : 200,
values : values,
message : message,
});
}
function notFound (values, message, reply) {
return reply
.code(200)
.header('Content-Type', 'application/json; charset=utf-8')
.send({
code : 400,
values : values,
message : message,
});
}
module.exports = {
ok, notFound
}
Lalu barulah kita buat UserController.js
'use strict';
const res = require('./ResponseController')
const userModel = require('./UserModel');
async function get (request, reply) {
const users = await userModel
.query()
.eager('phone')
.orderBy('name', 'ASC');
return res.ok(users, "", reply)
}
async function store (request, reply) {
let name = request.body.name;
let email = request.body.email;
const users = await userModel
.query()
.insert({ name: name, email: email});
return res.ok(users, "Successfully add users", reply)
}
async function show (request, reply) {
let id = request.params.id;
const users = await userModel.query().findById(id);
return res.ok(users, "", reply)
}
async function update (request, reply) {
let name = request.body.name;
let email = request.body.email;
let id = request.body.id;
const users = await userModel
.query()
.findById(id)
.patch({
name: name, email: email
});
return res.ok(users, "Successfully update users", reply)
}
async function destroy (request, reply) {
const users = await userModel
.query()
.deleteById(request.body.id);
return res.ok(users, "Successfully delete users", reply)
}
module.exports = {
get, show, destroy, store, update
};
Kita lihat pada method get, kita menggunakan query .eager yang berfungsi untuk menampilkan data dari model yang berelasi dari table users yaitu model phone.
Lanjut, kita buat PhoneController.js
'use strict'
const res = require('./ResponseController')
const phoneModel = require('./PhoneModel');
async function get (request, reply) {
const phone = await phoneModel
.query()
.orderBy('user_id', 'ASC');
return res.ok(phone, "", reply)
}
async function store (request, reply) {
let userId = request.body.user_id;
let phoneNum = request.body.phone;
let description = request.body.description;
const phone = await phoneModel
.query()
.insert({ user_id: userId, phone_number: phoneNum, description: description });
return res.ok(phone, "Successfully add phone number", reply)
}
async function show (request, reply) {
let id = request.params.user_id;
const phone = await phoneModel
.query()
.where('user_id', '=', id);
return res.ok(phone, "", reply)
}
async function update (request, reply) {
let phoneNum = request.body.phone;
let id = request.body.id;
let description = request.body.description;
const person = await phoneModel
.query()
.findById(id)
.patch({
phone_number: phoneNum, description: description
});
return res.ok(person, "Successfully update phone number", reply)
}
async function destroy (request, reply) {
const phone = await phoneModel
.query()
.deleteById(request.body.id);
return res.ok(phone, "Successfully delete phone number", reply)
}
module.exports = {
get, show, destroy, store, update
};
Terakhir, kita buat routes.js tempat menaruh routingnya
let users = require('./UserController');
let phone = require('./PhoneController');
async function routes (fastify, options) {
fastify.get('/users', users.get);
fastify.get('/users/:id', users.show);
fastify.post('/users', users.store);
fastify.put('/users', users.update);
fastify.delete('/users', users.destroy);
fastify.get('/phone', phone.get);
fastify.get('/phone/:user_id', phone.show);
fastify.post('/phone', phone.store);
fastify.put('/phone', phone.update);
fastify.delete('/phone', phone.destroy);
}
module.exports = routes;
Sekarang silahkan jalankan dan coba dengan Postman
node server.js
Jangan lupa untuk menggunakan x-www-form-urlencoded untuk melakukan POST/PUT/DELETE.
Karena kita menggunakan fungsi eager pada select all user maka kita akan melihat relasi antara table users dan phone seperti pada gambar dibawah ini.
Untuk route yang lain silahkan dicoba sendiri ya ^^
Semoga membantu dan happy coding! ^^