Pada saat kita mempelajari SQL, pasti erat dengan kata kata Associations
bukan?
Mari kita coba mengingat lagi yah, associations itu apa !
Associations
, merupakan relasi atau hubungan antar 2 entitas objek
berdasarkan adanya suatu atribut yang memiliki kesamaan.
Gunanya untuk apa?
Gunanya adalah untuk membuat satu entitas dapat mengakses data dari entitas lainnya, via atribut yang sama ini.
Contoh: Pada sebuah Departemen yang memiliki suatu data unik, pastinya bisa mengakses karyawan yang terdaftar pada Departemen tsb karena tiap karyawan memiliki data unik dari Departemen tersebut.
Hubungan / relasi ini sifatnya bisa banyak:
- 1-to-1 (One-to-One)
- 1-to-n (One-to-Many)
- n-to-n (Many-to-Many)
Karena kita sedang belajar sequelize
, Pertanyaannya sekarang adalah,
bagaimanakah cara kita mengimplementasikannya dalam sequelize
?
Apakah memungkinkan ?
sequelize
ini memungkinkan sekali untuk membuat association
, bahkan
tanpa kita perlu menulis query nya loh !
Tapi selalu ada harga yang dibayarkan untuk membuat hal-hal seperti ini,
harganya adalah kita harus mengerti bagaimana cara untuk membuat association
ini dengan cara sequelize
itu sendiri !
Contohnya bagaimana ?
Dalam sequelize
punya Model
, berarti kita harus:
- Mendefinisikan bahwa
Orang
memiliki sebuahProfile
- Mendefinisikan bahwa
Profile
adalah bagian dari / milikOrang
Caranya adalah, kita harus memodifikasi bagian models
yang dibuat dengan
mendefinisikan association pada method associate yang ada.
// File: models/orang.js
...
static associate(models) {
// hasOne
// sourceKey <-- untuk Source punya PK
// foreignKey <-- untuk Target punya FK
// -------------------------------------
// by default akan melihat apakah pada Target memiliki SourceId
// e.g. OrangId pada Profils
Orang.hasOne(Profil, {
sourceKey: 'id',
foreignKey: 'OrangId'
});
}
// File: models/profil.js
...
static associate(models) {
// belongsTo
// foreignKey <-- untuk Source punya FK
// targetKey <-- untuk Target punya PK
// ----------------------------------------------
// by default akan melihat apakah pada Source memiliki TargetId
// e.g. OrangId pada Profils
Profil.belongsTo(Orang, {
foreignKey: 'OrangId',
targetKey: 'id'
});
}
Dalam sequelize
punya Model
ini, berarti kita harus:
- Mendefinisikan bahwa
Creator
memiliki banyakVideo
- Mendefinisikan bahwa
Video
dimiliki olehCreator
Caranya adalah, kita harus memodifikasi bagian models
yang dibuat dengan
mendefinisikan association pada method associate yang ada.
// File: models/creator.js
...
static associate(models) {
// hasMany
// sourceKey <-- untuk Source punya PK
// foreignKey <-- untuk Target punya FK
// ------------------------------------------------------------
// by default akan melihat apakah pada Target memiliki SourceId
// e.g. CreatorId pada Videos
Creator.hasMany(models.Video, {
sourceKey: 'id',
foreignKey: 'CreatorId'
});
}
// File: models/video.js
...
static associate(models) {
// belongsTo
// foreignKey <-- untuk Source punya FK
// targetKey <-- untuk Target punya PK
// ----------------------------------------------
// by default akan melihat apakah pada Source memiliki TargetId
// e.g. CreatorId pada Videos
Video.belongsTo(models.Creator, {
foreignKey: 'CreatorId',
targetKey: 'id'
});
}
n-to-n
Untuk n-to-n akan dibahas pada pembelajaran selanjutnya
Sekarang mari kita mencoba untuk membuat aplikasinya (1-to-n)
Requirement dari aplikasi ini adalah:
-
Diberikan sebuah database pada
postgresql
dengan namadevelopment
-
Dengan diberikan sebuah data
data/creator.json
dandata/video.json
, buatlah tabel dengan namaCreators
danVideos
. -
Tabel
Creators
danVideos
akan memiliki kolom yang dapat dilihat pada Tabel 1 -
Buatlah sebuah aplikasi web sederhana yang akan memiliki endpoint yang dapat dilihat pada Tabel 2
Creators
Kolom | Tipe | Deskripsi |
---|---|---|
id | SERIAL | PRIMARY KEY |
name | VARCHAR(255) | NOT NULL |
channel | VARCHAR(255) | NOT NULL |
joined | VARCHAR(255) | NOT NULL |
subscribers_count | VARCHAR(255) | NOT NULL |
createdAt | TIMESTAMP | NOT NULL |
updatedAt | TIMESTAMP | NOT NULL |
Videos
Kolom | Tipe | Deskripsi |
---|---|---|
id | SERIAL | PRIMARY KEY |
name | VARCHAR(255) | NOT NULL |
link | VARCHAR(255) | NOT NULL |
channel | VARCHAR(255) | NOT NULL |
view_count | INTEGER | NOT NULL |
CreatorId | INTEGER | FOREIGN KEY |
createdAt | TIMESTAMP | NOT NULL |
updatedAt | TIMESTAMP | NOT NULL |
Endpoint | Deskripsi |
---|---|
GET / | Menampilkan list semua link |
GET /creators | Menampilkan list data Creators |
GET /videos | Menampilkan list data Videos |
GET /combos | Menampilkan list data gabungan Creators dan Videos |
Jadi setelah melihat requirement seperti, apa sajakah yang harus kita lakukan?
Disclaimer:
Pada pembelajaran kali ini, sudah disediakan starter-pack
, yang akan
digunakan untuk mempelajari bahan ini, sehingga diharapkan sebelum mempelajari
bahan ini, Anda sudah mengerti pembuatan views
pada express
dengan ejs
dan routing endpoint
pada express
.
Pertama-tama kita harus menginisialisasi folder dan module yang dibutuhkan dengan cara:
- Melakukan
npm init -y
- Menginstall module yang dibutuhkan
express
,ejs
,pg
, dansequelize
dengannpm install express ejs pg sequelize
- Menginstall module yang dibutuhkan pada saat
development
yaitunodemon
dansequelize-cli
dengannpm install -D nodemon sequelize-cli
Maka setelah menjalankan langkah di atas, bentuk folder starter-pack
akan menjadi seperti ini:
.
├── controllers
│ └── controller.js
├── data
│ ├── creator.json
│ └── video.json
├── node_modules
│ └── ...
├── routes
│ └── ...
├── views
│ └── ...
├── .gitignore
├── app.js
├── package.json
└── package-lock.json
Selanjutnya setelah kita melakukan inisialisasi dan konfigurasi sequelize
dengan cara:
- Inisialisasi sequelize dengan
npx sequelize-cli init
- Setelah perintah di atas diketik, maka akan terbentuk folder:
config
, yang digunakan untuk menyimpan konfigurasi databasemigrations
, yang digunakan untuk membuat atau mengubah tabel pada databasemodels
, yang digunakan untuk membuat Model representasi tabel dataseeders
, yang digunakan untuk memasukkan data awal.
- Mengedit file konfigurasi pada
config/config.json
dengan memasukkan credential yang tepat, dandialect
yang diubah kepostgres
- Setelah melakukan konfigurasi di atas, dengan menggunakan GUI / CLI untuk
postgresql
buatlah database dengan namadevelopment
.
Selanjutnya adalah kita akan membuat model Creator
dengan tabel Creators
dan model Video
dengan tabel Videos
sekaligus dengan menggunakan
sequelize
. Caranya adalah dengan:
- Melihat ulang terlebih dahulu struktur tabel yang dibutuhkan, perhatikan
bahwa pada struktur tabel memiliki
id
, namun pada saat kita menggunakansequelize
kita tidak perlu menuliskan hal tersebut, karena akan dibuat secara otomatis. - Berdasarkan info ini, maka pada tabel
Creators
, yang butuh dibuat adalahname
,channel
,joined
, dansubscribers_count
. - Berdasarkan info ini, maka pada tabel
Videos
, yang butuh dibuat adalahname
,link
,channel
, danview_count
. - Harus diingat juga ketika membuat model dan tables / migrations, perhatikan
bahwa nama pada Model =
Singular
dan nama table =Plurals
.Jangan sampai terbalik yah !
- Sehingga, berdasarkan data ini, maka yang harus diketik adalah
npx sequelize-cli model:generate --name Creator --attributes \ name:String,channel:String,joined:String,subscribers_count:String
dannpx sequelize-cli model:generate --name Video --attributes \ name:String,link:String,channel:String,view_count:Integer
- Setelah perintah di atas diketik, maka akan terbentuk sebuah file pada
folder
models
dan sebuah file pada foldermigrations
, coba lihat file padmodels
danmigrations
untuk mengetahui lebih lanjut bagaimaan kode dibuat. - Dikarenakan kita di sini masih ingin menggunakan
promise
instead ofasync / await
, maka kita akan melakukan edit pada filemigrations
yang baru dibuat olehsequelize
, contoh perubahan untuk mengubahasync / await
menjadipromise
dapat dilihat pada Code 00 - Jangan lupakan untuk seluruh data ditambahkan property
allowNull: false
pada filemigrations
yang terbentuk. - Selanjutnya, kita akan menjalankan perintah untuk membuat tabel ini dengan
menjalankan perintah
npx sequelize-cli db:migrate
- Setelah menjalankan perintah ini, maka dapat dilihat pada postgresql bahwa
tabel
Creators
danVideos
sudah terbentuk.
'use strict';
module.exports = {
// Awalnya di sini ada async bukan ?
// kita akan mengubah nya dengan cara
// membuang keyword async tersebut
up: (queryInterface, Sequelize) => {
// Kemudian di sini harusnya ada await bukan?
// kita akan mengganti keyword await tersebut
// menjadi return.
// Hal ini dapat terjadi karena
// queryInterface.createTable sudah bersifat Promise.
return queryInterface.createTable('...', {
...
});
},
// sama dengan yang ada di up tadi,
// keyword asyncnya dibuang
// dan await nya diganti return.
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('...');
}
};
Karena adanya relasi antara dua tabel Creators
(id) dengan Videos
(CreatorId), maka selanjutnya kita akan menambah kolom dan menyambungkan
kolom dengan asosiasi-nya.
Apabila hanya membutuhkan CreatorId
saja, maka kita hanya butuh untuk:
- Membuat file migrations baru untuk
Videos
- Menambahkan kolom
CreatorId
padaVideos
(Migration) - Menambahkan kolom
CreatorId
padaVideo
(Model) - Tidak perlu menambahkan / mendefinisikan Foreign Key pada Tabel
Videos
Langkah-langkahnya adalah:
- Membuat file migration baru dengan cara mengetik perintah:
npx sequelize-cli migration:generate --name add-column-creatorid-to-videos
- Menambahkan kode untuk menambah kolom
CreatorId
pada migration file yang dibuat. Kode dapat dilihat di bawah ini
// File: migrations/<timestamp>-add-column-creatorid-to-videos
module.exports = {
up: (queryInterface, Sequelize) => {
// Di sini kita menggunakan addColumn
// untuk menambahkan kolom CreatorId
return queryInterface.addColumn('Videos', 'CreatorId', {
type: Sequelize.INTEGER,
references: {
model: {
tableName: 'Creators',
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE',
key: 'id'
}
});
},
down: (queryInterface, Sequelize) => {
// Di sini kita akan menghapus
// kolom CreatorId
return queryInterface.removeColumn('Videos', 'CreatorId');
}
};
- Selanjutnya, kita akan menambahkan asosiasi pada
models/creator.js
danmodels/video.js
// File: models/creator.js
class Creator extends Model {
static associate(models) {
// define association here
Creator.hasMany(models.Video, {
sourceKey: 'id',
foreignKey: 'CreatorId'
});
}
};
// File: models/video.js
class Video extends Model {
static associate(models) {
// define association here
Video.belongsTo(models.Creator, {
foreignKey: 'CreatorId',
targetKey: 'id'
});
}
};
Selanjutnya, setelah tabel terbentuk, kita akan memasukkan data yang kita
miliki dalam data/creators.json
dan data/videos.json
menjadi data dalam
tabel kita, oleh karena itu langkah-langkahnya adalah:
- Membuat file seed dengan cara mengetik perintah
npx sequelize-cli seed:generate --name seed-creators
dannpx sequelize-cli seed:generate --name seed-videos
- Setelah mengetik perintah di atas, maka akan terbentuk file baru dengan nama
seeders/<timestamp>-seed-creators.js
danseeders/<timestamp>-seed-videos.js
, buka file tersebut dan kita akan mengedit file tersebut. Kode yang akan ditulis dapat dilihat pada Code 01 - Setelah menuliskan kode tersebut, kita akan melakukan seeding dengan cara
mengetik
npx sequelize db:seed:all
'use strict';
// 01.
// Jangan lupa fs karena kita mau baca json
const fs = require('fs');
module.exports = {
up: (queryInterface, Sequelize) => {
// 02.
// Di sini kita akan membaca filenya terlebih dahulu
// Ingat bahwa pada sequelize semua tabel akan memiliki 2 kolom tambahan
// createdAt dan updatedAt
// sehingga kita harus memasukkan data tersebut.
let creators = JSON.parse(fs.readFileSync('./data/creators.json', 'utf8'));
creators = creators.map(elem => {
// Jangan lupa dipetakan karena dalam tabel Creators dibutuhkan
// 2 tambahan kolom ini
elem.createdAt = new Date();
elem.updatedAt = new Date();
return elem;
})
// 03.
// Masukkan data ke dalam tabel Creators
// Kita gunakan
// return queryInterface.bulkInsert('NamaTabel', arrayObj, opt)
return queryInterface.bulkInsert(
'Creators',
creators,
{}
);
},
down: (queryInterface, Sequelize) => {
// Ceritanya, kalau ada up (kita melakukan)
// down (kita mereverse apa yang kita lakukan)
// Kita gunakan
// return queryInterface.bulkDelete('NamaTabel', arrayObj, opt)
return queryInterface.bulkDelete('Creators', null, {});
}
};
'use strict';
// 01.
// Jangan lupa fs karena kita mau baca json
const fs = require('fs');
module.exports = {
up: (queryInterface, Sequelize) => {
// 02.
// Di sini kita akan membaca filenya terlebih dahulu
// Ingat bahwa pada sequelize semua tabel akan memiliki 2 kolom tambahan
// createdAt dan updatedAt
// sehingga kita harus memasukkan data tersebut.
let videos = JSON.parse(fs.readFileSync('./data/videos.json', 'utf8'));
videos = videos.map(elem => {
// Jangan lupa dipetakan karena dalam tabel Videos dibutuhkan
// 2 tambahan kolom ini
elem.createdAt = new Date();
elem.updatedAt = new Date();
return elem;
})
// 03.
// Masukkan data ke dalam tabel Videos
// Kita gunakan
// return queryInterface.bulkInsert('NamaTabel', arrayObj, opt)
return queryInterface.bulkInsert(
'Videos',
videos,
{}
);
},
down: (queryInterface, Sequelize) => {
// Ceritanya, kalau ada up (kita melakukan)
// down (kita mereverse apa yang kita lakukan)
// Kita gunakan
// return queryInterface.bulkDelete('NamaTabel', arrayObj, opt)
return queryInterface.bulkDelete('Videos', null, {});
}
};
Sampai dengan tahap ini, artinya kita sudah berhasil mendefinisikan asosiasi
pada sequelize
dengan baik.
Selanjutnya adalah saatnya kita untuk memodifikasi dan menambahkan kode untuk melihat data yang sudah terhubung ini.
Selanjutnya, seperti umumnya langkah kita dalam menampilkan data ini,
maka kita akan mencoba untuk memodifikasi kode yang ada pada
controllers/controller.js
agar dapat menampilkan data yang sudah kita
gabungkan ini.
Untuk dokumentasi sequelize
nya dapat dilihat pada Referensi
Fetching data Associations
Terdapat 2 cara untuk melakukan penampilan data ini, yaitu dengan menggunakan
Lazy Loading
dan Eager Loading
:
Lazy Loading
--> DataAssociation
akan dipanggil via methodgetNamaTarget
, mengembalikan PromiseEager Loading
--> DataAssociation
akan dipanggil pada settingoption
pada method querynya.
Cara menggunakan Lazy Loading
, misalnya pada Creator
kita ingin
memanggil Video
atau sebaliknya, maka
class Controller {
static returnCreatorWithVideos(req, res) {
Creator.findOne({
where: {
id: 1
}
}) // <-- mengembalikan promise
.then((data) => {
// Ingat, karena Creator hasMany Video,
// maka method yang dipanggil bentuk jamak
return data.getVideos(); // <--- sifatnya promise lagi
})
.then((data) => {
res.send(data);
})
.catch(err) => {
res.send(err);
}
}
static returnVideoWithCreator(req, res) {
Video.findOne({
where: {
id: 1
}
}) // <--- mengembalikan promise
.then((data) => {
// Ingat, karena Video belongsTo Creator,
// maka method yang dipanggil bentuk singular
return data.getCreator(); // <--- sifatnya promise lagi
})
.then((data) => {
res.send(data);
})
.catch((err) => {
res.send(err);
})
}
}
Cara ini kurang efisien bukan ? karena apabila harus menggunakan findAll
,
maka masih harus melakukan looping per element untuk memanggil method untuk
menampilkan data tersebut. Maka dari itu, ada suatu cara yang kedua yang
dinamakan dengan Eager Loading
supaya lebih cepat untuk menampilkan datanya,
caranya pun cukup mudah yaitu dengan menambahkan option include
pada method
querynya, cara ini juga yang akan digunakan pada kode kita.
// File: controllers/controller.js
...
static getCombinationRootHandler(req, res) {
// Apabila dilihat dari sisi Creator
Creator
.findAll({
// Jangan lupa menambahkan option include, valuenya berupa Array
include: [
Video
]
})
.then(data => {
res.send(data);
})
.catch(err => {
res.send(err);
});
}
// File: controllers/controller.js
static getCombinationRootHandler(req, res) {
// Apabila dilihat dari sisi Video
Video
.findAll({
// Jangan lupa menambahkan option include, valuenya berupa Array
include: [
Creator
]
})
.then(data => {
res.send(data);
})
.catch(err => {
res.send(err);
});
}
Dan dengan demikian artinya kita sudah selesai untuk mempelajari bagaimana
caranya kita untuk melakukan Assocation
pada sequelize dan cara untuk
menampilkan data kombinasi Association
tersebut !